freertos任务之间的通讯方式有很多,消息队列就是一种,它可以在任务中发送或者读取信息。
有了解过数据结构的应该能理解消息队列的含义,消息队列本质上来说就是队列。队列就好比你去排队打饭,排队的队伍就是一个队列,队列的原理就是先进先出。你先排队那就你先打完饭,你后排队就等别人打完饭才到你。消息队列也一样,先发送的数据可以先被读取到,后发送的数据后面才会被读取。
#include "queue.h"
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,UBaseType_t uxItemSize,);
这是消息队列的头文件和创建函数xQueueCreate();我这里采用的是动态的方法进行创建,创建函数的第一个参数是队列储存元素的个数,第二个参数是存储元素的字节个数。
QueueHandle_t queue;
queue = xQueueCreate(4,4);
首先创建消息队列的句柄,这个是至关重要的,我们发送消息和接收消息都要操作这个句柄。队列创建函数的两个参数我都给了4,代表我要设置队列的大小为4,存储元素的字节也为4。
void vQueueDelete(QueueHandle_t xQueue);
这是删除消息队列的函数,只要把消息队列的句柄传进去就好了。
BaseType_t xQueueSend(
QueueHandle_t Queue, //队列句柄
const void *pvItemToQueue, //发送数据的指针
TickType_t xTicksToWait); //发送数据的时间
这个是常规的队列发送函数,不能在中断中使用,因为发送函数的第三个参数可以设置发送时间。可以起到一个阻塞的效果,但是在中断中是不可以进行阻塞的,中断有自己的队列发送函数。
BaseType_t xQueueSendFromISR(
QueueHandle_t Queue, //队列句柄
const void *pvItemToQueue, //发送数据的指针
BaseType_t *pxHigherPriorityTaskWoken); //可以给pdTRUE或者pdFLASE
BaseType_t xQueueReceive(
QueueHandle_t Queue, //队列句柄
void *pvBuffer, //接收数据的指针
TickType_t xTicksToWait); //发送数据的时间
这是常规的队列接收函数,同样也不能在中断中使用。
BaseType_t xQueueReceiveFromISR(
QueueHandle_t Queue, //队列句柄
void *pvBuffer, //发送数据的指针
BaseType_t *pxHigherPriorityTaskWoken); //可以给pdTRUE或者pdFLASE
这是中断里使用的接收信息函数。
接下来做一个消息队列的实验,在stm32c8t6上面设置两个按键,key1和key2。按下key1时发送消息,按下key2时接收消息并打印。
QueueHandle_t queue; //消息队列句柄
TaskHandle_t startTask_handler; //总任务的句柄
TaskHandle_t ledTask_handler; //led的句柄
TaskHandle_t sendTask_handler; //发送消息的句柄
TaskHandle_t receiveTask_handler; //接收消息的句柄
TaskHandle_t randTask_handler; //随机数的句柄
xTaskCreate(startTask,"startTask",512,NULL,1,&startTask_handler); //创建开始任务
void startTask(void *arg)
{
BaseType_t xReturn = pdFALSE; //创建接收值
taskENTER_CRITICAL(); //临界区
queue = xQueueCreate(4,4);
xReturn = xTaskCreate(ledTask,"ledTask",64,NULL,1,&ledTask_handler); //创建led任务
xReturn = xTaskCreate(sendTask,"sendTask",64,NULL,3,&sendTask_handler); //创建发送任务
xReturn = xTaskCreate(receiveTask,"receiveTask",64,NULL,2,&receiveTask_handler); //创建接收任务
xReturn = xTaskCreate(randTask,"randTask",64,NULL,1,&randTask_handler); //创建随机数任务
if(xReturn == pdTRUE)
{
printf("Task create ok\n");
}
else
{
printf("Task create error\n");
}
vTaskDelete(startTask_handler); //删除开始任务句柄
taskEXIT_CRITICAL(); //临界区
}
这是任务创建的函数,一共创建5个任务,一个开始任务startTsak(void *arg),用来创建其他4个任务的。
其他4个任务分别是led任务,随机数任务,发送消息任务和接受消息任务。led任务是用来当心跳包的,可以观察led的状态判断自己的程序有没有卡死之类的。随机数任务是用来赋值给发送函数发送的值的。发送任务就是发送消息的,接受任务就是用来接受消息的。
void ledTask(void *arg)
{
while(1)
{
GPIO_SetBits(GPIOB,GPIO_Pin_8);
vTaskDelay(500);
GPIO_ResetBits(GPIOB,GPIO_Pin_8);
vTaskDelay(500);
}
}
led任务比较简单,就是单纯让led在闪烁。
int send_data1;
void randTask(void *arg)
{
while(1)
{
send_data1 = rand() % 100 + 1;
}
}
首先定义一个全局变量send_data1,通过rand()函数将值不断赋值给send_data1,这样就可以确保每次发送的数据都是不一样的。
void sendTask(void *arg)
{
BaseType_t xReturn = pdFALSE;
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) == RESET)
{
xReturn = xQueueSend(queue,&send_data1,0);
if(xReturn == pdFALSE)
{
printf("data send error\n");
}
else
{
printf("data send ok\n");
}
}
vTaskDelay(200);
}
}
首先定义一个xReturn 变量来接收xQueueSend()函数的值,来判断它是否操作成功。因为我自己没有封装按键key的函数,所以直接索性判断PA0是否被按下了。xQueueSend()函数的第一个参数就是消息队列的句柄;第二个参数是生成的随机数,这里要取地址;最后一个参数是设置发送的时间,我给的是0,就是一调用就直接发送。
最后一个小问题,当执行完流程后最好延时一下,不延时的话可能会导致按下一次按键,就会发送好几次数据,我这里延时了200ms。
void receiveTask(void *arg)
{
int rec_data;
BaseType_t xReturn = pdFALSE;
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1) == RESET)
{
xReturn = xQueueReceive(queue,&rec_data,portMAX_DELAY);
if(xReturn == pdFALSE)
{
printf("receive ok\n");
}
else
{
printf("receive data :%d\n",rec_data);
}
}
vTaskDelay(200);
}
}
先定义一个变量rec_data来接收数据,当PA1被按下时,也就是按键被按下时,就开始接收。xQueueReceive()函数的第一个参数是队列句柄;第二个参数是用来接收数据,要取地址;最后一个参数是接收的时间,我这里给的是portMAX_DELAY,这个是freertos的一个宏定义,表示一直等待接收。流程的最后也是一样给延个时,防止它一次性读取很多数据。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "queue.h"
#include "led.h"
#include "stdlib.h"
QueueHandle_t queue; //消息队列句柄
TaskHandle_t startTask_handler; //总任务的句柄
TaskHandle_t ledTask_handler; //led的句柄
TaskHandle_t sendTask_handler; //发送消息的句柄
TaskHandle_t receiveTask_handler; //接收消息的句柄
TaskHandle_t randTask_handler; //随机数的句柄
int send_data1;
void ledTask(void *arg)
{
while(1)
{
GPIO_SetBits(GPIOB,GPIO_Pin_8);
vTaskDelay(500);
GPIO_ResetBits(GPIOB,GPIO_Pin_8);
vTaskDelay(500);
}
}
void receiveTask(void *arg)
{
int rec_data;
BaseType_t xReturn = pdFALSE;
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1) == RESET)
{
xReturn = xQueueReceive(queue,&rec_data,portMAX_DELAY);
if(xReturn == pdFALSE)
{
printf("receive ok\n");
}
else
{
printf("receive data :%d\n",rec_data);
}
}
vTaskDelay(200);
}
}
void sendTask(void *arg)
{
BaseType_t xReturn = pdFALSE;
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) == RESET)
{
xReturn = xQueueSend(queue,&send_data1,0);
if(xReturn == pdFALSE)
{
printf("data send error\n");
}
else
{
printf("data send ok\n");
}
}
vTaskDelay(200);
}
}
void randTask(void *arg)
{
while(1)
{
send_data1 = rand() % 100 + 1;
}
}
void startTask(void *arg)
{
BaseType_t xReturn = pdFALSE; //创建接收值
taskENTER_CRITICAL(); //临界区
queue = xQueueCreate(4,4);
xReturn = xTaskCreate(ledTask,"ledTask",64,NULL,1,&ledTask_handler); //创建led任务
xReturn = xTaskCreate(sendTask,"sendTask",64,NULL,3,&sendTask_handler); //创建发送任务
xReturn = xTaskCreate(receiveTask,"receiveTask",64,NULL,2,&receiveTask_handler); //创建接收任务
xReturn = xTaskCreate(randTask,"randTask",64,NULL,1,&randTask_handler); //创建随机数任务
if(xReturn == pdTRUE)
{
printf("Task create ok\n");
}
else
{
printf("Task create error\n");
}
vTaskDelete(startTask_handler); //删除开始任务句柄
taskEXIT_CRITICAL(); //临界区
}
int main(void)
{
LED_Init();
usrt1_init(9600);
xTaskCreate(startTask,"startTask",512,NULL,1,&startTask_handler); //创建开始任务
vTaskStartScheduler();
}
我这里用串口来进行操作,先是进行复位,然后串口助手第一行打印了Task create ok这句话,说明所有任务都创建成功了。
我按了一下key1,串口打印data send ok,说明发送成功;再按一下key2,串口打印了recrive data :12,说明消息队列也接收到了消息。
随后我发送了3次数据,此时队列里有3个元素,随后我又读取了两次数据,这时队列了就剩一个数据了。所以,这就导致了我接下来再发送5次数据时,后面两次都失败了。原因是在发送第4次数据时,队列已经满了,就发送进不去了,因为我设置的队列最大值为4,所以就会打印error。
顺便再提一嘴,如果各位在操作时,发现发送数据和接收数据同时进行,或者在进行发送和接受数据操作时,突然打印不出数据了。那么问题肯定是出在发送和接收数据的延时上或者任务的优先级上,我也遇到过这些问题。
以上就是队列的操作。