使用消息队列解决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;
}

配置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. 在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. 线程方法实现
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. 查看串口输出结果
    串口输出结果是不是跟我们想象中的结果不一样,我们想的是它会按照顺序输出
Normal01 Task Start ... 
Normal02 Task Start ... 
Normal03 Task Start ... 

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

解决方法

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

代码实现

  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. 在main中激活线程 和 初始化消息队列
PrintTaskHandle = osThreadNew(StartPrintTask, NULL, &PrintTask_attributes);
PrintQueueHandle = osMessageQueueNew (16, sizeof(getPrintBufFromQueue), &PrintQueue_attributes);
  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. 打印方法实现,注: 此处tempBuffer大小和消息队列的Item大小一致
#define fun_print(format, ...){ \
		char tempBuffer[32]; \
		memset(tempBuffer, 0, 32); \
		sprintf(tempBuffer, format, ##__VA_ARGS__); \
		osMessageQueuePut(PrintQueueHandle, tempBuffer, 0, 0); \
	}
  1. 此时在查看我们的串口输出结果,我们可以看到不会出现第一张图中的乱序
    串口输出结果

使用互斥量解决

#define mt_log_mutex myMutex01Handle
#define mt_log(format, ...) \
  osMutexAcquire(mt_log_mutex, osWaitForever); \
  printf(format, ##__VA_ARGS__); \
  osMutexRelease(mt_log_mutex);

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

  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Keil RTX RTOS 提供了消息队列的支持。下面是使用消息队列的基本步骤: 1. 定义消息队列 消息队列可以是全局变量或局部变量。定义消息队列时需要指定消息队列的大小和消息的数据类型,例如: ``` #define MSGQUEUE_SIZE 16 osMessageQueueId_t msgQueue; uint32_t msgQueueBuffer[MSGQUEUE_SIZE]; const osMessageQueueAttr_t msgQueueAttr = { .name = "MsgQueue" }; msgQueue = osMessageQueueNew(MSGQUEUE_SIZE, sizeof(uint32_t), &msgQueueAttr); ``` 上面的代码定义了一个大小为 16 的消息队列,用于存储 uint32_t 类型的消息消息队列的属性可以通过 osMessageQueueAttr_t 结构体来设置。 2. 发送消息消息队列 发送消息时需要将消息数据写入到消息队列中,例如: ``` uint32_t msgData = 123; osStatus_t status = osMessageQueuePut(msgQueue, &msgData, 0, osWaitForever); if (status != osOK) { // 发送消息失败 } ``` 上面的代码将值为 123 的消息发送消息队列中。osMessageQueuePut() 函数的第一个参数是消息队列 ID,第二个参数是指向消息数据的指针,第三个参数是消息的优先级,第四个参数是超时时间。 3. 接收消息 接收消息时需要从消息队列中读取消息数据,例如: ``` uint32_t msgData; osStatus_t status = osMessageQueueGet(msgQueue, &msgData, NULL, osWaitForever); if (status != osOK) { // 接收消息失败 } ``` 上面的代码从消息队列中读取一个消息,并将消息数据存储到 msgData 变量中。osMessageQueueGet() 函数的第一个参数是消息队列 ID,第二个参数是指向消息数据的指针,第三个参数是指向消息优先级的指针,第四个参数是超时时间。 以上就是使用 Keil RTX RTOS 消息队列的基本步骤。当然,在实际应用中,还需要根据具体的需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值