嵌入式实时操作系统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-bit23

    uint32_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);
    	}
    }
    
posted @ 2025-12-29 15:11  无术师  阅读(132)  评论(0)    收藏  举报