消息队列应用实例
本文主要介绍如何在任务或中断中向队列发送消息或者从队列中接收消息。
使用STM32CubeMX将FreeRTOS移植到工程中,创建两个任务以及两个消息队列,并开启两个中断
两个任务
- Keyscan_Task:读取按键的键值,并将键值发送到队列Key_Queue中
-
Keyprocess_Task:按键处理任务,读取队列Key_Queue中的消息,并根据不同的消息值做相应的处理
两个队列
- Msg_Queue:用于传递串口发送过来的消息
-
Key_Queue:用于传递按键值
两个中断
- 串口接收中断:接收串口发来的数据,并将接收到的数据发送到队列Msg_Queue中
-
定时器中断:定时读取队列Msg_Queue中的消息,并控制LED3/LED4的亮灭
1. STM32CubeMX设置
- RCC设置外接HSE,时钟设置为72M
- PC0/PC1/PC2/PC3设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平
- PA0设置为GPIO输入模式、下拉模式;PE2/PE3/PE4设置为GPIO输入模式、上拉模式
- USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位;开启串口中断
- 激活TIM3定时器,时钟源选择为内部时钟,PSC预分频设置为7200-1,向上计数,自动重装载值(ARR)设置为5000-1,激活TIM3定时器中断;根据公式可算出:计数器时钟CK_CNT = 72M/7200 = 10000Hz,计时器中断时间为 ARR/10000 = 500ms。可参考定时器中断介绍
- 激活FreeRTOS,添加任务,设置任务名称、优先级、堆栈大小、函数名称等参数
- 添加队列Msg_Queue和Key_Queue,如下图设置
- 使用FreeRTOS操作系统,一定要将HAL库的Timebase Source从SysTick改为其他定时器,选好定时器后,系统会自动配置TIM
- 输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h' files per IP ;点击GENERATE CODE,生成工程代码
2. MDK-ARM软件编程
- 创建按键驱动文件key.c和key.h,参考 按键输入 例程
- 添加KeyscanTask、KeyprocessTask任务函数代码
void KeyscanTask(void const * argument){
uint8_t key;
BaseType_t err;
for(;;){
key = KEY_Scan(0);
if((Key_QueueHandle!=0)&&(key)){
err = xQueueSend(Key_QueueHandle,&key,10);
if(err == errQUEUE_FULL)
printf("Key_Queue is Full, data send failed!\r\n");
else
printf("Send data to Key_Queue successed!\r\n");
}
osDelay(10);
}
}
void KeyprocessTask(void const * argument){
uint8_t key_value;
for(;;){
if(Key_QueueHandle != 0){
if(xQueueReceive(Key_QueueHandle,&key_value,portMAX_DELAY)){
switch(key_value)
{
case KEY_UP_PRES:
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);
printf("LED0 Togglen!\r\n");
break;
case KEY_DOWN_PRES:
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_1);
printf("LED1 Togglen!\r\n");
break;
}
}
}
osDelay(10);
}
}
|
- 添加串口中断回调函数:将串口输入的字符进行入队操作
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
RxBuff[Rx_Count++]=RxByte;
if(RxByte==0x0A){
xQueueSendFromISR(Msg_QueueHandle,RxBuff,NULL);
printf("Send CMD to Msg_Queue FromISR succesed!\r\n");
Rx_Count=0;
}
if(Rx_Count > 8){
printf("Wrong CMD, Please Check...!\r\n");
memset(RxBuff,0,sizeof(RxBuff));
Rx_Count=0;
}
while(HAL_UART_Receive_IT(&huart1,&RxByte,1)==HAL_OK);
}
|
- 添加定时器中断回调函数:每500ms检查一下Msg_Queue是否有数据,如果有数据从Msg_Queue中出队,并对字符串进行比较从而控制LED3/LED4的亮灭
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
if (htim->Instance == TIM1) {
HAL_IncTick();
}
if(htim == &htim3){
uint8_t RxCMD[8];
if(Msg_QueueHandle != 0){
if(xQueueReceiveFromISR(Msg_QueueHandle,RxCMD,NULL)){
printf("Read CMD Msg_Queue from ISR is:");
for(int i =0;i<8;i++)
printf("%c",RxCMD[i]);
printf("\n");
}
if(strncmp((char *)RxCMD,"LED3on",6) == 0)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_2,GPIO_PIN_RESET);
else if(strncmp((char *)RxCMD,"LED3off",6) == 0)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_2,GPIO_PIN_SET);
else if(strncmp((char *)RxCMD,"LED4on",6) == 0)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_3,GPIO_PIN_RESET);
else if(strncmp((char *)RxCMD,"LED4off",6) == 0)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_3,GPIO_PIN_SET);
}
}
}
|
- 在main.c中添加开启串口接收中断和定时器中断代码
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
if (htim->Instance == TIM1) {
HAL_IncTick();
}
if(htim == &htim3){
uint8_t RxCMD[8];
if(Msg_QueueHandle != 0){
if(xQueueReceiveFromISR(Msg_QueueHandle,RxCMD,NULL)){
printf("Read CMD Msg_Queue from ISR is:");
for(int i =0;i<8;i++)
printf("%c",RxCMD[i]);
printf("\n");
}
if(strncmp((char *)RxCMD,"LED3on",6) == 0)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_2,GPIO_PIN_RESET);
else if(strncmp((char *)RxCMD,"LED3off",6) == 0)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_2,GPIO_PIN_SET);
else if(strncmp((char *)RxCMD,"LED4on",6) == 0)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_3,GPIO_PIN_RESET);
else if(strncmp((char *)RxCMD,"LED4off",6) == 0)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_3,GPIO_PIN_SET);
}
}
}
|
3. 下载验证
编译无误下载到开发板后,打开串口调试助手,串口输出串口中断和定时器中断开启成功信息;按下K_UP和K_DOWN按键,可以依次对LED1/LED2的亮灭状态进行翻转;串口中输入字符串“LED3on、LED3off、LED4on、LED4off”可以控制LED3/LED4的亮灭并打印出相应的调试信息
|