aspnet网站开发视频,网站后台无法修改,无忧网站优化,精准客源app1. 队列简介
1.1 队列的概念
队列是任务到任务、任务到中断、中断到任务数据交流的一种机制#xff08;消息传递#xff09; 类似全局变量#xff1f;假设有一个全局变量a 0#xff0c;现有两个任务都在写这个变量 a#xff1a; 大家想象一下如果任务 1 运行一次#…1. 队列简介
1.1 队列的概念
队列是任务到任务、任务到中断、中断到任务数据交流的一种机制消息传递 类似全局变量假设有一个全局变量a 0现有两个任务都在写这个变量 a 大家想象一下如果任务 1 运行一次任务 2 运行一次理论上 a 等于什么等于 2。但是大家想象一下这个 a 在 C 语言这里其实它内部是分成很多个操作的我们来看一下内部首先就把 a 给读到 cpu 的寄存器 r0 里面然后再把 r0 进行加一然后加完之后再把 r0 这个值给回这个 a 这个地址这样的话 a 就变成 1 了如果裸机的话肯定是没有这种任务1、任务2 去干扰你。 但是 os 的话就存在这种可能了比如任务 1 执行到第二步这里的时候被更高优先级的任务 2 给打断了这时候有可能出现什么情况那么假设一下首先 a 本来是 0读到 r0 里面此时 r0 r0 1 1此时被任务 2 打断那打断之后代表 r0 还没给到 a那这时候任务 2 它同样的也是这么一个操作因为任务 2 的优先级是最高的所以它完整的执行了这 3 步这时候 a 它还是 0因为任务 1 还没有执行到第三步还没给回 a所以执行完这 3 步以后a 1然后任务 2 执行完之后又回到任务 1 的第三步那这时候 r0 它也是 11 它又赋值给 a这时候它们运行的结果 a 就等于 1。 那这样的话执行了两个任务都执行了一次但是它实际的效果竟然 a 1这样就造成一个数据的受损。 全局变量的弊端数据无保护导致数据不安全当多个任务同时对该变量操作时数据易受损。
使用队列的情况如下 本质临界区-关中断-关闭优先级最低的 PendSV 中断-关闭任务调度。 队列的优点读写队列做好了保护防止多任务同时访问冲突我们只需要直接调用API函数即可简单易用
FreeRTOS基于队列 实现了多种功能其中包括队列集、互斥信号量、计数型信号量、二值信号量、 递归互斥信号量因此很有必要深入了解 FreeRTOS 的队列 。
1.2 队列的特征
在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”队列能够存储“队列项目”的最大数量称为队列的长度。 类似于数组。 注意在创建队列时就要指定队列长度以及队列项目的大小 FreeRTOS 队列特点
数据入队出队方式队列通常采用 “先进先出”(FIFO) 的数据存储缓冲机制即先入队的数据会先从队列中被读取FreeRTOS中也可以配置为 “后进先出”(LIFO) 方式数据传递方式。FreeRTOS中队列采用实际值传递即将数据拷贝到队列中进行传递。FreeRTOS采用拷贝数据传递也可以传递指针所以在传递较大的数据的时候采用指针传递。像 ucos 是指针传递把地址给写进去 优点互不干扰。 缺点耗时比较长。 多任务访问。队列不属于某个任务任何任务和中断都可以向队列发送/读取消息。出队、入队阻塞。当任务向一个队列发送消息时假设此时当队列已满无法入队这时候就可以指定一个阻塞时间。 若阻塞时间为 0 直接返回不会等待若阻塞时间为 0~port_MAX_DELAY 等待设定的阻塞时间若在该时间内还无法入队超时后直接返回不再等待若阻塞时间为 port_MAX_DELAY 死等一直等到可以入队为止。出队阻塞与入队阻塞类似 入队阻塞
队列满了此时写不进去数据 ① 将该任务的状态列表项挂载在 pxDelayedTaskList阻塞列表 ② 将该任务的事件列表项挂载在 xTasksWaitingToSend等待发送列表
出队阻塞 队列为空此时读取不了数据 ①将该任务的状态列表项挂载在 pxDelayedTaskList阻塞列表 ②将该任务的事件列表项挂载在 xTasksWaitingToReceive等待接收列表 问题当多个任务写入消息给一个“满队列”时这些任务都会进入阻塞状态也就是说有多个任务在等待同一个队列的空间。那当队列中有空间时哪个任务会进入就绪态 答 优先级最高的任务如果大家的优先级相同那等待时间最久的任务会进入就绪态 1.3 队列基本操作过程 这时候这个 20 它会移到队列头部。
2. 队列结构体介绍
队列实际它的内存是分为两部分第一个就是存放结构体的内存接着是队列里面的队列项它用来存放数据的我们写入数据给队列就写入到队列项里面了。
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 /* 写入上锁计数器 *//* 其他的一些条件编译 */
} xQUEUE;上锁计数器当锁住队列的时候此时还是可以正常读写队列的只不过操作不了它的等待发送和等待接收这两个列表当解锁的时候才会去操作这两个列表。那这个就叫队列锁。 当用于队列使用时
typedef struct QueuePointers
{int8_t * pcTail; /* 存储区的结束地址 */int8_t * pcReadFrom; /* 最后一个读取队列的地址 */
} QueuePointers_t;当用于互斥信号量和递归互斥信号量时
typedef struct SemaphoreData
{TaskHandle_t xMutexHolder; /* 互斥信号量持有者 */UBaseType_t uxRecursiveCallCount; /* 递归互斥信号量的获取计数器 */
} SemaphoreData_t;队列结构体整体示意图 3. 队列相关 API 函数介绍
使用队列的主要流程创建队列-写队列-读队列。
3.1 创建队列
创建队列相关API函数介绍
函数描述xQueueCreate()动态方式创建队列xQueueCreateStatic()静态方式创建队列
动态和静态创建队列之间的区别
动态队列所需的内存空间由 FreeRTOS 从 FreeRTOS 管理的堆中自动分配。静态创建需要用户自行分配内存。
3.1.1 动态创建队列常用
#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), (queueQUEUE_TYPE_BASE ))此函数用于使用动态方式创建队列队列所需的内存空间由 FreeRTOS 从 FreeRTOS 管理的堆中自动分配 。
形参描述uxQueueLength队列长度uxItemSize队列项目的大小
返回值描述NULL队列创建失败其他值队列创建成功返回队列句柄 前面说 FreeRTOS 基于队列实现了多种功能每一种功能对应一种队列类型队列类型的 queue.h 文件中有定义 #define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U ) /* 队列 */
#define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U ) /* 队列集 */
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U ) /* 互斥信号量 */
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U ) /* 计数型信号量 */
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U ) /* 二值信号量 */
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U ) /* 递归互斥信号量 */ 3.2 写队列
往队列写入消息API函数
函数任务级中断级描述xQueueSend()往队列的尾部写入消息xQueueSendToBack()同 xQueueSend()xQueueSendToFront()往队列的头部写入消息xQueueOverwrite()覆写队列消息只用于队列长度为 1 的情况xQueueSendFromISR()在中断中往队列的尾部写入消息xQueueSendToBackFromISR()同 xQueueSendFromISR()xQueueSendToFrontFromISR()在中断中往队列的头部写入消息xQueueOverwriteFromISR()在中断中覆写队列消息只用于队列长度为 1 的情况
队列写入消息
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait )xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait )xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait )xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )
#define xQueueOverwrite( xQueue, pvItemToQueue )xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )可以看到这几个写入函数调用的是同一个函数 xQueueGenericSend()只是指定了不同的写入位置 覆写队列长度为 1只有一个队列项目比如队列里有数据了那覆写就是不管队列中有没有数据都会再写进去覆盖就是永远是写的进去的所以是不会阻塞的所以它的默认阻塞时间是 0就直接跳过直接继续写。 其他像尾部写入、头部写入那这些它们都是会阻塞的它们都是有一个阻塞时间的设置的如果队列满了之后它是写不进去的。那这个是覆写方式与其他方式它们之间的一个区别。 队列一共有 3 种写入位置
#define queueSEND_TO_BACK ( ( BaseType_t ) 0 ) /* 写入队列尾部 */
#define queueSEND_TO_FRONT ( ( BaseType_t ) 1 ) /* 写入队列头部 */
#define queueOVERWRITE ( ( BaseType_t ) 2 ) /* 覆写队列 */注意覆写方式写入队列只有在队列的队列长度为 1 时才能够使用 往队列写入消息函数入口参数解析
BaseType_t xQueueGenericSend(QueueHandle_t xQueue,const void * const pvItemToQueue,TickType_t xTicksToWait,const BaseType_t xCopyPosition); 形参描述xQueue待写入的队列pvItemToQueue待写入消息xTicksToWait阻塞超时时间xCopyPosition写入的位置
返回值描述pdTRUE队列写入成功errQUEUE_FULL队列写入失败
3.3 读队列
从队列读取消息API函数
函数任务级中断级描述xQueueReceive()从队列头部读取消息并删除消息xQueuePeek()从队列头部读取消息xQueueReceiveFromISR()在中断中从队列头部读取消息并删除消息xQueuePeekFromISR()在中断中从队列头部读取消息
BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )此函数用于在任务中从队列中读取消息并且消息读取成功后会将消息从队列中移除。
形参描述xQueue待读取的队列pvBuffer信息读取缓冲区xTicksToWait阻塞超时时间
返回值描述pdTRUE读取成功pdFALSE读取失败
BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )此函数用于在任务中从队列中读取消息 但与函数 xQueueReceive()不同此函数在成功读取消息后并不会移除已读取的消息
形参描述xQueue待读取的队列pvBuffer信息读取缓冲区xTicksToWait阻塞超时时间
返回值描述pdTRUE读取成功pdFALSE读取失败
4. 队列操作实验
实验目的学习 FreeRTOS 的队列相关API函数的使用 实现队列的入队和出队操作。 实验设计将设计四个任务start_task、task1、task2、task3
四个任务的功能如下 start_task用来创建task1和task2以及task3任务 task1当按键 key0 或 key1 按下将键值拷贝到队列 key_queue入队当按键 key_up 按下将传输大数据这里拷贝大数据的地址到队列 big_date_queue 中。 task2读取队列 key_queue 中的消息出队打印出接收到的键值。 task3从队列 big_date_queue 读取大数据地址通过地址访问大数据。
4.1 创建队列
#include queue.hQueueHandle_t key_queue; /* 小数据句柄 */
QueueHandle_t big_date_queue; /* 大数据句柄 */
char buff[100] {我是一个大数组大大的数组 124214 uhsidhaksjhdklsadhsaklj};
/*** brief FreeRTOS例程入口函数* param 无* retval 无*/
void freertos_demo(void)
{ /* 队列的创建 */key_queue xQueueCreate( 2, sizeof(uint8_t) );if(key_queue ! NULL){printf(key_queue队列创建成功\r\n);}else printf(key_queue队列创建失败\r\n);big_date_queue xQueueCreate( 1, sizeof(char *) );if(big_date_queue ! NULL){printf(big_date_queue队列创建成功\r\n);}else printf(big_date_queue队列创建失败\r\n);xTaskCreate((TaskFunction_t ) start_task,(char * ) start_task,(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,(void * ) NULL,(UBaseType_t ) START_TASK_PRIO,(TaskHandle_t * ) start_task_handler );vTaskStartScheduler();
}
4.2 任务函数实现
/* 任务一实现入队 */
void task1( void * pvParameters )
{uint8_t key 0;char * buf;buf buff; /* buf buff[0] */BaseType_t err 0;while(1) {key key_scan(0);if(key KEY0_PRES || key KEY1_PRES){err xQueueSend( key_queue, key, portMAX_DELAY ); /* 写入队列、死等 */if(err ! pdTRUE){printf(key_queue队列发送失败\r\n);}}else if(key WKUP_PRES){err xQueueSend( big_date_queue, buf, portMAX_DELAY ); /* 写入队列、死等 */if(err ! pdTRUE){printf(key_queue队列发送失败\r\n);}}vTaskDelay(10);}
}/* 任务二小数据出队 */
void task2( void * pvParameters )
{uint8_t key 0;BaseType_t err 0;while(1){err xQueueReceive(key_queue, key, portMAX_DELAY);if(err ! pdTRUE){printf(key_queue队列读取失败\r\n);}else {printf(key_queue读取队列成功数据%d\r\n,key);}}
}/* 任务三大数据出队 */
void task3( void * pvParameters )
{char * buf;BaseType_t err 0;while(1){err xQueueReceive(big_date_queue, buf, portMAX_DELAY);if(err ! pdTRUE){printf(big_date_queue队列读取失败\r\n);}else {printf(数据%s\r\n,buf);}}
}5. 队列相关API函数解析
队列的创建API函数xQueueCreate( )往队列写入数据API函数入队xQueueSend( )从队列读取数据API函数出队 xQueueReceive( ) 更多的队列相关API函数讲解可以查看《FreeRTOS开发指南》第十三章—队列