文章目录
一、队列的特性
1、常规操作
- 队列可以包含若干个数据:队列中有若干项,这被称为"长度"(length)
- 每个数据大小固定
- 创建队列时就要指定长度、数据大小
- 数据的操作采用先进先出的方法(FIFO,First In First Out):写数据时放到尾
部,读数据时从头部读 - 也可以强制写队列头部:覆盖头部数据
更详细的操作入下图所示:
2、传输数据的两种方法
使用队列传输数据时有两种方法:
- 拷贝:把数据、把变量的值复制进队列里
- 引用:把数据、把变量的地址复制进队列里
FreeRTOS 使用拷贝值的方法,这更简单:
- 局部变量的值可以发送到队列中,后续即使函数退出、局部变量被回收,也不会影响队列中的数据
- 无需分配buffer来保存数据,队列中有buffer 局部变量可以马上再次使用
- 发送任务、接收任务解耦:接收任务不需要知道这数据是谁的、也不需要发送任务来释放数据
- 如果数据实在太大,你还是可以使用队列传输它的地址
- 队列的空间有FreeRTOS内核分配,无需任务操心
- 对于有内存保护功能的系统,如果队列使用引用方法,也就是使用地址,必须确保双方任务对这个地址都有访问权限。使用拷贝方法时,则无此限制:内核 有足够的权限,把数据复制进队列、再把数据复制出队列。
3、队列的阻塞访问
只要知道队列的句柄,谁都可以读、写该队列。任务、ISR 都可读、写队列。可以多个任务读写队列。
任务读写队列时,简单地说:如果读写不成功,则阻塞;可以指定超时时间。口语化地说,就是可以定个闹钟:如果能读写了就马上进入就绪态,否则就阻塞直到超时。
某个任务读队列时,如果队列没有数据,则该任务可以进入阻塞状态:还可以指定阻塞的时间。如果队列有数据了,则该阻塞的任务会变为就绪态。如果一直都没有数据,则时间到之后它也会进入就绪态。
既然读取队列的任务个数没有限制,那么当多个任务读取空队列时,这些任务都会进入阻塞状态:有多个任务在等待同一个队列的数据。当队列中有数据时,哪个任务会进入就绪态?
- 优先级最高的任务
- 如果大家的优先级相同,那等待时间最久的任务会进入就绪态
4、任务之间如何传输数据
数据个数 | 互斥措施 | 阻塞-唤醒 | 使用场景 | |
---|---|---|---|---|
全局变量 | 1 | 无 | 无 | 一读一写 |
环形缓冲区 | 多个 | 无 | 无 | 一读一写 |
队列 | 多个 | 有 | 有 | 多读多写 |
队列中,数据的读写本质就是环形缓冲区,在这个基础上增加了互斥措施、阻塞-唤醒机制。
如果这个队列不传输数据,只调整"数据个数",它就是信号量(semaphore)。
如果信号量中,限定"数据个数"最大值为1,它就是互斥量(mutex)。
二、队列函数
使用队列的流程:创建队列、写队列、读队列、删除队列。
1、创建
队列的创建有两种方法:动态分配内存、静态分配内存。
- 动态分配内存:xQueueCreate,队列的内存在函数内部动态分配
函数原型如下:
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
参数 | 说明 |
---|---|
uxQueueLength | 队列长度,最多能存放多少个数据(item) |
uxItemSize | 每个数据(item)的大小:以字节为单位 |
返回值 | 非 0:成功,返回句柄,以后使用句柄来操作队列;NULL:失败,因为内存不足 |
- 静态分配内存:xQueueCreateStatic,队列的内存要事先分配好
函数原型如下:
QueueHandle_t xQueueCreateStatic(
UBaseType_t uxQueueLength,
UBaseType_t uxItemSize,
uint8_t *pucQueueStorageBuffer,
StaticQueue_t *pxQueueBuffer
);
参数 | 说明 |
---|---|
uxQueueLength | 队列长度,最多能存放多少个数据(item) |
uxItemSize | 每个数据(item)的大小:以字节为单位 |
pucQueueStorageBuffer | 如果 uxItemSize 非 0,pucQueueStorageBuffer 必须指向一个 uint8_t 数组,此数组大小至少为"uxQueueLength * uxItemSize" |
pxQueueBuffer | 必须执行一个 StaticQueue_t 结构体,用来保存队列的数据结构 |
返回值 | 非 0:成功,返回句柄,以后使用句柄来操作队列;NULL:失败,因为 pxQueueBuffer 为 NULL |
示例代码:
// 示例代码
#define QUEUE_LENGTH 10
#define ITEM_SIZE sizeof( uint32_t )
// xQueueBuffer 用来保存队列结构体
StaticQueue_t xQueueBuffer;
// ucQueueStorage 用来保存队列的数据
// 大小为:队列长度 * 数据大小
uint8_t ucQueueStorage[ QUEUE_LENGTH * ITEM_SIZE ];
void vATask( void *pvParameters )
{
QueueHandle_t xQueue1;
// 创建队列: 可以容纳 QUEUE_LENGTH 个数据,每个数据大小是 ITEM_SIZE
xQueue1 = xQueueCreateStatic( QUEUE_LENGTH,
ITEM_SIZE,
ucQueueStorage,
&xQueueBuffer );
}
2、复位
队列刚被创建时,里面没有数据;使用过程中可以调用 *xQueueReset()*把队列恢复为初始状态,此函数原型为:
/* pxQueue : 复位哪个队列;
* 返回值: pdPASS(必定成功)
*/
BaseType_t xQueueReset( QueueHandle_t pxQueue);
3、删除
删除队列的函数为 vQueueDelete(),只能删除使用动态方法创建的队列,它会释放内存。原型如下:
void vQueueDelete( QueueHandle_t xQueue );
4、写队列
可以把数据写到队列头部,也可以写到尾部,这些函数有两个版本:在任务中使用、在ISR 中使用。函数原型如下:
/* 等同于xQueueSendToBack
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSend(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);
/*
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSendToBack(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);
/*
* 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞
*/
BaseType_t xQueueSendToBackFromISR(
QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);
/*
* 往队列头部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSendToFront(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);
/*
* 往队列头部写入数据,此函数可以在中断函数中使用,不可阻塞
*/
BaseType_t xQueueSendToFrontFromISR(
QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);
这些函数用到的参数是类似的,统一说明如下:
参数 | 说明 |
---|---|
xQueue | 队列句柄,要写哪个队列 |
pvItemToQueue | 数据指针,这个数据的值会被复制进队列,复制多大的数据?在创建队列时已经指定了数据大小 |
xTicksToWait | 如果队列满则无法写入新数据,可以让任务进入阻塞状态,xTicksToWait 表示阻塞的最大时间(Tick Count)。如果被设为 0,无法写入数据时函数会立刻返回;如果被设为 portMAX_DELAY,则会一直阻塞直到有空间可写 |
返回值 | pdPASS:数据成功写入了队列;errQUEUE_FULL:写入失败,因为队列满了 |
5、读队列
使用 xQueueReceive() 函数读队列,读到一个数据后,队列中该数据会被移除。这个函数有两个版本:在任务中使用、在 ISR 中使用。函数原型如下:
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );
BaseType_t xQueueReceiveFromISR(
QueueHandle_t xQueue,
void *pvBuffer,
BaseType_t *pxTaskWoken
);
参数说明如下:
参数 | 说明 |
---|---|
xQueue | 队列句柄,要读哪个队列 |
pvBuffer | bufer 指针,队列的数据会被复制到这个 buffer;复制多大的数据?在创建队列时已经指定了数据大小 |
xTicksToWait | 如果队列空则无法读出数据,可以让任务进入阻塞状态,xTicksToWait 表示阻塞的最大时间(Tick Count)。如果被设为 0,无法读出数据时函数会立刻返回;如果被设为 portMAX_DELAY,则会一直阻塞直到有数据可写 |
返回值 | pdPASS:从队列读出数据入;errQUEUE_EMPTY:读取失败,因为队列空了 |
6、查询
可以查询队列中有多少个数据、有多少空余空间。函数原型如下:
/*
* 返回队列中可用数据的个数
*/
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );
/*
* 返回队列中可用空间的个数
*/
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );
7、覆盖/偷看
当队列长度为 1 时,可以使用 *xQueueOverwrite()*或 *xQueueOverwriteFromISR()*来覆盖数据。
注意,队列长度必须为 1。当队列满时,这些函数会覆盖里面的数据,这也以为着这些函数不会被阻塞。
函数原型如下:
/* 覆盖队列
* xQueue: 写哪个队列
* pvItemToQueue: 数据地址
* 返回值: pdTRUE表示成功, pdFALSE表示失败
*/
BaseType_t xQueueOverwrite(
QueueHandle_t xQueue,
const void * pvItemToQueue
);
BaseType_t xQueueOverwriteFromISR(
QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);
三、队列的基本使用
本节代码为:13_queue_game。以前使用环形缓冲区传输红外遥控器的数据,本程序改为使用队列,其中实现了在播放音乐《孤勇者》的同时玩挡球板小游戏。
game.c
#include <stdlib.h>
#include <stdio.h>
#include "cmsis_os.h"
#include "FreeRTOS.h" // ARM.FreeRTOS::RTOS:Core
#include "task.h" // ARM.FreeRTOS::RTOS:Core
#include "event_groups.h" // ARM.FreeRTOS::RTOS:Event Groups
#include "semphr.h" // ARM.FreeRTOS::RTOS:Core
#include "draw.h"
#include "resources.h"
#include "driver_lcd.h"
#define NOINVERT false
#define INVERT true
#define sprintf_P sprintf
#define PSTR(a) a
#define PLATFORM_WIDTH 12
#define PLATFORM_HEIGHT 4
#define UPT_MOVE_NONE 0
#define UPT_MOVE_RIGHT 1
#define UPT_MOVE_LEFT 2
#define BLOCK_COLS 32
#define BLOCK_ROWS 5
#define BLOCK_COUNT (BLOCK_COLS * BLOCK_ROWS)
typedef struct{
float x;
float y;
float velX;
float velY;
}s_ball;
static const byte block[] ={
0x07,0x07,0x07,
};
static const byte platform[] ={
0x60,0x70,0x50,0x10,0x30,0xF0,0xF0,0x30,0x10,0x50,0x70,0x60,
};
static const byte ballImg[] ={
0x03,0x03,
};
static const byte clearImg[] ={
0,0,0,0,0,0,0,0,0,0,0,0,
};
static bool btnExit(void);
static bool btnRight(void);
static bool btnLeft(void);
void game1_draw(void);
static byte uptMove;
static s_ball ball;
static bool* blocks;
static byte lives, lives_origin;
static uint score;
static byte platformX;
static uint32_t g_xres, g_yres, g_bpp;
static uint8_t *g_framebuffer;
QueueHandle_t xQueuePlatform;//挡球板队列
/* 挡球板任务 */
static void platform_task(void *params)
{
byte platformXtmp = platformX;
uint8_t dev, data, last_data;
struct input_Data idata;
// Draw platform
draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
while (1)
{
/* 读取红外遥控器 */
//if (0 == IRReceiver_Read(&dev, &data))
if(xQueueReceive(xQueuePlatform,&idata,portMAX_DELAY) == pdPASS)/* 读取队列 */
{
data = idata.data;
if (data == 0x00)
{
data = last_data;
}
if (data == 0xe0) /* Left */
{
btnLeft();
}
if (data == 0x90) /* Right */
{
btnRight();
}
last_data = data;
// Hide platform
draw_bitmap(platformXtmp, g_yres - 8, clearImg, 12, 8, NOINVERT, 0);
draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
// Move platform
if(uptMove == UPT_MOVE_RIGHT)
platformXtmp += 3;
else if(uptMove == UPT_MOVE_LEFT)
platformXtmp -= 3;
uptMove = UPT_MOVE_NONE;
// Make sure platform stays on screen
if(platformXtmp > 250)
platformXtmp = 0;
else if(platformXtmp > g_xres - PLATFORM_WIDTH)
platformXtmp = g_xres - PLATFORM_WIDTH;
// Draw platform
draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
platformX = platformXtmp;
}
}
}
void game1_task(void *params)
{
uint8_t dev, data, last_data;
g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
draw_init();
draw_end();
/* 在游戏刚开始处创建队列 */
xQueuePlatform = xQueueCreate(10,sizeof(struct input_Data));
uptMove = UPT_MOVE_NONE;
ball.x = g_xres / 2;
ball.y = g_yres - 10;
ball.velX = -0.5;
ball.velY = -0.6;
// ball.velX = -1;
// ball.velY = -1.1;
blocks = pvPortMalloc(BLOCK_COUNT);
memset(blocks, 0, BLOCK_COUNT);
lives = lives_origin = 3;
score = 0;
platformX = (g_xres / 2) - (PLATFORM_WIDTH / 2);
xTaskCreate(platform_task, "platform_task", 128, NULL, osPriorityNormal, NULL);
while (1)
{
game1_draw();
//draw_end();
vTaskDelay(50);
}
}
static bool btnExit()
{
vPortFree(blocks);
if(lives == 255)
{
//game1_start();
}
else
{
//pwrmgr_setState(PWR_ACTIVE_DISPLAY, PWR_STATE_NONE);
//animation_start(display_load, ANIM_MOVE_OFF);
vTaskDelete(NULL);
}
return true;
}
static bool btnRight()
{
uptMove = UPT_MOVE_RIGHT;
return false;
}
static bool btnLeft()
{
uptMove = UPT_MOVE_LEFT;
return false;
}
void game1_draw()
{
bool gameEnded = ((score >= BLOCK_COUNT) || (lives == 255));
byte platformXtmp = platformX;
static bool first = 1;
// Move ball
// hide ball
draw_bitmap(ball.x, ball.y, clearImg, 2, 2, NOINVERT, 0);
draw_flushArea(ball.x, ball.y, 2, 8);
// Draw platform
//draw_bitmap(platformX, g_yres - 8, platform, 12, 8, NOINVERT, 0);
//draw_flushArea(platformX, g_yres - 8, 12, 8);
if(!gameEnded)
{
ball.x += ball.velX;
ball.y += ball.velY;
}
bool blockCollide = false;
const float ballX = ball.x;
const byte ballY = ball.y;
// Block collision
byte idx = 0;
LOOP(BLOCK_COLS, x)
{
LOOP(BLOCK_ROWS, y)
{
if(!blocks[idx] && ballX >= x * 4 && ballX < (x * 4) + 4 && ballY >= (y * 4) + 8 && ballY < (y * 4) + 8 + 4)
{
// buzzer_buzz(100, TONE_2KHZ, VOL_UI, PRIO_UI, NULL);
// led_flash(LED_GREEN, 50, 255); // 100ask todo
blocks[idx] = true;
// hide block
draw_bitmap(x * 4, (y * 4) + 8, clearImg, 3, 8, NOINVERT, 0);
draw_flushArea(x * 4, (y * 4) + 8, 3, 8);
blockCollide = true;
score++;
}
idx++;
}
}
// Side wall collision
if(ballX > g_xres - 2)
{
if(ballX > 240)
ball.x = 0;
else
ball.x = g_xres - 2;
ball.velX = -ball.velX;
}
if(ballX < 0)
{
ball.x = 0;
ball.velX = -ball.velX;
}
// Platform collision
bool platformCollision = false;
if(!gameEnded && ballY >= g_yres - PLATFORM_HEIGHT - 2 && ballY < 240 && ballX >= platformX && ballX <= platformX + PLATFORM_WIDTH)
{
platformCollision = true;
// buzzer_buzz(200, TONE_5KHZ, VOL_UI, PRIO_UI, NULL); // 100ask todo
ball.y = g_yres - PLATFORM_HEIGHT - 2;
if(ball.velY > 0)
ball.velY = -ball.velY;
ball.velX = ((float)rand() / (RAND_MAX / 2)) - 1; // -1.0 to 1.0
}
// Top/bottom wall collision
if(!gameEnded && !platformCollision && (ballY > g_yres - 2 || blockCollide))
{
if(ballY > 240)
{
// buzzer_buzz(200, TONE_2_5KHZ, VOL_UI, PRIO_UI, NULL); // 100ask todo
ball.y = 0;
}
else if(!blockCollide)
{
// buzzer_buzz(200, TONE_2KHZ, VOL_UI, PRIO_UI, NULL); // 100ask todo
ball.y = g_yres - 1;
lives--;
}
ball.velY *= -1;
}
// Draw ball
draw_bitmap(ball.x, ball.y, ballImg, 2, 2, NOINVERT, 0);
draw_flushArea(ball.x, ball.y, 2, 8);
// Draw platform
//draw_bitmap(platformX, g_yres - 8, platform, 12, 8, NOINVERT, 0);
//draw_flushArea(platformX, g_yres - 8, 12, 8);
if (first)
{
first = 0;
// Draw blocks
idx = 0;
LOOP(BLOCK_COLS, x)
{
LOOP(BLOCK_ROWS, y)
{
if(!blocks[idx])
{
draw_bitmap(x * 4, (y * 4) + 8, block, 3, 8, NOINVERT, 0);
draw_flushArea(x * 4, (y * 4) + 8, 3, 8);
}
idx++;
}
}
}
// Draw score
char buff[6];
sprintf_P(buff, PSTR("%u"), score);
draw_string(buff, false, 0, 0);
// Draw lives
if(lives != 255)
{
LOOP(lives_origin, i)
{
if (i < lives)
draw_bitmap((g_xres - (3*8)) + (8*i), 1, livesImg, 7, 8, NOINVERT, 0);
else
draw_bitmap((g_xres - (3*8)) + (8*i), 1, clearImg, 7, 8, NOINVERT, 0);
draw_flushArea((g_xres - (3*8)) + (8*i), 1, 7, 8);
}
}
// Got all blocks
if(score >= BLOCK_COUNT)
draw_string_P(PSTR(STR_WIN), false, 50, 32);
// No lives left (255 because overflow)
if(lives == 255)
draw_string_P(PSTR(STR_GAMEOVER), false, 34, 32);
}
freertos.c
#include "driver_led.h"
#include "driver_lcd.h"
#include "driver_mpu6050.h"
#include "driver_timer.h"
#include "driver_ds18b20.h"
#include "driver_dht11.h"
#include "driver_active_buzzer.h"
#include "driver_passive_buzzer.h"
#include "driver_color_led.h"
#include "driver_ir_receiver.h"
#include "driver_ir_sender.h"
#include "driver_light_sensor.h"
#include "driver_ir_obstacle.h"
#include "driver_ultrasonic_sr04.h"
#include "driver_spiflash_w25q64.h"
#include "driver_rotary_encoder.h"
#include "driver_motor.h"
#include "driver_key.h"
#include "driver_uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
static StackType_t g_pucStackOfLightTask[128];
static StaticTask_t g_TCBofLightTask;
static TaskHandle_t xLightTaskHandle;
static StackType_t g_pucStackOfColorTask[128];
static StaticTask_t g_TCBofColorTask;
static TaskHandle_t xColorTaskHandle;
void game1_task(void *params);
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
void StartDefaultTask(void *argument);
void MX_FREERTOS_Init(void);
void MX_FREERTOS_Init(void)
{
LCD_Init();
LCD_Clear();
IRReceiver_Init();
LCD_PrintString(0, 0, "Starting");
/* 创建任务: 声 */
extern void PlayMusic(void *params);
xTaskCreate(PlayMusic, "MusicTask", 128, NULL, osPriorityNo ·rmal, NULL);
xTaskCreate(game1_task, "GameTask", 128, NULL, osPriorityNormal, NULL);
/* 创建任务: 光 */
xLightTaskHandle = xTaskCreateStatic(Led_Test, "LightTask", 128, NULL, osPriorityNormal, g_pucStackOfLightTask, &g_TCBofLightTask);
/* 创建任务: 色 */
xColorTaskHandle = xTaskCreateStatic(ColorLED_Test, "ColorTask", 128, NULL, osPriorityNormal, g_pucStackOfColorTask, &g_TCBofColorTask);
}
void StartDefaultTask(void *argument)
{
uint8_t dev, data;
int len;
int bRunning;
TaskHandle_t xSoundTaskHandle = NULL;
BaseType_t ret;
LCD_Init();
LCD_Clear();
IRReceiver_Init();
LCD_PrintString(0, 0, "Waiting control");
while (1)
{
/* 读取红外遥控器 */
if (0 == IRReceiver_Read(&dev, &data))
{
if (data == 0xa8) /* play */
{
/* 创建播放音乐的任务 */
extern void PlayMusic(void *params);
if (xSoundTaskHandle == NULL)
{
LCD_ClearLine(0, 0);
LCD_PrintString(0, 0, "Create Task");
ret = xTaskCreate(PlayMusic, "SoundTask", 128, NULL, osPriorityNormal+1, &xSoundTaskHandle);
bRunning = 1;
}
else
{
/* 要么suspend要么resume */
if (bRunning)
{
LCD_ClearLine(0, 0);
LCD_PrintString(0, 0, "Suspend Task");
vTaskSuspend(xSoundTaskHandle);
PassiveBuzzer_Control(0); /* 停止蜂鸣器 */
bRunning = 0;
}
else
{
LCD_ClearLine(0, 0);
LCD_PrintString(0, 0, "Resume Task");
vTaskResume(xSoundTaskHandle);
bRunning = 1;
}
}
}
else if (data == 0xa2) /* power */
{
/* 删除播放音乐的任务 */
if (xSoundTaskHandle != NULL)
{
LCD_ClearLine(0, 0);
LCD_PrintString(0, 0, "Delete Task");
vTaskDelete(xSoundTaskHandle);
PassiveBuzzer_Control(0); /* 停止蜂鸣器 */
xSoundTaskHandle = NULL;
}
}
}
}
}