历史上的今天

今天是:2024年11月14日(星期四)

2019年11月14日 | 基于ERTOS的CAN总线驱动设计

发布者:HeavenlyLove 关键字:嵌入式计算机  μC-OS  ERTOS 手机看文章 扫描二维码
随时随地手机看文章

MC/OS-II 是美国人 Jean Labrosse 编写的一个免费的、源码公开的嵌入式实时内核。对于开发计算机嵌入式应用产品的技术人员来说是一个实用价值很高的实时嵌入式操作系统 ERTOS(Embedded Real Time Operation System)。

 

要开发出完善的 ERTOS,就要在多任务的调度和对 I/O 设备操作的稳定性、协调性方面做出大量的工作,这也是我在开发 ERTOS 过程中深深体会到的重点所在。希望本文能对开发 ERTOS 的技术人员在多任务信息流和 I/O 驱动方面有所启迪。


1 多任务信息流关键技术


在讨论多任务信息流之前,先讨论一下多任务的工作状态。在μC/OS 中,每个任务都是无限循环的,每个任务都处在以下五种状态之一:休眠态、就绪态、运行态、挂起态和中断态,如图 1 所示。

 

 

在多任务的调度和驱动程序的编写过程中,必然要涉及到公用代码段和共享存储区的保护问题。即使是原有的 C 函数,可重用性方面在没有得到理论和实践的验证情况下也需要对其进行保护。这样就需要合理的算法对公用代码段、共享存储区进行保护,避免操作系统在运行过程中产生重用性问题而导致运行结果不可预测。

 

系统在开发过程中,既要考虑到减少系统的复杂程度,也要兼顾其稳定性与运行效率的要求。这就需要我们对各种算法进行合理的选择:在稳定性可以保障的情况下,选择相对简单,占用 CPU 时间少的算法;在稳定性不能保障的情况下,考虑选择周全的算法。只有这样才能使操作系统在一定的配置环境下达到最高的运行效率。

 

接下来分别用 void CanSendMessageProcess(void *data)、void CanSendMessage(void *data)、void CanReceiveMessageProcess(void *data)和 void CanReceiveMessage(void *data)这四个任务来描述在采用消息队列、邮箱和信号量通信机制时的信息流的传递过程。

 

(1)消息队列通信机制

 

消息队列在初始化的时候,建立一个指定空间大小的数组,这个数组在使用的时候取得了环形缓冲区的概念。这个数组在运行期间不会被消除,这样就避免了重复建立数组的时候内存空间的泄漏问题。当一个任务向消息队列发送一个信息的时候,相应的指针加 1(OSQIn+1),队列满时(OSQEntries = OSQSize),OSQIn 则与 OSQOut 指向同一单元。如果在 OSQIn 指向的单元内插入新的指向消息的指针,就构成 FIFO(First-In-First-Out)队列。相反,如果在 OSQOut 指向单元的下一个单元插入新的指针,就构成 LIFO 队列(Last-In-First-Out)。在本实例中,我们定义 FIFO 队列。消息指针总是从 OSQOut 指向的单元取出。OSQStart 和 OSQEnd 定义了消息指针数组的头和尾,以便在 OSQIn 和 OSQOut 到达队列的边缘时,进行边界检查和必要的指针调整,实现其循环功能。

 

消息队列数据结构如下:

 

typedef struct os_q {

 

struct os_q *OSQPtr; /* 在空闲队列控制块中链接所有的队列控制块*/

 

void *OSQStart; /*指向消息队列的指针数组的起始地址的指针*/

 

void *OSQEnd; /* 指向消息队列结束单元的下一个地址的指针*/

 

void *OSQIn; /* 指向消息队列中插入下一条信息位置的指针*/

 

void *OSQOut; /* 指向消息队列中下一个取出消息位置的指针*/

 

INT16U OSQSize; /* 消息队列中总的单元数*/

 

INT16U OSQEntries; /*消息队列中总的消息数量*/

 

} OS_Q;

 

图 2 为消息队列信息流的演示说明。

 

① CanSendMessageProcess 任务完成信息的计算工作以后,将要发送的信息送进消息队列 1。

 

② CanSendMessage 任务负责取得消息队列 1 里面的信息。

 

③ 通过 CAN 总线 I/O 端口将数据发送到总线上去。如果消息队列中没有信息,则该任务由运行状态进入等待状态,直到从消息队列中接收到信息为止。

 

④ CanReceiveMessage 任务负责读取总线上面的信息。

 

⑤ CanReceiveMessage 任务将读取到的信息送入消息队列 2。

 

⑥ CanReceiveMessageProcess 任务是从消息队列 2 中取出信息开始计算工作,如果消息队列为空的话,该任务进入等待状态。

 

消息队列适用于一对一、一对多、多对多和多对一的关系。也就是说,消息队列可以作为一块共享的公共区域,为实施互斥,任务间需要同步;为了合作,进程间需要交换信息,这样也就实现了同步和通信。

 

 

(2)邮箱通信机制

 

邮箱的概念和管道(管线)有相似的定义,一个任务或者中断服务子程序向另一个任务发送一个指针型的变量,该指针指向一个包含了特定“消息”的数据结构。在源端的任务只能向邮箱写,在目的端的任务只能从邮箱读。邮箱传输流数据,即连续的字节串或流。因此,访问一个邮箱就像是访问一个顺序文件。邮箱可以用来通知一个事件的发生(发送一条信息),也可以用来共享某些资源,这样邮箱就被当成一个二值信号量。

 

图 3 为邮箱信息流的演示说明。

 

① CanSendMessageProcess 任务将计算好的数据发送给 CanSendMessage 任务,然后进入就绪态等待应答信号。CanSendMessage 在接收的同时发送应答握手信号给 CanSendMessageProcess,确认信息接收完毕。

 

②CanSendMessage 任务将 CanSend MessageProcess 任务发送来的信息发送到 CAN 总线,发送结束后进入就绪态等待下一次传输工作。

 

③ CanReceiveMessage 任务接收来自总线的信息流,将接收到的信息发送到 Can  ReceiveMessageProcess 任务,进入就绪态等待应答信号。

 

④ CanReceiveMessageProcess 任务收到信息后发送应答握手信号。

 

(3)信号量通信机制

 

信号量(semaphore)是一种约定机制:两个或多个任务通过简单的信号进行合作,一个任务可以被迫在某一位置停止,直到它接收到一个特定的信号。在多任务内核中普遍将信号量用于:

 

◇ 标志某事件的发生;

 

◇ 控制共享资源的使用权(满足互斥条件);

 

◇ 使两个任务的行为同步。

 

信号量主要实施三种操作:

 

◇ 一个信号量可以初始化为非负数;

 

◇ 等待(wait)操作使信号量减 1。如果值变成负数,则执行等待的任务被阻塞。

 

◇ 得到 CPU 使用权的任务 singal 操作使信号量加 1。如果值不是正数,则被等待操作阻塞的任务被解除阻塞。

 

为了满足信息传递过程中实时高效的原则,在消息队列中部分地引入信号量的概念。也就是 CanSendMessageProcess 任务,把若干个字节的信息一次性地发送到消息队列,令信号量加 1 并由运行态进入等待挂起状态。在 CanSendMessage 任务获得信号量后进入就绪态,等待 CPU 的使用权进入运行态。进入运行态后,该任务使信号量减 1 并从消息队列中取出信息后通过 I/O 端口发送到 CAN 总线。CanReceiveMessage 任务和 CanReceive MessageProcess 任务执行与上面相反的操作。这个实例说明了信号量用于标志某事件的发生。(见图 2。)

 

  

2 μC/OS-II 的中断处理


μC/OS-II 中,中断服务程序一般用汇编语言来写。以下是中断服务程序的示意代码。

  

用户中断服务程序:

  

保存全部 CPU 寄存器;

  

调用 OSIntEnter 或 OSIntNesting 直接加 1;

  

执行用户代码做中断服务;

  

调用 OSIntExit;

  

恢复所有 CPU 寄存器;

  

执行中断返回指令;

  

这里μC/OS-II 提供了两个 ISR 与内核的接口函数:OSIntEnter 和 OSIntExit。OSIntEnter 通知μC/OS-II 内核,中断服务程序开始运行了。实际上,此函数做的工作是把一个全局变量 OSIntNesting 加 1。在 x86 等有累加指令的 CPU 中,可以用指令代替 OSIntEnter:

  

INC BYTE PTR OSIntNesting

  

此中断嵌套计数器可以确保所有中断处理完成后再作任务调度。另一个接口函数 OSIntExit 则通知内核,中断服务已结束。根据相应情况,返回被中断点(可能是一个任务或者被嵌套的中断服务程序)或由内核作任务调度。

  

用户编写的 ISR 必须被安装到某一位置,以便中断发生后,CPU 根据相应的中断号运行准确的服务程序。许多实时操作系统都提供了安装、卸载中断服务程序的 API 接口函数,有些成熟的 RTOS 甚至对中断控制器的管理都有相应的 API 函数。但 μC/OS-II 内核没有提供类似的接口函数,需要用户在对应的 CPU 移植中自己实现。这些接口函数与具体的硬件环境有关,接下来 PC 体系下的中断处理对此有详细的说明。

  

3 PC 体系下的中断


X86 系列的处理器可支持 256 个中断,并用向量表的方法来关联每个中断和相应 ISR 的位置。在实模式下,中断向量表(IVT)存于内存的低端 1K。每个向量表条目占 4 字节,保存一个 ISR 的段地址和偏移信息。PC 系统使用两个级联的可编程中断控制器 82C59A。一个 82C59A 能连接 8 个硬件中断,编号为 IRQ0~IRQ7。 PC 总共可管理 15 个外部中断源,PC 的中断控制器如图 4 所示。(关于 82C59A 的详细使用可参见有关资料。)

  

在μC/OS 下,CAN 总线 I/O 端口中断向量设置伪代码:

  

void CanInitHW(UI segment,BYTE Irq0,BYTE Irq1){

  

保存原有的中断向量

  

保存掩码寄存器的值

  

使 82C59A 的掩码寄存器(0x21)各位置 1,关闭中断输入

  

关闭 CPU 中断

  

设置新的中断向量

  

正在服务的中断禁止再次响应服务(假定当前服务中断是 IRQ5)

  

开 CPU 中断

  

清除 82C59A 的掩码寄存器(0X21、0XA1)各位,开启中断输入

  

}

  

4 信号量与缓冲队列支持下的 CAN 总线驱动


前面介绍了μC/OS-II 内核下多任务调度的关键技术、中断与 PC 体系下中断的一般方法。又以 82C59A 的中断 5(IRQ5)、0x0D 中断向量为例,介绍了中断服务子程序的重新分配和响应 SJA1000 控制器收发的中断服务子程序。

  

下面介绍信号量配合下的环形缓冲队列与中断处理程序之间的关系问题,这也是设备驱动部分的核心内容。

  

ERTOS 的驱动程序与其它操作系统有所不同。比如 Windows、Unix、Solaris、Linux 等操作系统弱化了设备的概念,用户进程对设备的使用可以通过文件系统来完成。然而,在μC /OS-II 上开发 CAN 总线驱动程序没有那么严格,只要满足设备在连续的 CPU 时间上使用时不发生时间重叠就可以了。

  

串行设备或者其它字符型设备都存在外设处理速度和 CPU 速度不匹配的问题,所以需要建立相应的缓冲区。向 CAN 口发送数据时,只要把数据写到缓冲区,然后由 SJA1000 控制器逐个取出往外发。从 CAN 口接收数据时,往往等收到若干个字节后才需要 CPU 进行处理,所以这些预收的数据可以先存于缓冲区。缓冲区可以设置收到若干个字节后再中断 CPU,这样避免了因为 CPU 的频繁中断而降低系统的实时性。

 

 

在对缓冲区读写的过程中,经常会遇到想发送数据时,发送缓冲已满;想去读时,接收缓冲却是空的。对于用户程序端,可以采用查询工作方式,即放弃无法读写的操作,然后再频繁地去尝试这个操作直到成功,这样程序效率显然降低。如果引入读、写两个信号量分别对缓冲区两端的操作进行同步,问题将迎刃而解。用户任务想写但缓冲区满时,在信号量上睡眠,让 CPU 运行别的任务,待 ISR 从缓冲区读走数据后唤醒此睡眠的任务;类似地,用户任务想读但缓冲区空时,也可以在信号量上睡眠,待外部设备有数据来了再唤醒。由于μC/OS-II 的信号量提供了超时等待机制,CAN 口当然也具有超时读写能力。

 

带缓冲和信号量的 CAN 口接收和发送部分见本刊网络补充版()。

 

接口函数总结如下。

 

void CanInitHW(UI segment,BYTE irq0,BYTE IRQ1)

 

/*设置 SJA1000 控制器端口中断向量*/

 

int canReleaseHW() /* 清除 SJA1000 控制器端口中断向量*/

 

int canSendMsg( CANBYTE port, MSG_STRUCT msg)

 

/* 向定制 SJA1000 控制器端口发送数据*/

 

int canReceiveMsg( CANBYTE port, MSG_STRUCT msg_ptr)

 

/*从定制 SJA1000 控制器端口接收数据

 

int canConfig( CANBYTE port, CAN_STRUCT can)

 

/*初始化和配置 SJA1000 控制器 */

 

int canNormalRun( CANBYTE port )

 

/*设置 SJA1000 正常(Normal)运行模式 */

 

int canReset( CANBYTE port )

 

/* SJA1000 控制器端口重新设置,缓冲区置位 0xff*/

 

CANBYTE can0r( CANBYTE addr)

 

/*读取 SJA1000 控制器端口 0 的定制寄存器的值 */

 

CANBYTE can1r( CANBYTE addr)

 

/*读取 SJA1000 控制器端口 1 的定制寄存器的值 */

 

接收和发送数据缓冲区数据结构定义:

 

typedef struct {

 

INT16U RingBufRxCtr; /* 接收缓冲中字符数目 */

 

OS_EVENT RingBufRxSem; /* 接收信号量 */

 

INT8U RingBufRxInPtr; /* 接收缓冲中下一字符的写入位置 */

 

INT8U RingBufRxOutPtr; /* 接收缓冲中下一待读出字符的位置 */

 

INT8U RingBufRx[CAN_RX_BUF_SIZE]; /* 接收环形缓冲区*/

 

INT16U RingBufTxCtr;

 

/* 发送缓冲中字符数目 */

 

OS_EVENT *RingBufTxSem; /* 发送信号量 */

 

INT8U *RingBufTxInPtr;

 

/* 发送缓冲中下一字符的写入位置 */

 

INT8U *RingBufTxOutPtr;

 

/* 发送缓冲中下一待读出字符的位置 */

 

INT8U RingBufTx[CAN_TX_BUF_SIZE]; /* 发送环形缓冲区*/

 

} CAN_RING_BUF;

 

结 语


本文是在嵌入式计算机技术领域的应用背景下提出的,整个工程开发结束以后,系统正常运作时间超过 27 天。希望本文的提出对开发嵌入式操作系统的技术人员能有所帮助,同时也希望同一领域的开发人员共同探讨、共同发展。


关键字:嵌入式计算机  μC-OS  ERTOS 引用地址:基于ERTOS的CAN总线驱动设计

上一篇:三种现场总线隔离方法解析
下一篇:最后一页

推荐阅读

全球最大的汽车半导体解决方案供应商1恩智浦半导体(纳斯达克代码:NXPI)宣布推出适用于各类汽车电池管理系统的新型电池单元控制器产品组合。该产品组合旨在提供业界领先的测量精度和灵活的嵌入式功能安全机制,同时其优势也增强了恩智浦用于功率控制的平台模式。全新的电池单元控制器将与恩智浦全系列世界级汽车微控制器(MCU)、电源管理系统基础芯片(...
近日,武汉华星光电技术有限公司透露,经过 3 年研发攻坚和产能爬坡期,这条全球最大的第六代 LTPS(低温多晶硅)显示面板生产线,截至 10 月底产值已破百亿,达 109 亿元,成湖北首家产值超百亿的半导体显示面板企业。 新闻主体:深圳市华星光电技术有限公司(简称华星光电)是 2009 年 11 月 16 日成立的国家级高新技术企业,总部坐落于深...
据路透社报道,美国总统特朗普于周四签署了一项新的行政命令:禁止美国投资由中国军方支持的31家公司。除涉及国有航空、建筑、核等公司之外,华为、海康威视、中国电信、中国移动也在列。该项禁令将于2021年1月11日上午9:30开始生效,将禁止美国公司和个人直接持有或通过投资基金持有美国政府认为与中国军方有关系的公司的股票。据国内媒体报道,海康威视...

史海拾趣

问答坊 | AI 解惑

有调通过marvell wifi 8686 sdio接口驱动来讨论下???

正在调sdio接口的wifi 8686驱动,sdio确认好的,读写寄存器,加载固件都没问题。有以下问题: 1. 我是直接把驱动放在bsp中编的,wince启动后自动加载wifi驱动, 并且这个wifi驱动会自行搜索热点。我的理解是wifi驱动加载固件启动完wifi芯片注册网络 ...…

查看全部问答∨

protel 99se中,sp232E在哪个库中?

如题。 protel 99se中,SP232E所在的库叫什么?…

查看全部问答∨

lstAdd的使用问题!

    SESSION_POS_S *pTestpos1, *pTestpos2, *pTestpos3, *pPos;     LIST *pMyList = NULL;     int NodeIndex = 0;             DPRINT("-------- lstAdd测试 -------\\n ...…

查看全部问答∨

看看我的成果

本人长期从事单片机开发应用,积累了一定的经验。本人将毕生研究心得集成了文字,放在我的博客里,希望大家光临指导,并给点意见。我的博客地址;http://blog.mcuol.com/user/Article/500.html…

查看全部问答∨

f2812AD转换波动很大,不知是什么原因?

用电位器输出一个电压值,从我watch窗口看到的ADCresult值都在变化,变化比较大。如0xabc0,b为都会能差4,帮忙分析一下可能那里设置不对。谢谢!…

查看全部问答∨

ez430-rf2500遇到了问题。。求指点

 想请问你一下 那个ez430-rf2500为什么ed连上电池两个灯还是不闪。。ap的红灯在闪烁。。并且PC上温度只有一个ap温度显示。。就是说ed的没有反应。。好像是没有连接到网络。。后来又折腾了一下。。现在ap的红绿灯都在闪。。但是ed还是没反应。 ...…

查看全部问答∨

特权同学写的sdram控制器读出的数据全是FF?

最近在用特权同学写的sdram控制器,但是串口读出的数据全是FF,不知道出错在什么地方?用没用过调通的朋友,不吝赐教,万分感谢啊…

查看全部问答∨

高手帮忙看一下这程序

#include<reg51.h>    //  包含51单片机寄存器定义的头文件unsigned char code Tab[ ]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};  //数字0~9的段码unsigned char int_time ; //中断次数计数变量unsigned ...…

查看全部问答∨

Verilog HDL信号类型不一致

module main (                       ....); wire   clk1MHz; // 产生1MHz的时钟波形 // 输入为20MHz的时钟 clock_divider  instT1MHz &nb ...…

查看全部问答∨
小广播
最新汽车电子文章
换一换 更多 相关热搜器件
更多往期活动
随便看看

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved