一、简介
1、队列简介:
队列:是任务到任务,任务到中断、中断到任务数据交流的一种机制(消息传递)。
FreeRTOS基于队列,实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、二值信号量、递归互斥信号量,因此很有必要深入了解FreeRTOS的队列。
(中断一关闭,就不会出现任务切换,以防多个任务同时操作队列)
2、FreeRTOS队列特点:
1.数据入队出队方式:先进先出
2.数据传递方式:实际值
3.多任务访问
4. 出队、入队堵塞
问题:当多个任务写入消息给一个“满队列”时,这些任务都会进入阻塞状态,也就是说有多个任务 在等待同一 个队列的空间。那当队列中有空间时,哪个任务会进入就绪态?
答: 1、优先级最高的任务 2、如果大家的优先级相同,那等待时间最久的任务会进入就绪态
注:我始终认为自己不是一个很聪明的人,所以这些理论知识,我都是浅尝辄止,量力而行。
3、往队列写入消息API函数 :
4、从队列读取消息API函数:
二、实验
1、实验步骤
2、代码:
main.c
#include "stm32f10x.h"
#include "FreeRTOS.h"
#include "task.h"
#include "freertos_demo.h"
#include "Delay.h"
#include "sys.h"
#include "usart.h"
#include "LED.h"
#include "Key.h"
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组 4
uart_init(115200);
delay_init();
Key_Init();
LED_Init();
// 创建任务
FrrrRTOS_Demo();
}
freertos_demo.c
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "LED.h"
#include "Key.h"
#include "usart.h"
#include "delay.h"
/******************************************************************任务配置****************************************************/
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_TASK_STACK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define TASK1_PRIO 2
//任务堆栈大小
#define TASK1_STACK_SIZE 128
//任务句柄
TaskHandle_t Task1_Handler;
//任务函数
void task1(void *pvParameters);
//任务优先级
#define TASK2_PRIO 3
//任务堆栈大小
#define TASK2_STACK_SIZE 128
//任务句柄
TaskHandle_t Task2_Handler;
//任务函数
void task2(void *pvParameters);
//任务优先级
#define TASK3_PRIO 4
//任务堆栈大小
#define TASK3_STACK_SIZE 128
//任务句柄
TaskHandle_t Task3_Handler;
//任务函数
void task3(void *pvParameters);
char task_buffer[500]; //用于存储系统中任务信息表格
/******************************************************************任务函数****************************************************/
QueueHandle_t key_queue; //小数据句柄
QueueHandle_t big_data_queue; //大数据 句柄
char buff[100] = {"苍天已死,黄天当立;岁在甲子,天下大吉"};
void FrrrRTOS_Demo(void)
{
key_queue = xQueueCreate(2, sizeof(uint8_t));
if(key_queue != NULL)
{
printf("\r\nkey_queue队列创建成功!!!\r\n");
}else{ printf("key_queue队列创建失败!!!\r\n"); }
big_data_queue = xQueueCreate(1, sizeof(char *));
if(big_data_queue != NULL)
{
printf("big_data_queue队列创建成功!!!\r\n");
}else{ printf("big_data_queue队列创建失败!!!\r\n"); }
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
( char* )"start_task", //任务名称
(uint16_t )START_TASK_STACK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
// 启动任务调度
vTaskStartScheduler();
}
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建1任务
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1_Handler);
//创建2任务
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2_Handler);
//创建3任务
xTaskCreate((TaskFunction_t )task3,
(const char* )"task3",
(uint16_t )TASK3_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3_Handler);
vTaskDelete(NULL); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//1 任务函数
void task1(void *pvParameters)
{
uint8_t key = 0;
BaseType_t err;
char *buf;
buf = &buff[0];
while(1)
{
key = Key_GetNum();
if(key == 1 || key == 2)
{
err = xQueueSend( key_queue, &key, portMAX_DELAY );
if(err != pdTRUE)
{
printf("key_queue队列发送失败\r\n");
}
}else if(key == 3)
{
err = xQueueSend( big_data_queue, &buf, portMAX_DELAY );
if(err != pdTRUE)
{
printf("key_queue队列发送失败\r\n");
}
}
vTaskDelay(50);
}
}
// 任务2 小数据出队函数
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 = %d\r\n",key);};
}
}
//不调用系统延时函数,因为xQueueReceive()函数如果读取完队列里面的数据,就会由就绪态转变为阻塞态;
// 任务3 大数据出队函数
void task3(void *pvParameters)
{
char * buf;
BaseType_t err = 0;
// 任务主循环
while (1)
{
err = xQueueReceive( big_data_queue, &buf, portMAX_DELAY);
if(err != pdTRUE)
{
printf("big_data_queue队列读取失败\r\n");
}else{printf("key = %s\r\n",buf);};
}
}
key.c
#include "stm32f10x.h" // Device header
#include "FreeRTOS.h"
#include "task.h"
#include "usart.h"
#include "Delay.h"
/**
* 函 数:按键初始化
* 参 数:无
* 返 回 值:无
* 按键:PB4/PB12/PB14
*/
void Key_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
/*GPIO初始化*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_12 | GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/**
* 函 数:按键获取键码
* 参 数:无
* 返 回 值:按下按键的键码值,范围:0~3,返回0代表没有按键按下
* 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手
*/
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0; //定义变量,默认键码值为0
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_4) == 0) //读PB4输入寄存器的状态,如果为0,则代表按键1按下
{
KeyNum= GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_4);
printf("KeyNum = %d\r\n",KeyNum);
delay_xms(20); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_4) == 0); //等待按键松手
delay_xms(20); //延时消抖
KeyNum = 1; //置键码为1
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12) == 0)
{
KeyNum= GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12);
printf("KeyNum = %d\r\n",KeyNum);
delay_xms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12) == 0);
delay_xms(20);
KeyNum = 2;
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
{
KeyNum= GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14);
printf("KeyNum = %d\r\n",KeyNum);
delay_xms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0);
delay_xms(20);
KeyNum = 3;
}
return KeyNum; //返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}
3、实验结果解析
开始运行:
按下按键1(PB4):
按下按键1,就会往队列key_queue里面写入key值(1),然后任务切换到task2将队列key_queue里面的数据读取出来;;
按下按键2(PB12):
按下按键2,就会往队列key_queue里面写入key值(2),然后任务切换到task2将队列key_queue里面的数据读取出来;
按下按键3(PB14) :
按下按键2,就会往队列big_data_queue里面写入key值(3),然后任务切换到task3将队列big_data_queue里面的数据读取出来;
三、重点
使用队列相关函数时需要将下面宏置1(默认是1):
#define configSUPPORT_DYNAMIC_ALLOCATION 1
队列创建函数:
xQueueCreate( uxQueueLength, uxItemSize ) ; //uxQueueLength:队列长度;uxItemSize 队列参数的大小
队列写入消息函数:
xQueueSend( xQueue, pvItemToQueue, xTicksToWait ); //xQueue:待写入的队列;pvItemToQueue:待写入的消息;xTicksToWait:阻塞超时时间
队列读取消息函数:
xQueueReceive( QueueHandle_t xQueue,void * const pvBuffer,TickType_t xTicksToWait ) ; //xQueue:待读取的队列;pvBuffer:信息读取缓冲区;xTicksToWait:阻塞超时时间
问题:任务2(task2)和任务3(task3)没有系统延时函数(xTaskDelay()),按优先级来说应该一直执行任务3(task3),复位后却先执行了任务1(task1)?
答:因为xQueueReceive()和xQueueSend()函数,如果读取完或写入完队列里面的数据,自动会使任务由就绪态转变为阻塞态,知道队列里面有数据可以写入或者读出;