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 天。希望本文的提出对开发嵌入式操作系统的技术人员能有所帮助,同时也希望同一领域的开发人员共同探讨、共同发展。
上一篇:三种现场总线隔离方法解析
下一篇:最后一页
推荐阅读
史海拾趣
Corstat Containers公司在电子行业中崭露头角,始于一次技术突破。当时,公司研发团队成功开发出一种新型的防静电电子元件包装盒,有效解决了电子产品在运输过程中因静电导致的损坏问题。这一创新产品迅速获得了市场的认可,许多知名电子品牌纷纷与Corstat Containers公司建立合作关系。随着订单量的不断增加,公司的规模逐渐扩大,生产线也更加完善。
背景:为了满足全球客户的需求,GardTec开始实施全球化战略,在亚洲和美国等地设立全球制造厂和库存地。
发展:通过在全球范围内的战略布局,GardTec不仅提升了产品的供应效率,还更好地服务了世界各地的客户。公司的全球化布局进一步巩固了其在风扇配件市场的领先地位。
影响:全球化战略的实施,使得GardTec的产品能够迅速响应市场变化,满足不同地区客户的多样化需求,从而增强了公司的市场竞争力。
背景:在电子产品行业,产品品质和安全合规性至关重要。GardTec深知这一点,始终将产品品质放在首位。
发展:公司建立了严格的质量控制体系,确保每一件产品都符合最高的品质标准。同时,GardTec还积极遵循国际安全标准和法规要求,如REACH/RoHS等,确保产品的合规性。
影响:高品质的产品和严格的合规性要求,使得GardTec在电子行业中赢得了客户的信赖和好评,进一步提升了公司的品牌形象和市场竞争力。
在追求经济效益的同时,Dae Ryung Electronic Co Ltd公司也积极履行社会责任并推动可持续发展。公司注重环保和节能减排工作,在生产过程中采用环保材料和工艺减少污染排放。同时,公司还积极参与社会公益事业和慈善活动回馈社会。这些做法不仅提升了公司的社会形象也促进了企业与社会的和谐发展。
随着工业4.0时代的到来,FOSLINK公司积极响应智能制造的号召,投入巨资引进先进的自动化生产设备和智能管理系统。通过实施智能制造战略,FOSLINK不仅实现了生产过程的数字化、网络化和智能化,还大幅提升了生产效率和产品质量。这一变革不仅降低了公司的生产成本,还增强了其在全球市场的竞争力。FOSLINK的智能制造实践,为电子行业树立了新的标杆。
为了扩大市场份额和提高品牌知名度,BENCENT公司开始积极参与国内外各类电子展会和论坛,与潜在客户和合作伙伴建立联系。同时,公司还加大了营销宣传力度,通过线上线下的方式提升品牌曝光度。这些努力不仅带来了更多的销售机会,也提升了公司在行业内的地位和影响力。
有调通过marvell wifi 8686 sdio接口驱动来讨论下??? 正在调sdio接口的wifi 8686驱动,sdio确认好的,读写寄存器,加载固件都没问题。有以下问题: 1. 我是直接把驱动放在bsp中编的,wince启动后自动加载wifi驱动, 并且这个wifi驱动会自行搜索热点。我的理解是wifi驱动加载固件启动完wifi芯片注册网络 ...… 查看全部问答∨ |
|
SESSION_POS_S *pTestpos1, *pTestpos2, *pTestpos3, *pPos; LIST *pMyList = NULL; int NodeIndex = 0; DPRINT("-------- lstAdd测试 -------\\n ...… 查看全部问答∨ |
|
用电位器输出一个电压值,从我watch窗口看到的ADCresult值都在变化,变化比较大。如0xabc0,b为都会能差4,帮忙分析一下可能那里设置不对。谢谢!… 查看全部问答∨ |
|
想请问你一下 那个ez430-rf2500为什么ed连上电池两个灯还是不闪。。ap的红灯在闪烁。。并且PC上温度只有一个ap温度显示。。就是说ed的没有反应。。好像是没有连接到网络。。后来又折腾了一下。。现在ap的红绿灯都在闪。。但是ed还是没反应。 ...… 查看全部问答∨ |
|
#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 ...… 查看全部问答∨ |
module main ( ....); wire clk1MHz; // 产生1MHz的时钟波形 // 输入为20MHz的时钟 clock_divider instT1MHz &nb ...… 查看全部问答∨ |