我的理解:其实队列的本质就是一个数组,但是可以在多任务中读取,写入数据。而环形数组就不能这样子,会出现一些错误。
队列服从先进先出的的顺序,读取和写入顺序是一模一样的。
使用队列传输数据时有两种方法:
⚫ 拷贝:把数据、把变量的值复制进队列里
⚫ 引用:把数据、把变量的地址复制进队列里
FreeRTOS 使用拷贝值的方法,这更简单:
⚫ 局部变量的值可以发送到队列中,后续即使函数退出、局部变量被回收,也
不会影响队列中的数据
⚫ 无需分配 buffer 来保存数据,队列中有 buffer
⚫ 局部变量可以马上再次使用
⚫ 发送任务、接收任务解耦:接收任务不需要知道这数据是谁的、也不需要发
送任务来释放数据
⚫ 如果数据实在太大,你还是可以使用队列传输它的地址
⚫ 队列的空间有 FreeRTOS 内核分配,无需任务操心
⚫ 对于有内存保护功能的系统,如果队列使用引用方法,也就是使用地址,必
须确保双方任务对这个地址都有访问权限。使用拷贝方法时,则无此限制:内核
有足够的权限,把数据复制进队列、再把数据复制出队列。
队列的阻塞访问
只要知道队列的句柄,谁都可以读、写该队列。任务、ISR 都可读、写队列。可以多个
任务读写队列。
任务读写队列时,简单地说:如果读写不成功,则阻塞;可以指定超时时间。口语化地
说,就是可以定个闹钟:如果能读写了就马上进入就绪态,否则就阻塞直到超时。
某个任务读队列时,如果队列没有数据,则该任务可以进入阻塞状态:还可以指定阻塞
的时间。如果队列有数据了,则该阻塞的任务会变为就绪态。如果一直都没有数据,则时间 到之后它也会进入就绪态。
既然读取队列的任务个数没有限制,那么当多个任务读取空队列时,这些任务都会进入
阻塞状态:有多个任务在等待同一个队列的数据。当队列中有数据时,哪个任务会进入就绪 态?
⚫ 优先级最高的任务
⚫ 如果大家的优先级相同,那等待时间最久的任务会进入就绪态
跟读队列类似,一个任务要写队列时,如果队列满了,该任务也可以进入阻塞状态:还 可以指定阻塞的时间。如果队列有空间了,则该阻塞的任务会变为就绪态。如果一直都没有 空间,则时间到之后它也会进入就绪态。
既然写队列的任务个数没有限制,那么当多个任务写"满队列"时,这些任务都会进入阻 塞状态:有多个任务在等待同一个队列的空间。当队列中有空间时,哪个任务会进入就绪态?
⚫ 优先级最高的任务
⚫ 如果大家的优先级相同,那等待时间最久的任务会进入就绪态
队列的API函数
创建队列
QueueHandle_t QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
uxQueueLength 队列长度,最多能存放多少个数据(item)
uxItemSize 每个数据(item)的大小:以字节为单位
返回值
非 0:成功,返回句柄,以后使用句柄来操作队列
NULL:失败,因为内存不足
删除队列的函数为
vQueueDelete(),只能删除使用动态方法创建的队列,它会释放内存。
原型如下:void vQueueDelete( QueueHandle_t xQueue );传入参数为队列句柄
写入队列函数
xQueue 队列句柄
pvItemToQueue 数据指针,这个数据的值会被复制进队列, 复制多大的数据?在创建队列时已经指定了数据大小 .
xTicksToWait如果队列满则无法写入新数据,可以让任务进入阻塞状态,
xTicksToWait 表示阻塞的最大时间(Tick Count)。
如果被设为 0,无法写入数据时函数会立刻返回;
如果被设为 portMAX_DELAY,则会一直阻塞直到有空间可写。
返回值
pdPASS:数据成功写入了队列
errQUEUE_FULL:写入失败,因为队列满了。
/* 等同于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
);
读取队列函数
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:读取失败,因为队列空了
使用示例
注意事项
/*
创建队列的位置会影响程序的运行,特别是在你的 platform_task 中,
你尝试在任务的循环中等待队列消息,但是队列在创建之前就已经被使用了。
具体来说,platform_task 中的循环会永远等待队列消息,因为队列 xQueue1 在 game1_task 中创建,
而不是在 platform_task 中创建。这导致 xQueueReceive 函数一直阻塞,因为没有可用的队列。
由于 portMAX_DELAY 被用于阻塞等待队列,这会导致程序卡死。
要解决这个问题,
你应该在 platform_task 中等待队列消息之前创建队列。
确保在任何尝试使用队列之前都创建了它。
简单来说,你需要确保在 xQueueReceive 调用之前创建了队列 xQueue1。
所以,将队列的创建移到 platform_task 之前的部分,或者在程序初始化时创建队列,而不是在任务中创建。
这样,当 platform_task 开始运行时,队列已经存在,它就可以正确地等待并接收到消息了。
*/
1.创建游戏任务
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
LCD_Init();
LCD_Clear();
IRReceiver_Init();
/* USER CODE END Init */
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* creation of defaultTask */
//defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* 创建任务:游戏 */
//extern void PlayMusic(void *params);
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);
/* USER CODE END RTOS_THREADS */
/* USER CODE BEGIN RTOS_EVENTS */
/* add events, ... */
/* USER CODE END RTOS_EVENTS */
}
2.创建挡球板和旋转编码器队列,创建挡球板任务和旋转编码器任务
void game1_task(void *params)
{
g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
draw_init();
draw_end();
/* 10代表能10个数据长度,sizeof,每个数据的大小*/
//创建队列位置一定在接收任务队列前面不然会卡死程序
xQueue_IR= xQueueCreate(10, sizeof(struct ir_data) );//创建一个挡球板队列
xQueue_rotary=xQueueCreate(10, sizeof(struct rotary_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);//创建一个挡球板任务
xTaskCreate(rotary_task, "rotary_task", 128, NULL, osPriorityNormal, NULL);//创建一个旋转编码器任务
while (1)
{
game1_draw();//画球
//draw_end();
vTaskDelay(50);
}
}
3.旋转编码器和挡球板任务中读取队列
void rotary_task(void *params)
{
byte platformXtmp = platformX;
struct rotary_data rdata;
struct ir_data idata;
int32_t cnt;
int i,left,speed;
while (1)
{
idata.dev = 1;
xQueueReceive( xQueue_rotary,&idata ,portMAX_DELAY);
speed=rdata.speed;
if(speed<0)
{
left=1;
idata.val=UPT_MOVE_LEFT;
speed=-speed;
}
if(speed>0)
{
idata.val=UPT_MOVE_RIGHT;
left=0;
}
if (speed > 100)
cnt = 4;
else if (speed > 50)
cnt = 2;
else
cnt = 1;
/* 写挡球板队列 */
//idata.val = left ? UPT_MOVE_LEFT : UPT_MOVE_RIGHT;
for (i = 0; i < cnt; i++)
{
xQueueSend(xQueue_IR, &idata, NULL);
}
}
}
/*
创建队列的位置会影响程序的运行,特别是在你的 platform_task 中,
你尝试在任务的循环中等待队列消息,但是队列在创建之前就已经被使用了。
具体来说,platform_task 中的循环会永远等待队列消息,因为队列 xQueue1 在 game1_task 中创建,
而不是在 platform_task 中创建。这导致 xQueueReceive 函数一直阻塞,因为没有可用的队列。
由于 portMAX_DELAY 被用于阻塞等待队列,这会导致程序卡死。
要解决这个问题,
你应该在 platform_task 中等待队列消息之前创建队列。
确保在任何尝试使用队列之前都创建了它。
简单来说,你需要确保在 xQueueReceive 调用之前创建了队列 xQueue1。
所以,将队列的创建移到 platform_task 之前的部分,或者在程序初始化时创建队列,而不是在任务中创建。
这样,当 platform_task 开始运行时,队列已经存在,它就可以正确地等待并接收到消息了。
*/
/* 挡球板任务 */
static void platform_task(void *params)
{
/*在这里创建队列会卡死程序 xQueue1=xQueueCreate(10, sizeof(struct ir_data) );//创建一个挡球板队列*/
byte platformXtmp = platformX;
uint8_t dev,data, last_data;
struct ir_data idata;
// Draw platform
draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
int bRunning;
TaskHandle_t xSoundTaskHandle = NULL;
BaseType_t ret;
while (1)
{
/* 读取红外遥控器 */
//if (0 == IRReceiver_Read(&dev, &data))
/*队列读取*/
xQueueReceive( xQueue_IR,&idata ,portMAX_DELAY);
uptMove=idata.val;
if (uptMove == music_play) /* play */
{
/* 创建播放音乐的任务 */
extern void PlayMusic(void *params);
if (xSoundTaskHandle == NULL)
{
ret = xTaskCreate(PlayMusic, "SoundTask", 128, NULL, osPriorityNormal+3, &xSoundTaskHandle);
bRunning = 1;
}
else
{
/* 要么suspend要么resume */
if (bRunning)
{
vTaskSuspend(xSoundTaskHandle);
PassiveBuzzer_Control(0); /* 停止蜂鸣器 */
bRunning = 0;
}
else
{
vTaskResume(xSoundTaskHandle);
bRunning = 1;
}
}
}
else if (uptMove == music_power) /* power */
{
/* 删除播放音乐的任务 */
if (xSoundTaskHandle != NULL)
{
vTaskDelete(xSoundTaskHandle);
PassiveBuzzer_Control(0); /* 停止蜂鸣器 */
xSoundTaskHandle = NULL;
}
}
// 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;
}
}
4.写旋转编码器队列
void RotaryEncoder_IRQ_Callback(void)
{
uint64_t time;
static uint64_t pre_time = 0;
struct rotary_data R_data;
/* 1. 记录中断发生的时刻 */
time = system_get_ns();
/* 上升沿触发: 必定是高电平
* 防抖
*/
mdelay(2);
if (!RotaryEncoder_Get_S1())
return;
/* S1上升沿触发中断
* S2为0表示逆时针转, 为1表示顺时针转
*/
g_speed = (uint64_t)1000000000/(time - pre_time);
if (RotaryEncoder_Get_S2())
{
g_count++;
}
else
{
g_count--;
g_speed = - g_speed;
}
pre_time = time;
R_data.cnt= g_count;
R_data.speed=g_speed;
xQueueSendToBackFromISR(xQueue_rotary , &R_data, NULL);
}
5.写挡球板队列
static int IRReceiver_IRQTimes_Parse(void)
{
uint64_t time;
int i;
int m, n;
unsigned char datas[4];
unsigned char data = 0;
int bits = 0;
int byte = 0;
struct ir_data idata;
/* 1. 判断前导码 : 9ms的低脉冲, 4.5ms高脉冲 */
time = g_IRReceiverIRQ_Timers[1] - g_IRReceiverIRQ_Timers[0];
if (time < 8000000 || time > 10000000)
{
return -1;
}
time = g_IRReceiverIRQ_Timers[2] - g_IRReceiverIRQ_Timers[1];
if (time < 3500000 || time > 55000000)
{
return -1;
}
/* 2. 解析数据 */
for (i = 0; i < 32; i++)
{
m = 3 + i*2;
n = m+1;
time = g_IRReceiverIRQ_Timers[n] - g_IRReceiverIRQ_Timers[m];
data <<= 1;
bits++;
if (time > 1000000)
{
/* 得到了数据1 */
data |= 1;
}
if (bits == 8)
{
datas[byte] = data;
byte++;
data = 0;
bits = 0;
}
}
/* 判断数据正误 */
datas[1] = ~datas[1];
datas[3] = ~datas[3];
if ((datas[0] != datas[1]) || (datas[2] != datas[3]))
{
g_IRReceiverIRQ_Cnt = 0;
return -1;
}
// PutKeyToBuf(datas[0]);
// PutKeyToBuf(datas[2]);
idata.dev = datas[0];
if (datas[2] == 0xe0)
idata.val = UPT_MOVE_LEFT;
else if (datas[2] == 0x90)
idata.val = UPT_MOVE_RIGHT;
else if (datas[2] == 0xa8)
idata.val =music_play;
else if (datas[2] == 0xa2)
idata.val =music_power;
else
idata.val = UPT_MOVE_NONE;
g_last_val = idata.val;
xQueueSendToBackFromISR( xQueue_IR, &idata, NULL);
return 0;
}
效果展示
基于RTOS使用红外遥控器和旋转编码器控制挡球板移动,还能播放音乐。