FreeRTOS消息队列 & ESP32实战
FreeRTOS消息队列
FreeRTOS的消息队列和操作系统课中讲的消息队列大差不差,都是为了有序的、安全的在多任务间在发送信息。下面是其一些特性。
-
多任务访问
队列不是属于某个特别指定的任务的,任何任务都可以向队列中发送消息,或者从队列中提取消息。 -
原始值传递
队列中的消息内容不是引用,即不是把内容的地址传递,而是将数据的内容直接拷贝到消息队列中,这样做的好处有两个,第一是在传递完后,消息缓冲区可立即进行更改不需要等到消息完成传递后再更改,第二是不会因为局部变量变化而导致消息混乱,将某个函数的局部变量作为消息传递的时候,当函数运行完成并返回,这个局部变量就会被销毁,这样就导致传递到队列里的地址就是一个垃圾地址,内容并没有什么用处。当然也可以直接将地址作为内容传递过去,这样就实现了引用传递。 -
出队阻塞
当任务想从队列中读取消息时可以将自己阻塞起来等待数据到达,当然可以不等待获取,也可以等待一定时间获取,更可以永久等待。 -
入队阻塞
当任务想向队列中传递消息时发现队列已经满了,就可以选择将自己阻塞起来等待队列有空位置再将消息传递进去。当然也有三种阻塞模式,不阻塞、阻塞一段时间、永久阻塞。
FreeRTOS消息队列结构体源码分析
/*
* Definition of the queue used by the scheduler.
* Items are queued by copy, not reference. See the following link for the
* rationale: https://www.freertos.org/Embedded-RTOS-Queues.html
*/
typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
int8_t *pcHead; /*< 指向队列头部的指针 */
int8_t *pcWriteTo; /*< 指向队列下个空闲位置的指针 */
union
{
QueuePointers_t xQueue; /*< 这个应该是指的当作为队列使用时的句柄,指向创建的队列 */
SemaphoreData_t xSemaphore; /*< 这个应该是指的当作为信号量使用时的句柄,指向创建的信号量 */
} u;
List_t xTasksWaitingToSend; /*等待发送任务列表,入队阻塞列表*/
/*< List of tasks that are blocked waiting to post onto this queue. Stored in priority order. */
List_t xTasksWaitingToReceive; /*等待接收任务列表,出队阻塞列表*/
/*< List of tasks that are blocked waiting to read from this queue. Stored in priority order. */
volatile UBaseType_t uxMessagesWaiting;/*< The number of items currently in the queue. */
UBaseType_t uxLength; /*< The length of the queue defined as the number of items it will hold, not the number of bytes.队列内容长度,按消息数量计算不是按字节计算 */
UBaseType_t uxItemSize; /*< The size of each items that the queue will hold. 消息内容最大长度*/
volatile int8_t cRxLock; /*< Stores the number of items received from the queue (removed from the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */
volatile int8_t cTxLock; /*< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */
#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */
#endif
#if ( configUSE_QUEUE_SETS == 1 )
struct QueueDefinition *pxQueueSetContainer;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
portMUX_TYPE mux; //Mutex required due to SMP
} xQUEUE;
/* The old xQUEUE name is maintained above then typedefed to the new Queue_t
name below to enable the use of older kernel aware debuggers. */
typedef xQUEUE Queue_t;
FreeRTOS API分析
- xQueueCreate();
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
{
Queue_t *pxNewQueue;
size_t xQueueSizeInBytes;
uint8_t *pucQueueStorage;
configASSERT( uxQueueLength > ( UBaseType_t ) 0 );
if( uxItemSize == ( UBaseType_t ) 0 )
{
/* There is not going to be a queue storage area. */
xQueueSizeInBytes = ( size_t ) 0;
}
else
{
/* Allocate enough space to hold the maximum number of items that
can be in the queue at any time. */
xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );
/*lint !e961 MISRA exception as the casts are only redundant for some ports. *///队列长度*消息大小=最大
}
/* Check for multiplication overflow. */
configASSERT( ( uxItemSize == 0 ) || ( uxQueueLength == ( xQueueSizeInBytes / uxItemSize ) ) );
/* Check for addition overflow. */
configASSERT( ( sizeof( Queue_t ) + xQueueSizeInBytes ) > xQueueSizeInBytes );
/* Allocate the queue and storage area. Justification for MISRA
deviation as follows: pvPortMalloc() always ensures returned memory
blocks are aligned per the requirements of the MCU stack. In this case
pvPortMalloc() must return a pointer that is guaranteed to meet the
alignment requirements of the Queue_t structure - which in this case
is an int8_t *. Therefore, whenever the stack alignment requirements
are greater than or equal to the pointer to char requirements the cast
is safe. In other cases alignment requirements are not strict (one or
two bytes). *//*这里意思应该是malloc申请的区域应该符合MISRA的标准对齐。*/
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );//队列结构体大小+缓冲区大小
if( pxNewQueue != NULL )
{
/* Jump past the queue structure to find the location of the queue
storage area. */
pucQueueStorage = ( uint8_t * ) pxNewQueue;
pucQueueStorage += sizeof( Queue_t ); /*lint !e9016 Pointer arithmetic allowed on char types, especially when it assists conveying intent. *///指针偏移直接跳过队列结构体大小到缓冲区。
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
/* Queues can be created either statically or dynamically, so
note this task was created dynamically in case it is later
deleted. */
pxNewQueue->ucStaticallyAllocated = pdFALSE;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
}
else
{
traceQUEUE_CREATE_FAILED( ucQueueType );
mtCOVERAGE_TEST_MARKER();
}
return pxNewQueue;//返回队列指针。
}
2. xQueueSend() & xQueueSendFromISR()
发送消息到队列尾部(后向入队),这两个函数是一样的,FromISR用于中断服务函数。
源码太过冗长,主要实现的功能就是入队和入队时间记录方便实现阻塞操作,这里就不再具体分析,想看的话可以留言我单独发一期。
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait )
参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
pvltemToQueue: 指向要发送的消息,发送时候会将这个消息拷贝到队列中。
xTicksToWait:阻塞时间,此参数指示当队列满的时候任务进入阻塞态等待队列空闲的最大
时间。如果为0的话当队列满的时候就立即返回;当为 portMAX_DELAY的话就会一直等待,直到队列有空闲的队列项,也就是死等,但是宏INCLUDE_vTaskSuspend必须为1。
3. xQueueSendToFront() & xQueueSendToFrontFromISR()
xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait )
发送消息到队列头(前向入队),FromISR用于中断服务函数。
参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
pvltemToQueue: 指向要发送的消息,发送时候会将这个消息拷贝到队列中。
xTicksToWait:阻塞时间,此参数指示当队列满的时候任务进入阻塞态等待队列空闲的最大
时间。如果为0的话当队列满的时候就立即返回;当为 portMAX_DELAY的话就会一直等待,直到队列有空闲的队列项,也就是死等,但是宏INCLUDE_vTaskSuspend必须为1。
4. xQueueOverwrite() & xQueueOverwriteFromISR()
发送消息到队列,带覆写功能,当队列满了以后自动覆盖掉旧的消息,所以也不存在满员等待的情况,FromISR用于中断服务函数。
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
pvltemToQueue: 指向要发送的消息,发送时候会将这个消息拷贝到队列中。
xQueueOverwrite( xQueue, pvItemToQueue )
5. xQueueReceive() & xQueueReceiveFromISR()
BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )
从队列中读取队列项消息,并且读取完以后删除掉队列项(消息)。
参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区中。
xTicksToWait: 阻塞时间,此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最大时间。如果为0的话当队列空的时候就立即返回;当为portMAX_DELAY的话就会一直等待,直到队列有数据,也就是死等,但是宏INCLUDE_vTaskSuspend必须为1。
6. xQueuePeek() & xQueuePeekFroemISR ()
BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )
从队列中读取队列项消息,并且读取完以后不删除队列项(消息)。
参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区中。
xTicksToWait: 阻塞时间,此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最大时间。如果为0的话当队列空的时候就立即返回;当为portMAX_DELAY的话就会一直等待,直到队列有数据,也就是死等,但是宏INCLUDE_vTaskSuspend必须为1。
ESP32使用
样例:设计一个接口,每当按键按下的时候向指定队列发送一次消息,没有消息则永久等待,当两个线程都获取到了信息再将这个信息消除具体框图如下。
程序框架:
此实验目的为了T12烙铁信息传递部分,因为网络接口还没实现,网络数据刷新用一个屏幕显示线程替代。
最后代码如下
外部中断配置部分
#include "key.h"
#define GPIO(n) (1ULL<<n)
#define GPIO_Logic(n) n
#define EXTI_Num 0
#define EXTI2_Num 18
extern QueueHandle_t Datasender;
void EXIT_Handelr_2()
{
BaseType_t xHigherPriorityTaskWoken;
int Temp=185;
xHigherPriorityTaskWoken=pdFALSE;
if(Datasender!=NULL)//中断有可能在队列创建完成前就已经运行此处需要判断一下
xQueueSendFromISR(Datasender,&Temp,&xHigherPriorityTaskWoken);//使用附带ISR的API才能在中断中传递数据,注意需要任务切换。
if( xHigherPriorityTaskWoken )//判断任务切换
{
// Actual macro used here is port specific.
portYIELD_FROM_ISR ();
}
}
void EXIT_Config()//GPIO终端配置函数
{
gpio_config_t EXTI_config;
EXTI_config.pin_bit_mask=GPIO(EXTI_Num); /*!< GPIO pin: set with bit mask, each bit maps to a GPIO */
EXTI_config.mode=GPIO_MODE_INPUT; /*!< GPIO mode: set input/output mode*/
EXTI_config.pull_up_en = 1; /*!< GPIO pull-up*/
EXTI_config.pull_down_en = 0;
EXTI_config.intr_type=GPIO_INTR_NEGEDGE; /*!< GPIO interrupt type*/
gpio_config(&EXTI_config);
gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3);
gpio_isr_handler_add(EXTI_Num,EXIT_Handelr,NULL);
}
队列创建部分:
#include "myqueue.h"
/*settings*/
#define queue_size 10
#define Itemsize sizeof(int)
/*proseperity*/
QueueHandle_t Datasender;
/*functionsy*/
void Create_queue()
{
Datasender=xQueueCreate(queue_size,Itemsize);
if(Datasender == NULL)
printf("Create_queue Failed ");
else
printf("Create_queue Successful");
}
lvgl GUI 显示部分:
/*********************** GUI_SHOW_CODE_START***********************/
lv_obj_t * label_1;
lv_obj_t * label_2;
extern QueueHandle_t Datasender;
void Refresh_State()
{
Create_queue();//函数实现在下边
EXIT_Config();//函数实现在下边
char a1[15];
int num = 0;
while (1)
{
if(Datasender!=NULL)
{
xQueuePeek(Datasender,&num,portMAX_DELAY);
sprintf(a1,"num is %d",num);
lv_label_set_text(label_1,a1);//设定文本内容
xQueueReceive(Datasender,&num,portMAX_DELAY);
sprintf(a1,"num is %d",num);
lv_label_set_text(label_2,a1);//设定文本内容
}
}
}
void Show_State()
{
lv_obj_t *scr = lv_scr_act(); //创建scr
lv_obj_set_pos(scr,0,0);
lv_scr_load(scr);
label_1 =lv_label_create(scr);//创建label
lv_label_set_recolor(label_1,1);//颜色可变换
lv_label_set_long_mode(label_1,LV_LABEL_LONG_SCROLL_CIRCULAR);//设置滚动模式
lv_obj_set_pos(label_1,10,0);//设置位置
lv_obj_set_size(label_1,100,60);//设定大小
lv_label_set_text(label_1, "This is the GUI thread");//设定文本内容
label_2 =lv_label_create(scr);//创建label
lv_label_set_recolor(label_2,1);//颜色可变换
lv_label_set_long_mode(label_2,LV_LABEL_LONG_SCROLL_CIRCULAR);//设置滚动模式
lv_obj_set_pos(label_2,10,30);//设置位置
lv_obj_set_size(label_2,100,60);//设定大小
lv_label_set_text(label_2, "This is the Intetnet thread");//设定文本内容
xTaskCreatePinnedToCore(Refresh_State,"Refresh_State_task",1024*2,NULL,3,NULL,1);
//此处必须使用针对哪一个核心的任务分配函数,中断分配和核心是绑定的。
}
/*********************** GUI_SHOW_CODE_END***********************/
效果演示:
按下按键触发中断。两个线程依次获取消息
有兴趣关于ESP32架构的中断介绍的,请持续关注。