FreeRTOS消息队列 & ESP32实战
FreeRTOS消息队列
FreeRTOS的消息队列和操作系统课中讲的消息队列大差不差,都是为了有序的、安全的在多任务间在发送信息。下面是其一些特性。
- 多任务访问
??队列不是属于某个特别指定的任务的,任何任务都可以向队列中发送消息,或者从队列中提取消息。 - 原始值传递
??队列中的消息内容不是引用,即不是把内容的地址传递,而是将数据的内容直接拷贝到消息队列中,这样做的好处有两个,第一是在传递完后,消息缓冲区可立即进行更改不需要等到消息完成传递后再更改,第二是不会因为局部变量变化而导致消息混乱,将某个函数的局部变量作为消息传递的时候,当函数运行完成并返回,这个局部变量就会被销毁,这样就导致传递到队列里的地址就是一个垃圾地址,内容并没有什么用处。当然也可以直接将地址作为内容传递过去,这样就实现了引用传递。 - 出队阻塞
??当任务想从队列中读取消息时可以将自己阻塞起来等待数据到达,当然可以不等待获取,也可以等待一定时间获取,更可以永久等待。 - 入队阻塞
??当任务想向队列中传递消息时发现队列已经满了,就可以选择将自己阻塞起来等待队列有空位置再将消息传递进去。当然也有三种阻塞模式,不阻塞、阻塞一段时间、永久阻塞。
FreeRTOS消息队列结构体源码分析
typedef struct QueueDefinition
{
int8_t *pcHead;
int8_t *pcWriteTo;
union
{
QueuePointers_t xQueue;
SemaphoreData_t xSemaphore;
} u;
List_t xTasksWaitingToSend;
List_t xTasksWaitingToReceive;
volatile UBaseType_t uxMessagesWaiting;
UBaseType_t uxLength;
UBaseType_t uxItemSize;
volatile int8_t cRxLock;
volatile int8_t cTxLock;
#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated;
#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;
} xQUEUE;
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 )
{
xQueueSizeInBytes = ( size_t ) 0;
}
else
{
xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );
}
configASSERT( ( uxItemSize == 0 ) || ( uxQueueLength == ( xQueueSizeInBytes / uxItemSize ) ) );
configASSERT( ( sizeof( Queue_t ) + xQueueSizeInBytes ) > xQueueSizeInBytes );
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
if( pxNewQueue != NULL )
{
pucQueueStorage = ( uint8_t * ) pxNewQueue;
pucQueueStorage += sizeof( Queue_t );
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
pxNewQueue->ucStaticallyAllocated = pdFALSE;
}
#endif
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);
if( xHigherPriorityTaskWoken )
{
portYIELD_FROM_ISR ();
}
}
void EXIT_Config()
{
gpio_config_t EXTI_config;
EXTI_config.pin_bit_mask=GPIO(EXTI_Num);
EXTI_config.mode=GPIO_MODE_INPUT;
EXTI_config.pull_up_en = 1;
EXTI_config.pull_down_en = 0;
EXTI_config.intr_type=GPIO_INTR_NEGEDGE;
gpio_config(&EXTI_config);
gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3);
gpio_isr_handler_add(EXTI_Num,EXIT_Handelr,NULL);
}
队列创建部分:
#include "myqueue.h"
#define queue_size 10
#define Itemsize sizeof(int)
QueueHandle_t Datasender;
void Create_queue()
{
Datasender=xQueueCreate(queue_size,Itemsize);
if(Datasender == NULL)
printf("Create_queue Failed ");
else
printf("Create_queue Successful");
}
lvgl GUI 显示部分:
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();
lv_obj_set_pos(scr,0,0);
lv_scr_load(scr);
label_1 =lv_label_create(scr);
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);
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);
}
效果演示:
两个显示线程 :
按下按键触发中断。两个线程依次获取消息
两个显示线程 :
有兴趣关于ESP32架构的中断介绍的,请持续关注。
|