上一课:
【小黑嵌入式系统第十四课】μC/OS-III程序设计基础(三)——信号量(任务同步&资源同步)、事件标记组(与&或&多个任务)
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站:人工智能
一个任务或者ISR有时需要和另一个任务交流信息,这个信息传递的过程称为任务间(或ISR与任务间)的通信。有两种途径可实现:全局变量、消息队列。
方式1:通过全局变量
全局变量为共享资源,每个任务或ISR在使用它时都必须保证对其的独占性。若有ISR参与使用,则唯一能保证对共享变量独占访问的方法就是关中断;如果只是任务间共享全局变量,则可通过——关中断、给调度器上锁、使用信号量或者互斥信号量。
(这里假定对全局变量的操作不能在一条CPU指令中完成)
注意:
任务要想与ISR通信(发送信息?ISR,注意方向),只能通过全局变量;
若ISR修改了全局变量值,任务并不能知道,除非ISR通知任务(如发送信号量等方式),或者任务定期地查询全局变量的值。
方式2:通过消息队列
消息可以通过消息队列作为中介发送给任务,也可直接发送给任务(μC/OS-III中,每个任务都有其内建的消息队列,称为任务消息队列,任务消息队列在任务创建时自动建立)。
任务在等待消息时不占用CPU时间
另外需注意:发送的消息不会被复制一份再放置到消息队列中,而是使用引用传递。因此需保证消息内容在接收消息的任务代码内可见。(不能在任务接收到消息时,该消息内容已无效了(如用自动变量保存消息内容而此时自动变量却已被释放))
消息队列就象一个类似于缓冲区的对象,可以实现同步和数据通信。
消息队列具有一定的容量,可以容纳多条消息。
消息队列中的消息一般按照先入先出(FIFO)的方式放置,但在需要时也可安排为后入先出(LIFO,在发布消息时选择)。
当任务往消息队列中发送消息时,可选择只将该消息发送给一个任务。当前等待消息的任务中只有最高优先级的那个将接收到消息,或最先进入等待消息列表的(同优先级)任务。
注:也可选择以广播的形式发送消息,那么所有“等待此消息的”任务都将获得该消息。
如果没有任务在等待消息队列的消息,则发送消息时会判断消息队列当前是否已满 。
消息队列中已存在消息,通过内核服务将消息传递给等待消息的任务中优先级最高的任务,或最先进入等待消息任务列表的(同优先级)任务。
如果消息队列为空,则等待消息的任务被放入等待消息的任务列表中,直到有其它任务向消息队列发送消息后,该任务才能结束等待状态或在等待超时的情况下运行。
OSQPend()
函数允许用户定义一个最长的等待时间Timeout
作为它的参数,这样可以避免该任务无休止地等待下去。
内核提供以下消息队列服务:
与信号量相比,消息队列不仅可以实现同步,而且通过缓冲的方式来传递多个数据信息,从而避免了信息的丢失或混乱。
消息队列有3种状态,即空状态(消息队列中没有任何消息)、满状态(消息队列中的每个存储单元都存放了消息)、正常状态(消息队列中消息但又没有到满的状态)。
多对多与全双工的工作方式也可实现,但不常见。
让一个LED以传递过来的参数确定点亮时间,以此示例来说明如何使用消息队列来实现任务之间的数据通信,假设TaskLED
为高优先级的任务。两个任务的处理流程如下。
LED
任务的代码如下。
发送延时参数任务SendDly
的代码如下。
为了说明如何使用消息队列来实现多任务接收数据,我们设计一个系统,按键一按下,LED按照指定节奏闪烁,蜂鸣器按照指定节奏鸣响。三个任务的处理流程如下。
TaskKEY
任务主要代码如下。
LED
任务的代码如下。
Beep
任务主要代码如下。
在μC/OS-III中,每个任务都有它自己的内嵌消息队列,称为任务消息队列。任务消息队列是在任务创建OSTaskCreate()
时创建的,因此任务创建之后便可以直接使用。
任务消息队列使用起来更方便。
当用户明确知道该给哪个任务发消息时,此时就可以使用任务消息队列。
μC/OS-III中的任务消息队列服务函数以OSTaskQ???()
命名。
使用任务消息队列做任务间的通信,可参考示例程序:“Micrium_CY8CKIT-050B_uCOS-III-Q_GNU(PSoC Creator 4.0).rar”
使用一个计数型信号量,初值为允许生产者发布的消息数目。如:消费者最多缓存10则消息,则该计数型信号量的初值为10。
ANSI C中,可以使用malloc()
和free()
两个函数来动态分配内存,在嵌入式系统中,它们一般也是可用的,但并不适合。如图为被两个函数分配过的内存区。
为了避免上面的问题,μC/OS-III自己设计了一套动态内存分配系统。μC/OS-III的动态内存分配是以块为单位分配的,一次只能分配一个块,块的大小可以由用户来定义。
μC/OS-III的动态内存管理是数据队列的绝佳伴侣,配合使用异常方便 。
将os_cfg.h中的宏OS_CFG_MEM_EN
设置为1即可使能存储管理服务。
动态内存管理的3个系统函数:
让一个LED以传递过来的参数确定点亮时间,以此示例来说明如何用动态内存管理来实现数据通信。两个任务的处理流程如下。
LED
任务的代码如下。
发送消息任务SendDly
的代码如下。
μC/OS-III可为应用程序提供定时器及相关服务,它和系统内部任务“定时器任务”相关,定时器服务的启动由os_cfg.h
中的宏OS_CFG_TMR_EN
设定。
这里的定时器是软件方式实现的递减定时器,共用时钟节拍任务中的时钟节拍硬件定时器。因此时间分辨率不会超过时钟节拍定时器。通常设置为比较粗的时间分辨率。
定时器的计数值减为0时,会引发一个操作,该操作由操作系统调用一个用户定义的回调函数(运行在定时器任务环境中)来实现。
定时器的使用比较简单,详情请见μC/OS-III电子书的第12章“定时器管理”。
关于(软件)定时器的使用,可参考示例程序:
“Micrium_CY8CKIT-050B_uCOS-III-Sem-ISR-Tmr_GNU(PSoC Creator 4.0).rar”
关于任务信号量、任务消息队列,最后提供一个实际应用示例项目:
“CY8CKIT-050B_uCOS-III-DispShift-TkQ_GNU(PSoC Creator 4.0).rar”
该项目基于μC/OS-III 操作系统实现,它使用LCD1602,演示了当 LCD 显示区域不足以显示出全部文字时,如何对 LCD 显示内容进行循环移动,分批显示出文字的各个部分。在无操作系统的情况下,要实现令人满意的同样效果,则会遇到许多不易解决的麻烦。
LCD 的显示内容可以在运行时更改,通过给 LCD 显示任务发任务消息的方式来告知新的显示数据和请求源的属性。
程序适用于具有1~4行显示能力的、HD44780 控制器兼容的 LCD 模块。