• 使用消息队列解决RTOS多线程同时打印串口乱序问题


    背景

    我们假定现在创建了3个工作线程,这3个线程都需要通过串口输出日志或内容。很简单的认为,我们可以做到各个任务连续输出自己线程的内容,实际上不是的,这涉及到了多线程原理,有时间跟大家说说,或者大家可以去了解了解。
    在这篇文章只涉及题目提出的问题解决方法。

    准备工作

    1. STM32 Cube系列软件开发工具
    2. 一块可以使用的STM32单片机
    3. 一个ST LINK 下载器 和 一个 USB 转 TTL
    4. 一个可以正常工作开发程序的电脑

    开发环境

    请自行搭建最基础的开发环境,能做到如下几点就可以了:

    1. 可以编写STM32程序并下载到单片机
    2. 重定向 printf 到串口
    3. 可以在串口看到正确的 你想要的代码输出结果
    4. FreeRTOS 使用 CMSIS V2 接口

    问题复现

    我们先复现一下 标题 中描述的问题:

    使用cubemx配置我们初始化代码

    配置USART1作为我们接下来例子中的使用串口

    // printf 重定向代码 STM32H743
    int __io_putchar(int ch){
    	while(! (USART1->ISR & ((0x1UL << (6U)))));
    	USART1->TDR = ch;
    	return ch;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    配置FreeRTOS,我们先添加3个线程,cubemx的图我就不放了(如果有需要,请在评论栏留言,我会找时间放图的),这里只放生成的代码。

    1. 线程ID 和 参数定义
    /* Definitions for NormalTask01 */
    osThreadId_t NormalTask01Handle;
    const osThreadAttr_t NormalTask01_attributes = {
      .name = "NormalTask01",
      .stack_size = 128 * 4,
      .priority = (osPriority_t) osPriorityNormal,
    };
    /* Definitions for NormalTask03 */
    osThreadId_t NormalTask03Handle;
    const osThreadAttr_t NormalTask03_attributes = {
      .name = "NormalTask03",
      .stack_size = 128 * 4,
      .priority = (osPriority_t) osPriorityNormal,
    };
    /* Definitions for NormalTask02 */
    osThreadId_t NormalTask02Handle;
    const osThreadAttr_t NormalTask02_attributes = {
      .name = "NormalTask02",
      .stack_size = 128 * 4,
      .priority = (osPriority_t) osPriorityNormal,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    1. 在main中激活线程
    /* creation of NormalTask01 */
    NormalTask01Handle = osThreadNew(StartNormalTask01, NULL, &NormalTask01_attributes);
    /* creation of NormalTask03 */
    NormalTask03Handle = osThreadNew(StartNormalTask03, NULL, &NormalTask03_attributes);
    /* creation of NormalTask02 */
    NormalTask02Handle = osThreadNew(StartNormalTask02, NULL, &NormalTask02_attributes);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 线程方法实现
    void StartNormalTask01(void *argument)
    {
      printf("Normal01 Task Start ... \r\n");
      for(;;)
      {
        osDelay(100)}
    }
    void StartNormalTask02(void *argument)
    {
      printf("Normal02 Task Start ... \r\n");
      for(;;)
      {
        osDelay(1);
      }
    }
    void StartNormalTask03(void *argument)
    {
      printf("Normal03 Task Start ... \r\n");
      for(;;)
      {
        osDelay(1);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    1. 查看串口输出结果
      串口输出结果是不是跟我们想象中的结果不一样,我们想的是它会按照顺序输出
    Normal01 Task Start ... 
    Normal02 Task Start ... 
    Normal03 Task Start ... 
    
    • 1
    • 2
    • 3

    实际上却是上面图片的结果,造成这样的原因就是多线程同时访问一个共享资源而造成资源的抢夺现象。

    解决方法

    新增一个串口守护线程,人为禁止其他线程访问串口资源,其他线程需要串口发送的内容则通过消息队列传递到串口守护线程统一进行输出。而消息队列先进先出的特性保证了我们串口输出不会乱序。

    代码实现

    1. 新增一个串口输出线程一个消息队列,消息队列作为其他线程传输内容到串口打印线程的媒介
    /* Definitions for PrintTask */
    osThreadId_t PrintTaskHandle;
    const osThreadAttr_t PrintTask_attributes = {
      .name = "PrintTask",
      .stack_size = 128 * 4,
      .priority = (osPriority_t) osPriorityNormal,
    };
    /* Definitions for PrintQueue */
    osMessageQueueId_t PrintQueueHandle;
    const osMessageQueueAttr_t PrintQueue_attributes = {
      .name = "PrintQueue"
    };
    static char getPrintBufFromQueue[32];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    1. 在main中激活线程 和 初始化消息队列
    PrintTaskHandle = osThreadNew(StartPrintTask, NULL, &PrintTask_attributes);
    PrintQueueHandle = osMessageQueueNew (16, sizeof(getPrintBufFromQueue), &PrintQueue_attributes);
    
    • 1
    • 2
    1. 打印线程方法实现
    void StartPrintTask(void *argument)
    {
      /* USER CODE BEGIN 5 */
      /* Infinite loop */
      osStatus_t queueReturn = osOK;
      for(;;)
      {
    	// 等待消息队列中的消息,timeout指定为osWaitForever
    	queueReturn = osMessageQueueGet(PrintQueueHandle, getPrintBufFromQueue, NULL, osWaitForever);
    	while(queueReturn == osOK){
    		// 打印从消息队列中获取的字符串缓冲
    		printf("%s", getPrintBufFromQueue);
    		// 这里timeout指定为0,因为程序不需要阻塞在这里
    		queueReturn = osMessageQueueGet(PrintQueueHandle, getPrintBufFromQueue, NULL, 0);
    	}
      }
      /* USER CODE END 5 */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    1. 打印方法实现,注: 此处tempBuffer大小和消息队列的Item大小一致
    #define fun_print(format, ...){ \
    		char tempBuffer[32]; \
    		memset(tempBuffer, 0, 32); \
    		sprintf(tempBuffer, format, ##__VA_ARGS__); \
    		osMessageQueuePut(PrintQueueHandle, tempBuffer, 0, 0); \
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 此时在查看我们的串口输出结果,我们可以看到不会出现第一张图中的乱序
      串口输出结果

    使用互斥量解决

    #define mt_log_mutex myMutex01Handle
    #define mt_log(format, ...) \
      osMutexAcquire(mt_log_mutex, osWaitForever); \
      printf(format, ##__VA_ARGS__); \
      osMutexRelease(mt_log_mutex);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (上述代码仅供交流使用,请勿直接用在生产环境,如有错误,请在评论栏提出讨论)

  • 相关阅读:
    工程伦理--8.4 组织不服从
    模板学堂丨禅道业务数据分析大屏
    2374. 边积分最高的节点
    泰山OFFICE技术讲座:字体的位置研究2-上升
    python中matrix()矩阵和array()数组(待完善)
    《QT从基础到进阶·二十七》进度条QProgressBar
    Map<K,V>的使用和List学习
    grid布局
    babyheap_0ctf_2017 详细解析【BUUCTF】
    Python学习笔记--面向对象的概念
  • 原文地址:https://blog.csdn.net/User287/article/details/126671352