嵌入式实时操作系统RTOS_FreeRTOS的任务通信

  • 在使用信号量、事件标志组和线程标志进行任务同步时,只能提供同步的时刻消息,而当任务之间需要数据传输时,就需要进行任务通信
  • 使用全局变量、消息队列、直接任务通知和邮箱来实现

全局变量

  • 在前后台系统中,我们使用全局变量在各个函数之间传递数据,在RTOS中,这依旧可行

全局变量互斥访问

  • 在RTOS中,全局变量作为共享资源,需要进行互斥访问以保护共享资源

  • 互斥量实现互斥访问

    借助互斥量实现互斥访问全局变量

    // 需要保护的全局变量
    volatile int32_t shared_counter = 0;
    // 互斥量
    extern osMutexId_t myMutexHandle;
    
    void StartTask01(void *argument)
    {
    	uint32_t count;
    	for(;;)
    	{
    	    // 等待获取互斥量
    	    if (osMutexAcquire(myMutexHandle, osWaitForever) == osOK)
    		{
    			// 临界区开始,此处可以对shared_counter进行读写操作
    			osMutexRelease(myMutexHandle);
    			// 临界区结束
    		}
    		osDelay(1000);
    	}
    }
    
  • 任务特殊设计实现互斥访问

    另一种实现互斥访问的更简单方法是:在任务设计之初就设计成只有一个任务会修改这个全局变量,其他任务只读不改,并且在这个全局变量前加上volatile关键字以防编译器优化

消息队列

消息队列概述

  • 消息队列简介

    消息队列本质是一个数据缓冲区,可以保存有限个确定大小的数据,就像我们之前用串口通信时用一个数组来保存要发送或接收的字符串一样

    但有一个重要的区别:(对FreeRTOS)放入消息队列的是实际的数据而非数据的地址,所以要根据接收数据的大小来设置消息队列的单个消息的大小

  • 消息队列的适用范围

    消息队列数据缓冲区的性质使其非常适合用于结构化的数据传递,如结构体数据

    消息队列与全局变量相比,解决了多任务访问共享资源的冲突问题,还提供了任务的同步和超时等机制,并且可以实现中断服务程序和任务之间的数据传递

    但是,由于采用复制的方式传输数据,当数据量较大时效率较低,这时更适合使用全局变量

  • 消息队列的工作模式

    消息队列按FIFO先进先出的模式使用,新的数据从队列尾部写入,在读取数据时从队首开始;

    • 在放入消息时,需要判断是否有多余空间:如果有则放入,如果没有则任务进入阻塞态,直到消息队列中有多余的空间
    • 在获取消息时,需要判断队列是否有消息:如果有则获取,需要提前准备好存放消息的缓冲区(不能小于单个消息的大小),如果没有则任务进入阻塞态,直到队列中有新的消息

    以串口访问为例,之前我们提到了使用互斥量对串口互斥访问的方法,利用消息队列的实现是:任务1发送的数据放入消息队列,任务2发送的数据也放入消息队列,单开一个串口发送任务从消息队列中取出消息发送

消息队列接口函数

  • 消息队列创建函数osMessageQueueNew

    此函数不能在中断服务程序中调用

    可以由CubeMX自动生成

    osMessageQueueId_t osMessageQueueNew(uint32_t msg_count,uint32_t msg_size, const osMessageQueueAttr_t *attr)
    //msg_count:消息队列中可以容纳的最大消息数量
    //msg_size:每个消息的大小(单位为字节)
    //attr:指向消息队列属性的指针
    //返回消息队列的句柄,用于标识消息队列
    
  • 消息放入函数osMessageQueuePut

    用于向指定的消息队列中放一个消息

    osStatus_t osMessageQueuePut(osMessageQueueId_t mq_id, const void *msg_ptr, uint8_t msg_prio, uint32_t timeout)
    //mq_id:消息队列的句柄
    //msg_ptr:指向存放消息的缓冲区指针(把该指针所指消息放入消息队列)
    //msg_prio:消息的优先级
    //timeout:超时时间,为0时可以在中断中调用,但此时不应该调入太大的消息以免耗时太长
    
  • 消息获取函数osMessageQueueGet

    用于从指定的消息队列中获取一个消息

    osStatus_t osMessageQueueGet(osMessageQueueId_t mq_id, void *msg_ptr, uint8_t msg_prio, uint32_t timeout)
    //mq_id:消息队列的句柄
    //msg_ptr:指向存放消息的缓冲区指针(把消息队列获取的消息存放到该缓冲区)
    //msg_prio:消息的优先级
    //timeout:超时时间
    

消息队列代码实现

  • 以接收串口接收数据为例,串口接收10个字节的数据,放入消息队列,数据处理任务从消息队列中取出这十个数据并发送到PC显示

    uint8_t RxBuf[10];
    uint8_t TxBuf[10];
    osMessageQueueId_t ComQueueHandle;
    ComQueueHandle = osMessageQueueNew(5,10,NULL);
    //创建一个可以容纳5个消息,每个消息长10字节的消息队列
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    {
    	if(huart->Instance==USART2)
    	{
    		//在中断中调用消息放入函数,超时等待时间要设置为0
    		osMessageQueuePut(ComQueueHandle,(void *)RxBuf,0,0);
    		//把串口接收到的数据放入消息队列
    		HAL_UART_Receive_IT(&huart,RxBuf,10);//重新使能接收中断
    	}
    }
    void ProcessTalk()
    {
    	for(;;)
    	{
    		if(osMessageQueueGet(ComQueueHandle,(void *)TxBuf,NULL,osWaitForever)==osOK)
    		{
    			//无限等待消息队列中的数据,存入TxBuf中
    			HAL_UART_Transimt(&huart2,TxBuf,10,HAL_MAX_DELAY);//原样发回
    		}
    	}
    }
    

邮箱

  • 邮箱概述

    邮箱是RTOS中一种用于任务间通信的机制,主要用于在任务之间传递单个消息或指针而非大量数据,它是一种轻量级、高效的通信方式

  • 邮箱代码实现

    FreeRTOS没有邮箱,这里就不介绍了,我们可以用任务通知来实现类似的轻量化通信

任务通知

任务通知概述

  • 也就是前文在介绍线程标志时提到过的,FreeRTOS特有的,可以部分替代线程标志的原生概念

  • 任务通知(也被叫做直达直接任务通知)可以用作轻量化的,替代邮箱的任务通信方式

    一如线程标志的介绍,每个任务创建后自带了一个任务控制块,其中包含了长达32位的通知值

    • 用于在任务同步中,我们设置/清除特定位,那就相当于一个自带的事件标志组,发挥线程标志的作用;
    • 用在任务通信中,我们利用这个特性来发送一个32位的数值,如状态或指针,就相当于一个自带的邮箱,发挥一个长度为1的消息队列的作用
  • 任务通知实现任务通信

    任务通知是FreeRTOS的特有概念,因此没有CMSIS-RTOS v2的统一封装,需要使用原生接口函数

    // 发送端
    xTaskNotify(task_handle, (uint32_t)result_ptr, eSetValueWithOverwrite);
    
    // 接收端
    uint32_t val;
    xTaskNotifyWait(0, 0, &val, portMAX_DELAY);
    MyStruct *p = (MyStruct *)val;
    
posted @ 2025-12-29 20:32  无术师  阅读(131)  评论(0)    收藏  举报