目录
2、函数 xQueueCreateStatic():静态创建队列
1、函数 xQueueSend()、 xQueueSendToBack()和 xQueueSendToFront()
2、函数 xQueueGenericSend():任务调用底层函数
xQueueSendToFrontFromISR()函数:在中断里面操作函数
4、函数 xQueueOverwriteFromISR():中断底层调用函数
队列的简介
队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中
断之间传递消息,队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之
间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的
长度,创建队列的时候会指定数据项目的大小和队列的长度。 由于队列用来传递消息的,所以
也称为消息队列。 FreeRTOS 中的信号量的也是依据队列实现的! 所以有必要深入的了解
FreeRTOS 的队列。
队列不是属于某个特别指定的任务的,任何任务都可以向队列中发送消息,或者从队列中
提取消息。
因为FreeRTOS是多任务的,里面的会有一些局部变量,需要供给其他任务使用,使用引出这个队列的定义,就是为了实现它们之间的交流。
任务对队列的操作
读取队列中的消息
当任务尝试从一个队列中读取消息的时候可以指定一个阻塞时间,这个阻塞时间就是当任
务从队列中读取消息无效的时候任务阻塞的时间。 出队就是就从队列中读取消息,出队阻塞是
针对从队列中读取消息的任务而言的。 比如任务 A 用于处理串口接收到的数据, 串口接收到数
据以后就会放到队列 Q 中, 任务 A 从队列 Q 中读取数据。但是如果此时队列 Q 是空的,说明
还没有数据,任务 A 这时候来读取的话肯定是获取不到任何东西,那该怎么办呢?任务 A 现在
有三种选择,一:二话不说扭头就走,二:要不我在等等吧,等一会看看,说不定一会就有数
据了,三:死等,死也要等到你有数据!选哪一个就是由这个阻塞时间决定的,这个阻塞时间
单位是时钟节拍数。 阻塞时间为 0 的话就是不阻塞,没有数据的话就马上返回任务继续执行接
下来的代码, 对应第一种选择。 如果阻塞时间为 0~ portMAX_DELAY, 当任务没有从队列中获
取到消息的话就进入阻塞态,阻塞时间指定了任务进入阻塞态的时间,当阻塞时间到了以后还
没有接收到数据的话就退出阻塞态,返回任务接着运行下面的代码,如果在阻塞时间内接收到
了数据就立即返回,执行任务中下面的代码,这种情况对应第二种选择。当阻塞时间设置为
portMAX_DELAY 的话,任务就会一直进入阻塞态等待,直到接收到数据为止! 这个就是第三种选择。
向队列中发送消息
将消息加入到队列中。和读取队列一样,当一个任务向队列发送消息的话也可以设置阻塞时间。比如任务 B 向消息队列 Q 发送消息,但是此时队列 Q 是满的,那肯定是发送失败的。此时任务B 就会遇到和上面任务 A 一样的问题,这两种情况的处理过程是类似的,只不过一个是向队列 Q 发送消息,一个是从队列 Q 读取消息而已。
队列结构体
有一个结构体用于描述队列,叫做 Queue_t,这个结构体在文件 queue.c 中定义如下
队列创建
在使用队列之前必须先创建队列, 有两种创建队列的方法,一种是静态的,使用函数
xQueueCreateStatic();另一个是动态的,使用函数 xQueueCreate()
1、函数 xQueueCreate():动态创建队列
函数原型
参数
返回值
2、函数 xQueueCreateStatic():静态创建队列
函数原型
参数
返回值
队列的操作
1、向队列写入数据:入队函数,就是向队列写入数据
中断入队函数,就是在定时器中断、外部中断、串口中断服务函数里面所调用的API函数。
1、函数 xQueueSend()、 xQueueSendToBack()和 xQueueSendToFront()
这三个函数都是用于向队列中发送消息的,这三个函数本质都是宏, 其中函数 xQueueSend()和 xQueueSendToBack()是一样的,都是后向入队,即将新的消息插入到队列的后面。函数xQueueSendToToFront()是前向入队,即将新消息插入到队列的前面。然而!这三个函数最后都是调用的同一个函数: xQueueGenericSend()。这三个函数只能用于任务函数中,不能用于中断服务函数,中断服务函数有专用的函数,它们以“FromISR”结尾
函数原型
参数
返回值
2、函数 xQueueGenericSend():任务调用底层函数
此函数才是真正干活的,上面讲的所有的任务级入队函数最终都是调用的此函数,此函数
也是我们后面重点要讲解的
函数原型:
参数
返回值
3、 xQueueSendFromISR()、
xQueueSendToBackFromISR()、
xQueueSendToFrontFromISR()函数:在中断里面操作函数
这三个函数也是向队列中发送消息的,这三个函数用于中断服务函数中。这三个函数本质
也宏,其中函数 xQueueSendFromISR ()和 xQueueSendToBackFromISR ()是一样的,都是后向入队,即将新的消息插入到队列的后面。函数 xQueueSendToFrontFromISR ()是前向入队,即将新消息插入到队列的前面。这三个函数同样调用同一个函数 xQueueGenericSendFromISR ()
函数原型
参数
返回值
注:我们注意观察,可以看出这些函数都没有设置阻塞时间值。原因很简单,这些函数都是在
中断服务函数中调用的, 并不是在任务中,所以也就没有阻塞这一说了!
4、函数 xQueueOverwriteFromISR():中断底层调用函数
此函数是 xQueueOverwrite()的中断级版本, 用在中断服务函数中,在队列满的时候自动覆
写掉旧的数据,此函数也是一个宏,实际调用的也是函数 xQueueGenericSendFromISR(),前面三个函数也都是调用这个函数来使用。
函数原型
参数
返回值
2、读取队列中的数据
1、函数 xQueueReceive()
此函数用于在任务中从队列中读取一条(请求)消息, 读取成功以后就会将队列中的这条数
据删除。
函数原型
参数
返回值
2、函数 xQueuePeek()
此函数用于从队列读取一条(请求)消息, 只能用在任务中! 此函数在读取成功以后不会将
消息删除。
函数原型
参数
返回值
3、 函数 xQueueGenericReceive()
不 管 是 函 数 xQueueReceive() 还 是 xQueuePeek() , 最 终 都 是 调 用 的 函 数
xQueueGenericReceive(),此函数是真正干事的。
函数原型
参数
返回值
4、函数 xQueueReceiveFromISR()
此函数是 xQueueReceive()的中断版本, 用于在中断服务函数中从队列中读取(请求)一条消息,读取成功以后就会将队列中的这条数据删除。
函数原型
参数
返回值
5、函数 xQueuePeekFromISR()
此函数是 xQueuePeek()的中断版本, 此函数在读取成功以后不会将消息删除。
函数原型
参数
返回值
实验操作
接下来我就介绍一下具体的实际操作,也就是调用上面那几个函数,来完成一些变量传递的功能。
实验内容:
我找到一个光照传感器BH1750,建立四个任务,一个任务是开始任务,这个任务作用就是创建其他三个任务,创建三个任务完成,会删除这个任务。一个任务是LED闪烁,表示功能正常运行,一个读取BH1750的关照数值,另一个任务把关照数值传进来,然后判断是否达到阈值,然后利用蜂鸣器报警。
操作流程:
先创建一个队列和三个任务;
然后调用入队函数(向队列中传入参数);
然后调用读取函数(向队列中读取参数);
主程序
#include "sys.h"
#include "usart.h"
#include "gpio.h"
#include "delay.h"
#include "lcd.h"
#include "FreeRTOS.h"
#include "task.h"
#include "bh1750.h"
#include "queue.h"
//LCD刷屏时使用的颜色
QueueHandle_t LxQueue;
//任务优先级
#define START_TASK_PRIO 4
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LED1_TASK_PRIO 0
//任务堆栈大小
#define LED1_STK_SIZE 20
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
//任务优先级
#define LxTask_TASK_PRIO 0
//任务堆栈大小
#define LxTask_STK_SIZE 128
//任务句柄
TaskHandle_t LxTaskTask_Handler;
//任务函数
void LxTask(void *pvParameters);
//任务优先级
#define Beep_TASK_PRIO 0
//任务堆栈大小
#define Beep_STK_SIZE 128
//任务句柄
TaskHandle_t BeepTask_Handler;
//任务函数
void BeepState(void *pvParameters);
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
My_USART_Init(); //初始化串口
MX_GPIO_Init(); //初始化GPIO
LCD_Init();
LCD_Clear(BLACK);
BACK_COLOR=BLACK;
POINT_COLOR=RED;
bh1750_init();
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
LxQueue=xQueueCreate(1,sizeof(float)); //创建队列
//创建LED1任务
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
//创建光照读取任务
xTaskCreate((TaskFunction_t) LxTask,
(const char* ) "LxTask",
(uint16_t ) LxTask_STK_SIZE,
(void* ) NULL,
(UBaseType_t ) LxTask_TASK_PRIO,
(TaskHandle_t* ) &LxTaskTask_Handler);
//创建蜂鸣器任务
xTaskCreate((TaskFunction_t) BeepState,
(const char* ) "BeepState",
(uint16_t ) Beep_STK_SIZE,
(void* ) NULL,
(UBaseType_t ) Beep_TASK_PRIO,
(TaskHandle_t* ) &BeepTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/* jwiw */
//LED1任务函数
void led1_task(void *pvParameters)
{
while(1)
{
LED1=~LED1;
vTaskDelay(800);
}
}
void LxTask(void *pvParameters)
{
float temp;
while(1)
{
temp=BH1750_Measure();//读取光照强度
if(LxQueue!=NULL)
xQueueSend(LxQueue,&temp,10);//把数据发送到队列中
LcdSprintf(20,240,200,30,24,"Temp_BUFF:%.2f",temp); //在LCD上显示
vTaskDelay(300);
}
}
float Lx;
void BeepState(void *pvParameters)
{
while(1)
{
if(LxQueue!=NULL)
xQueueReceive(LxQueue,&Lx,10);//读取队列中的数据
if(Lx>300)
{
LED0=1;
GPIO_ResetBits(GPIOB,GPIO_Pin_8);//蜂鸣器任务
}
else
{
LED0=0;
GPIO_SetBits(GPIOB,GPIO_Pin_8);
}
LcdSprintf(20,280,200,30,24,"Lx_Buff:%.2f",Lx); //LCD显示是否和上面显示一致
vTaskDelay(300);
}
}