嵌入式实时操作系统RTOS_FreeRTOS的任务同步
- 任务的同步,指协调任务之间的执行顺序,任务之间不进行数据传输的关系
- 使用信号量、事件标志组、线程标志、互斥量来实现
信号量
信号量的分类
-
二值信号量
取值只为0或1的信号量,取值为0表示事件没有发生,取值为1表示事件已经发生,与之前使用的标志位作用相同
某个任务设置了二值信号量后,另一个任务在需要任务同步的地方调用这一信号量,如果为0则该任务转为阻塞态,直到信号量有效后转为就绪态
-
计数信号量
用于资源的计数,其初始值一般为可用资源的数量
当计数值大于0时,才允许任务执行
信号量的接口函数
-
信号量创建函数osSemaphoreNew
此函数不能在中断服务程序中调用
可以由CubeMX自动生成:在FreeRTOS的配置窗口中,选择Timers and Semaphores 标签页,在下方Binary Semaphores 或Counting Semaphores中点击Add
osSemaphoreNew_t osSemaphoreNew(uint32_t max_count, uint32_t initial_count, const osSemaphoreAttr_t* attr) //max_count:信号量最大计数值,为1时就表示二值信号量 //initial_count:信号量的初值 //attr:指向信号量属性的指针,默认为NULL //返回值为信号量的句柄,用于标识信号量 -
信号量获取函数osSemaphoreAcquire
获取一个信号量,使信号量的值-1
osSatus_t osSemaphoreAcquire(osSemaphoreNew_t semaphore_id, uint32_t timeout) //semaphore_id:所要获取信号量的句柄 //timeout:超时等待时间,以时钟节拍为单位,使用osWaitForever可以一直等 /*返回值为函数的执行状态: osOK:成功获取信号量,信号量的值减一 osErrorTimeout:在给定的超时时间内无法获取信号量,如果超时时间为0则可以在中断中调用 osErrorResource:信号量处在无效状态 osErrorParameter:参数错误*/ -
信号量释放函数osSemaphoreRelease
释放一个信号量,使信号量的值+1
osSatus_t osSemaphoreRelease(osSemaphoreId_t semaphore_id) /*返回值为函数的执行状态: osOK:成功释放信号量,信号量的值加一 osErrorResource:无法释放信号量 osErrorParameter:参数错误*/
信号量使用代码实现
-
二值信号量的使用
以按下按键为事件驱动任务,使用中断检测按键,中断与任务之间用二值信号量同步
注意,在CubeMX中创建信号量后自动生成创建信号量、声明信号量句柄、属性结构体的相关代码,我们只需要使用即可,此处写出仅为方便理解,后文的事件标志组等同理
//创建一个二值信号量 osSemaphoreNew_t KeyBinarySemHandle;//定义按键信号量的句柄 KeyBinarySemHandle = osSemaphoreNew(1,0,NULL);//新建为一个二值信号量,初始值为1 //中断检测按键 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin==B1_Pin) { osSemaphoreRelease(KeyBinarySemHandle);//释放信号量,使之为1 } } //任务函数 void Task01(void *argument) { for{;;} { osSemaphoreAcquire(KeyBinarySemHandle,osWaitForever); //等待信号量,直到信号量为1才进行此任务 } } -
计数信号量的使用
计数信号量除了可以作为可用资源的计数器,也可以用于解决信号积压问题:即接收方无法保证在下一次信号到来前能处理完本次接收的信号时,会出现有效响应的次数会小于实际发送的信号次数的现象,那就可以用计数信号量来防止二值信号量未及时响应的信号被丢弃
//创建一个计数信号量 osSemaphoreNew_t CountSemHandle;//定义按键信号量的句柄 CountSemHandle = osSemaphoreNew(10,0,&CountSem_attributes); void Task01() { for(;;) { osSemaphoreRelease(CountSemHandle);//释放信号量,使之为1 osDelay(1000); osSemaphoreRelease(CountSemHandle); osDelay(1000); } } void Task02() { for(;;) { if(osSemaphoreAcquire(CountSemHandle)==osOK) { //如果正确接收到信号量,执行此任务 //因为此处执行时间比信号量发送第二次的执行时间长 //因此如果用使用二值信号量就会导致错失一次信号发送 } } }
事件标志组
事件标志组概述
-
事件标志组简介
信号量只能实现两个任务之间的同步,如果要实现多个任务的同步,就要使用事件标志组:本质是多个二值信号的组合,每个二值信号就是一个事件标志位,用来表明一个事件是否发生,而一个标志位由相关的任务或ISR(中断服务程序)置位
-
对应事件发生,事件标志组对应标志位有效,触发对应任务,这样的流程实现了多个任务协同控制一个任务
-
显然,这样的流程和我们之前使用标志位时类似,区别在于事件标志组提供了任务的同步和超时处理机制,确保事件所触发的任务能够及时执行
-
独立性同步
独立性同步(OR)指的是等待的任一个事件发生时,就可触发任务
-
关联性同步
关联性同步(and)指的是等待的所有事件都发生了,才触发任务
事件标志组的接口函数
-
事件标志组创建函数onEventFlagsNew
此函数不能在中断服务程序中调用
可以由CubeMX自动生成
osEventFlagsId_t onEventFlagsNew(const osEventFlagsAttr_t *attr) //attr:指向事件组属性的指针 //返回值为事件标志组的句柄,用于标识事件标志组 -
事件标志组设置函数osEventFlagsSet
对事件标志组中的一个或多个事件标志位置位
注意:设置的事件标志位数据类型为
uint32_t,但是FreeRTOS中的一组事件标志组最多只有24个事件标志位,实际需要设置的是bit0-bit23uint32_t osEventFlagsSet(osEventFlagsId_t ef_id, uint32_t flags) //ef_id:事件标志组的句柄 //flags:事件标志组所要设置的标志位,如设置bit0和bit3时,flags=0x09 -
事件标志组等待函数osEventFlagsWait
等待事件标志组中的一个或多个事件标志位,直到等待的事件标志位置位
uint32_t osEventFlagsWait(osEventFlagsId_t ef_id, uint32_t flags, uint32_t options, uint32_t timeout) //ef_id:事件标志组的句柄 //flags:所要等待的标志位 /*options:标志选项 osFlagsWaitAny:等待任意一个事件标志位(默认选项) osFlagsWaitAll:等待所有事件标志位 osFlagsNoClear:事件标志位置位后,不清除该标志位(默认清除)*/ // //timeout:等待超时时间,0为立刻返回,使用osWaitForever可以一直等
事件标志组代码实现
-
以按键按下与定时时间到两个事件来驱动某个任务进行为例
//创建一个事件标志组 osEventFlagsId_t EventFlagHandle;//定义事件标志组的句柄 EventFlagHandle = osEventFlagsNew(NULL); void KeyTask() { for(;;) { //按键扫描程序,此处略 if(/*按键被按下*/) { osEventFlagsSet(EventFlagHandle,0x0001);//置位bit0 } osDelay(10);//每隔10ms扫描一次 } } void TimerTask() { for(;;) { osDelay(5000);//定时5s osEventFlagsSet(EventFlagHandle,0x0002);//置位bit1 } } void MyTask() { for(;;) { osEventFlagsWait(EventFlagHandle,0x0003,osFlagsWaitAll,osWaitForever); //要执行的任务 osDelay(1000); } }
线程标志
线程标志概述
-
与事件标志组类似,可以用于多个任务的同步,区别在于其不需要手动创建,而是随着任务创建后存在
-
注意,“线程标志”是 CMSIS-RTOS v2 或其他 RTOS中的概念,FreeRTOS中并没有这一概念,对FreeRTOS来说类似的概念是任务通知,用于通知任务,但两者有所区别,比如线程标志可以进行多事件同步,任务通知则不原生支持;任务通知还可以用于任务的通信,在后文中介绍
-
而且一个任务的线程标志只能由这个任务自己等待,由其他任务设置;相比执行事件标志组就相当于一个公用的资源,任何任务都可以等待或设置
线程标志的接口函数
-
线程标志设置函数osThreadFlagsSet
线程标志位总共有31位,从bit0到bit30
uint32_t osThreadFlagsSet(osThreadId_t thread_id, uint32_t flags) //thread_id:任务的句柄 //flags:设置线程标志位 -
线程标志等待函数osThreadFlagsWait
uint32_t osThreadFlagsSet(uint32_t flags, uint32_t options, uint32_t timeout) //因为只能由自己等待,所以无需传入句柄 /*options:标志选项 osFlagsWaitAny:等待任意一个线程标志位(默认选项) osFlagsWaitAll:等待所有线程标志位 osFlagsNoClear:线程标志位置位后,不清除该标志位(默认清除)*/
线程标志代码实现
-
以线程标志实现串口通信为例,在串口接收中断回调函数中置位线程标志,在串口发送任务中等待线程标志,由此实现串口接收到PC传来的字符时将其转发回PC
uint8_t RxBuffer[10]; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance==USART2) { osThreadFlagsSet(ComTaskHandle,0x0001);//接收到消息后置位 HAL_UART_Receive_IT(&huart2,(uint8_t*)RxBuffer,10); } } void ComTask() { for(;;) { osThreadFlagsSet(0x0001,osFlagsWaitAny,osWaitForever);//等待自身标志 HAL_UART_Transmit(&huart2,RxBuffer,10,100);//发送回PC } }
互斥量
互斥量概述
-
共享资源与临界区
会被多个任务访问的资源,称为共享资源
程序中涉及访问共享资源的语句称为临界区
-
互斥量的作用
各个任务在进入各自的临界区时,必须互斥访问,以防一个任务在读写的过程中另一个任务也访问,造成共享资源的可靠性与完整性被破坏
因此,互斥量本质也是二值信号量,一个任务申请后表示这一资源已经被使用,在使用完成后再由这一个任务释放
互斥量接口函数
-
互斥量创建函数osMutexNew
此函数不能在中断服务程序中调用
可以由CubeMX自动生成
osMutexId_t osMutexNew(const osMutexAttr_t *attr) //attr:指向互斥量属性的指针 //返回值为互斥量的句柄,用于标识互斥量 -
互斥量获取函数osMuteAcquire
此函数不能在中断服务程序中调用
由用户调用
osSatus_t osMutexAcquire(osMutexId_t mutex_id, uint32_t timeout) //mutex_id:所要获取互斥量的句柄 //timeout:超时等待时间,以时钟节拍为单位,使用osWaitForever可以一直等 /*返回值为函数的执行状态: osOK:成功获取信号量 osErrorTimeout:在给定的超时时间内无法获取互斥量 osErrorResource:信号量处在无效状态 osErrorParameter:参数错误 osErrorISR:不能从中断服务程序中调用*/ -
互斥量释放函数osMutexRelease
此函数不能在中断服务程序中调用
由用户调用
osSatus_t osMutexRelease(osMutexId_t mutex_id)
互斥量代码实现
-
同样以串口重定向发送为例,但这次有两个任务都要使用串口,对串口进行互斥访问
(其实HAL库中的串口函数已经有了互斥锁功能,此处仅作为使用演示)
osSatus_t UartMutexHandle; UartMutexHandle = osMutexNew(NULL); void com1Task() { for(;;) { osMutexAcquire(UartMutexHandle,osWaitForever);//获取串口资源 printf("com1 task use UART\n"); osMutexRelease(UartMutexHandle);//释放串口资源 osDelay(1000); } } void com2Task() { for(;;) { osMutexAcquire(UartMutexHandle,osWaitForever);//获取串口资源 printf("com2 task use UART\n"); osMutexRelease(UartMutexHandle);//释放串口资源 osDelay(1000); } }
本文来自博客园,作者:无术师,转载请注明原文链接:https://www.cnblogs.com/artlessist/p/19416049
本文使用知识共享4.0协议许可 CC BY-NC-SA 4.0
特别说明版权归属的文章以及不归属于本人的转载内容(如引用的文章与图片)除外

浙公网安备 33010602011771号