文章目录
- 一、使用队列实现多设备输入
- 1、增加旋转编码器
- 2、使用队列集执行任务
- 3、增加姿态控制(使用MPU6050控制挡球板)
- 二、队列实验_分发数据给多个任务(赛车游戏)
- 三、传输大块数据
- 四、示例:邮箱(Mailbox)
- 五、队列集
- 1、创建队列集
- 2、把队列加入队列集
- 3、读取队列集
一、使用队列实现多设备输入
1、增加旋转编码器
本节代码为:“14_queue_game_multi_input”。在"13_queue_game"的基础上,增加旋转编码控制功能
实验目的:使用红外遥控器、旋转编码器玩游戏。
实现方案:
- 游戏任务:读取队列A获得控制信息,用来控制游戏
- 红外遥控器驱动:在中断函数里解析出按键后,写队列A
- 旋转编码器:
- 它的中断函数里解析出旋转编码器的状态,写队列B;
- 它的任务函数里,读取队列B,构造好数据后写队列A
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;//挡球板队列(红外中断写队列 游戏创建队列 挡球板读取队列)
QueueHandle_t xQueueRotary; //旋转编码器队列(旋转编码器中断写队列 游戏创建队列和任务 任务是读取中断队列 并再次写解析数据队列 挡球板读取队列)
static uint8_t uchQueueRotaryBuf[10 * sizeof(struct rotary_Data)]; //旋转编码器数据存放
static StaticQueue_t pxQueueBuf; //旋转编码器队列数据结构
/* 挡球板任务 */
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))
xQueueReceive(xQueuePlatform,&idata,portMAX_DELAY);/* 读取队列 */
uptMove = idata.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;
}
}
static void rotary_task(void *params)
{
struct rotary_Data rdata;
struct input_Data idata;
int left;
int cnt;
while(1)
{
/* 读取旋转编码器中断队列 */
xQueueReceive(xQueueRotary,&rdata,portMAX_DELAY);
/* 处理数据 */
/* 速度判断,负数表示向左,正数表示向右 */
if(rdata.speed < 0)
{
left = 1;
rdata.speed = 0 - rdata.speed;
}
else
left = 0;
// cnt = rdata.speed / 10;
// if (!cnt)
// cnt = 1;
if(rdata.speed>100)//根据速度决定循环次数
cnt = 5;
else if(rdata.speed>50)
cnt = 2;
else
cnt = 1;
/* 写挡球板队列 挡球板根据旋转编码器数据做出调整 */
idata.dev = 1;//设置旋转编码器的输入设备号为1
idata.data = left?UPT_MOVE_LEFT:UPT_MOVE_RIGHT;
for(int i=0;i<cnt;i++)
{
xQueueSend(xQueuePlatform,&idata,0);
}
}
}
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));//红外遥控器队列
xQueueRotary = xQueueCreateStatic(10,sizeof(struct rotary_Data),uchQueueRotaryBuf,&pxQueueBuf);//旋转编码器队列
/* 创建旋转编码器解析任务 */
xTaskCreate(rotary_task, "rotary_task", 128, NULL, osPriorityNormal, NULL);
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);
}
freerots.c
这里与上个实验主要不同的就是游戏任务game_task中增加了旋转编码器队列,而freertos.c中只是进行调用,所以这里的freertos.c与上个实验一样,防止代码重复影响观看,这里就不展开。
2、使用队列集执行任务
修改“Core\Inc\FreeRTOSConfig.h“,增加:
/* USER CODE BEGIN Includes */
#define configUSE_QUEUE_SETS 1
/* Section where include file can be added */
/* USER CODE END Includes */
在STM32CubeMX里把堆调大,比如8000:
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"
#include "driver_ir_receiver.h"
#include "driver_rotary_encoder.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;
static QueueSetHandle_t xQueueSetInput;/* 输入设备的队列集 */
static QueueHandle_t xQueuePlatform;
static QueueHandle_t xQueueIR;
static QueueHandle_t xQueueRotary;
/* 挡球板任务 */
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))
xQueueReceive(xQueuePlatform,&idata,portMAX_DELAY);/* 读取队列 */
uptMove = idata.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;
}
}
static void ProcessIRData(void)
{
struct ir_Data idata;
static struct input_Data input;
xQueueReceive(xQueueIR,&idata,0);
if(idata.data == IR_KEY_LEFT)
{
input.dev = idata.dev;
input.data= UPT_MOVE_LEFT;
}
else if(idata.data == IR_KEY_RIGHT)
{
input.dev = idata.dev;
input.data= UPT_MOVE_RIGHT;
}
else if(idata.data == IR_KEY_REPEAT)
{
/* 重复按下时保持不变 */
}
else
{
input.dev = idata.dev;
input.data= UPT_MOVE_NONE;
}
/* 写挡球板队列 */
xQueueSend(xQueuePlatform,&input,0);
}
static void ProcessRotaryData(void)
{
struct rotary_Data rdata;
struct input_Data idata;
int left;
int i,cnt;
/* 读取旋转编码器中断队列 */
xQueueReceive(xQueueRotary, &rdata, 0);
/* 处理数据 */
/* 速度判断,负数表示向左,正数表示向右 */
if(rdata.speed < 0)
{
left = 1;
rdata.speed = 0 - rdata.speed;
}
else
left = 0;
// cnt = rdata.speed / 10;
// if (!cnt)
// cnt = 1;
if(rdata.speed>100)//根据速度决定循环次数
cnt = 4;
else if(rdata.speed>50)
cnt = 2;
else
cnt = 1;
/* 写挡球板队列 挡球板根据旋转编码器数据做出调整 */
idata.dev = 1;//设置旋转编码器的输入设备号为1
idata.data = left ? UPT_MOVE_LEFT : UPT_MOVE_RIGHT;
for(int i=0;i<cnt;i++)
{
xQueueSend(xQueuePlatform, &idata, 0);
}
}
static void Inputtask(void *params)
{
QueueSetMemberHandle_t xQueueHandle;
while(1)
{
/* 读队列集 得到有数据的队列句柄*/
xQueueHandle = xQueueSelectFromSet(xQueueSetInput, portMAX_DELAY);
if(xQueueHandle)
{
/* 读取队列 得到数据 处理数据*/
if(xQueueHandle == xQueueIR)
{
ProcessIRData();
}
else if(xQueueHandle == xQueueRotary)
{
ProcessRotaryData();
}
/* 写挡球板队列 */
}
}
}
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));
/* 创建队列集 */
xQueueSetInput = xQueueCreateSet(IR_QUEUE_LEN + ROTARY_QUEUE_LEN);
/* 将创建的队列加入队列集 */
xQueueIR = GetQueueIR();
xQueueRotary = GetQueueRotary();
xQueueAddToSet(xQueueIR,xQueueSetInput);
xQueueAddToSet(xQueueRotary, xQueueSetInput);
/* 创建输入任务 */
xTaskCreate(Inputtask, "Inputtask", 128, NULL, osPriorityNormal, NULL);
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);
}
freerots.c
这里与上个实验主要不同的就是游戏任务game_task中增加了旋转编码器队列,而freertos.c中只是进行调用,所以这里的freertos.c与上个实验一样,防止代码重复影响观看,这里就不展开。
3、增加姿态控制(使用MPU6050控制挡球板)
本节源码:在"15_queueset_game"的基础上,改出"16_queueset_game_mpu6050",支持6轴传感器,使用姿态控制玩游戏。
关于MPU6050的了解可以参考:https://zhuanlan.zhihu.com/p/30621372
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"
#include "driver_ir_receiver.h"
#include "driver_rotary_encoder.h"
#include "driver_mpu6050.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;
static QueueSetHandle_t xQueueSetInput;/* 输入设备的队列集 */
static QueueHandle_t xQueuePlatform;
static QueueHandle_t xQueueIR;
static QueueHandle_t xQueueRotary;
static QueueHandle_t xQueueMPU6050;//MPU6050队列
/* 挡球板任务 */
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))
xQueueReceive(xQueuePlatform,&idata,portMAX_DELAY);/* 读取队列 */
uptMove = idata.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;
}
}
static void ProcessIRData(void)
{
struct ir_Data idata;
static struct input_Data input;
xQueueReceive(xQueueIR,&idata,0);
if(idata.data == IR_KEY_LEFT)
{
input.dev = idata.dev;
input.data= UPT_MOVE_LEFT;
}
else if(idata.data == IR_KEY_RIGHT)
{
input.dev = idata.dev;
input.data= UPT_MOVE_RIGHT;
}
else if(idata.data == IR_KEY_REPEAT)
{
/* 重复按下时保持不变 */
}
else
{
input.dev = idata.dev;
input.data= UPT_MOVE_NONE;
}
/* 写挡球板队列 */
xQueueSend(xQueuePlatform,&input,0);
}
static void ProcessRotaryData(void)
{
struct rotary_Data rdata;
struct input_Data idata;
int left;
int i,cnt;
/* 读取旋转编码器中断队列 */
xQueueReceive(xQueueRotary, &rdata, 0);
/* 处理数据 */
/* 速度判断,负数表示向左,正数表示向右 */
if(rdata.speed < 0)
{
left = 1;
rdata.speed = 0 - rdata.speed;
}
else
left = 0;
// cnt = rdata.speed / 10;
// if (!cnt)
// cnt = 1;
if(rdata.speed>100)//根据速度决定循环次数
cnt = 4;
else if(rdata.speed>50)
cnt = 2;
else
cnt = 1;
/* 写挡球板队列 挡球板根据旋转编码器数据做出调整 */
idata.dev = 1;//设置旋转编码器的输入设备号为1
idata.data = left ? UPT_MOVE_LEFT : UPT_MOVE_RIGHT;
for(int i=0;i<cnt;i++)
{
xQueueSend(xQueuePlatform, &idata, 0);
}
}
static void ProcessMPU6050Data(void)
{
struct mpu6050_data mdata;//mpu6050 x方向的数据
struct input_Data idata;
/* 读取MPU6050中断队列 获得数据 */
xQueueReceive(xQueueMPU6050, &mdata, 0);
/* 处理数据 */
/* 方向判断,大于90表示向左,小于90表示向右 */
if(mdata.angle_x > 90)
{
idata.data = UPT_MOVE_LEFT;
}
else if(mdata.angle_x < 90)
{
idata.data = UPT_MOVE_RIGHT;
}
else
{
idata.data = UPT_MOVE_NONE;
}
// cnt = rdata.speed / 10;
// if (!cnt)
// cnt = 1;
/* 写挡球板队列 挡球板根据MPU6050数据做出调整 */
idata.dev = 2;//设置MPU6050的输入设备号为2
xQueueSend(xQueuePlatform, &idata, 0);//读取完数据将数据放松给挡球板队列
}
static void Inputtask(void *params)
{
QueueSetMemberHandle_t xQueueHandle;
while(1)
{
/* 读队列集 得到有数据的队列句柄*/
xQueueHandle = xQueueSelectFromSet(xQueueSetInput, portMAX_DELAY);
if(xQueueHandle)
{
/* 读取队列 得到数据 处理数据*/
if(xQueueHandle == xQueueIR)
{
ProcessIRData();
}
else if(xQueueHandle == xQueueRotary)
{
ProcessRotaryData();
}
else if(xQueueHandle == xQueueMPU6050)
{
ProcessMPU6050Data();
}
/* 写挡球板队列 */
}
}
}
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));
/* 创建队列集 */
xQueueSetInput = xQueueCreateSet(IR_QUEUE_LEN + ROTARY_QUEUE_LEN + MPU6050_QUEUE_LEN);
/* 将创建的队列加入队列集 */
xQueueIR = GetQueueIR();
xQueueRotary = GetQueueRotary();
xQueueMPU6050 = GetQueueMPU6050();//获得mpu6050的句柄
xQueueAddToSet(xQueueIR,xQueueSetInput);
xQueueAddToSet(xQueueRotary, xQueueSetInput);
xQueueAddToSet(xQueueMPU6050,xQueueSetInput);//加入队列集
/* 这里让队列先加入队列集再创建任务是因为mpu6050每隔50ms就会运行一次 如果让其执行完再加入队列集会导致队列满了 */
xTaskCreate(MPU6050_Task, "MPU6050Task", 128, NULL, osPriorityNormal, NULL);
/* 创建输入任务 */
xTaskCreate(Inputtask, "Inputtask", 128, NULL, osPriorityNormal, NULL);
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();
MPU6050_Init();
IRReceiver_Init();
RotaryEncoder_Init();
LCD_PrintString(0, 0, "Starting");
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
extern void PlayMusic(void *params);
extern void MPU6050_Task(void *params);
xTaskCreate(PlayMusic, "MusicTask", 128, NULL, osPriorityNormal, NULL);
xTaskCreate(game1_task, "GameTask", 128, NULL, osPriorityNormal, NULL);
xTaskCreate(MPU6050_Task, "MPU6050Task", 128, NULL, osPriorityNormal, NULL);
void StartDefaultTask(void *argument)
{
uint8_t dev, data;
int len;
int bRunning;
TaskHandle_t xSoundTaskHandle = NULL;
BaseType_t ret;
vTaskDelete(NULL);
LCD_Init();
LCD_Clear();
MPU6050_Init();
RotaryEncoder_Init();
IRReceiver_Init();
LCD_PrintString(0, 0, "Waiting control");
while(1)
{
MPU6050_Test();
}
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
{
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;
}
}
}
}
}
二、队列实验_分发数据给多个任务(赛车游戏)
本节源码:在"16_queueset_game_mpu6050"的基础上,改出"17_queue_car_dispatch"
红外遥控器的中断函数解析出按键值后,写入3个队列:3个赛车任务读取其中一个队列得到按键数据。
game2.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"
#include "driver_ir_receiver.h"
#include "driver_rotary_encoder.h"
#include "driver_mpu6050.h"
#define CAR_COUNT 3
#define CAR_WIDTH 12
#define CAR_LENGTH 15
#define ROAD_SPEED 6
#define NOINVERT false
#define INVERT true
static uint32_t g_xres, g_yres, g_bpp;
static uint8_t *g_framebuffer;
struct car
{
int x;
int y;
int control_key;
};
struct car cars[3] =
{
{0,0,IR_KEY_1},
{0,17,IR_KEY_2},
{0,34,IR_KEY_3},
};
static const byte carImg[]={
0x40,0xF8,0xEC,0x2C,0x2C,0x38,0xF0,0x10,0xD0,0x30,0xE8,0x4C,0x4C,0x9C,0xF0,
0x02,0x1F,0x37,0x34,0x34,0x1C,0x0F,0x08,0x0B,0x0C,0x17,0x32,0x32,0x39,0x0F,
};
static const byte roadMarking[]={
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
};
static const byte clearImg[30]={0};
#if 0
void car_test(void)
{
g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
draw_init();
draw_end();
draw_bitmap(0, 0, carImg, 15, 16, NOINVERT, 0);//x坐标 y坐标 位图 位图宽 位图高 位图是否翻转 最后一般为0(一行15个字节即位图宽为15 高度为2即表示16个像素 则位图高为16)
draw_flushArea(0, 0, 15, 16);
draw_bitmap(0, 16, roadMarking, 8, 1, NOINVERT, 0);
draw_flushArea(0, 16, 8, 1);
while(1);
}
#endif
static void ShowCar(struct car *car)
{
draw_bitmap(car->x, car->y, carImg, 15, 16, NOINVERT, 0);
draw_flushArea(car->x, car->y, 15, 16);
}
static void HideCar(struct car *car)
{
draw_bitmap(car->x, car->y, clearImg, 15, 16, NOINVERT, 0);
draw_flushArea(car->x, car->y, 15, 16);
}
static void car_task(void *params)
{
struct car *car = params;
struct ir_Data idata;
/* 创建自己的队列 */
QueueHandle_t xQueueIR = xQueueCreate(10,sizeof(struct ir_Data));
/* 注册队列 */
RegisterQueueHandle(xQueueIR);
/* 初始化小车 */
ShowCar(car);
while(1)
{
/* 读取按键值 */
xQueueReceive(xQueueIR,&idata,portMAX_DELAY);
/* 控制汽车往右移动 */
if(idata.data == car->control_key)
{
if(car->x < g_xres - CAR_LENGTH)
{
/* 隐藏汽车 */
HideCar(car);
/* 调整位置 */
car->x += 20;//每次按下右移20个单位
if(car->x > g_xres - CAR_LENGTH)//超过屏幕分辨率(128)
car->x = g_xres - CAR_LENGTH;//到达最大位置处
/* 重新显示汽车 */
ShowCar(car);
}
}
}
}
void car_game(void)
{
int i,j;
int x;
g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
draw_init();
draw_end();
/* 绘制路标 */
for(i=0;i<3;i++)
{
for(j=0;j<8;j++)
{
draw_bitmap(16*j, 16+17*i, roadMarking, 8, 1, NOINVERT, 0);
draw_flushArea(16*j, 16+17*i, 8, 1);
}
}
#if 0
/* 显示三辆小车 */
for(i=0;i<3;i++)
{
draw_bitmap(cars[i].x, cars[i].y, carImg, 15, 16, NOINVERT, 0);
draw_flushArea(cars[i].x, cars[i].y, 15, 16);
}
#endif
xTaskCreate(car_task, "car1task", 128, &cars[0], osPriorityNormal, NULL);
xTaskCreate(car_task, "car2task", 128, &cars[1], osPriorityNormal, NULL);
xTaskCreate(car_task, "car3task", 128, &cars[2], osPriorityNormal, NULL);
}
三、传输大块数据
FreeRTOS的队列使用拷贝传输,也就是要传输uint32_t时,把4字节的数据拷贝进队列; 要传输一个8字节的结构体时,把8字节的数据拷贝进队列。
如果要传输1000字节的结构体呢?写队列时拷贝1000字节,读队列时再拷贝1000字节? 不建议这么做,影响效率!
这时候,我们要传输的是这个巨大结构体的地址:把它的地址写入队列,对方从队列得到这个地址,使用地址去访问那1000字节的数据。
使用地址来间接传输数据时,这些数据放在RAM里,对于这块RAM,要保证这几点:
- RAM的所有者、操作者,必须清晰明了
这块内存,就被称为"共享内存"。要确保不能同时修改RAM。比如,在写队列之前只有由发送者修改这块RAM,在读队列之后只能由接收者访问这块RAM。
- RAM要保持可用
这块RAM应该是全局变量,或者是动态分配的内存。对于动然分配的内存,要确保它不能提前释放:要等到接收者用完后再释放。另外,不能是局部变量。
FreeRTOS_10_queue_bigtransfer程序会创建一个队列,然后创建1个发送任务、1个接收任务:
- 创建的队列:长度为1,用来传输"char *"指针
- 发送任务优先级为1,在字符数组中写好数据后,把它的地址写入队列
- 接收任务优先级为2,读队列得到"char *"值,把它打印出来
这个程序故意设置接收任务的优先级更高,在它访问数组的过程中,接收任务无法执行、无法写这个数组。
main函数中创建了队列、创建了发送任务、接收任务,代码如下:
/* 定义一个字符数组 */
static char pcBuffer[100];
/* vSenderTask被用来创建2个任务,用于写队列
* vReceiverTask被用来创建1个任务,用于读队列
*/
static void vSenderTask( void *pvParameters );
static void vReceiverTask( void *pvParameters );
/*-----------------------------------------------------------*/
/* 队列句柄, 创建队列时会设置这个变量 */
QueueHandle_t xQueue;
int main( void )
{
prvSetupHardware();
/* 创建队列: 长度为1,数据大小为4字节(存放一个char指针) */
xQueue = xQueueCreate( 1, sizeof(char *) );
if( xQueue != NULL )
{
/* 创建1个任务用于写队列
* 任务函数会连续执行,构造buffer数据,把buffer地址写入队列
* 优先级为1
*/
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 1, NULL );
/* 创建1个任务用于读队列
* 优先级为2, 高于上面的两个任务
* 这意味着读队列得到buffer地址后,本任务使用buffer时不会被打断
*/
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
/* 启动调度器 */
vTaskStartScheduler();
}
else
{
/* 无法创建队列 */
}
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
发送任务的函数中,现在全局大数组pcBuffer中构造数据,然后把它的地址写入队列,代码如下:
static void vSenderTask( void *pvParameters )
{
BaseType_t xStatus;
static int cnt = 0;
char *buffer;
/* 无限循环 */
for( ;; )
{
sprintf(pcBuffer, "www.100ask.net Msg %d\r\n", cnt++);
buffer = pcBuffer; // buffer变量等于数组的地址, 下面要把这个地址写入队列
/* 写队列
* xQueue: 写哪个队列
* pvParameters: 写什么数据? 传入数据的地址, 会从这个地址把数据复制进队列
* 0: 如果队列满的话, 即刻返回
*/
xStatus = xQueueSendToBack( xQueue, &buffer, 0 ); /* 只需要写入4字节, 无需写入整个buffer */
if( xStatus != pdPASS )
{
printf( "Could not send to the queue.\r\n" );
}
}
}
接收任务的函数中,读取队列、得到buffer的地址、打印,代码如下:
static void vReceiverTask( void *pvParameters )
{
/* 读取队列时, 用这个变量来存放数据 */
char *buffer;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
BaseType_t xStatus;
/* 无限循环 */
for( ;; )
{
/* 读队列
* xQueue: 读哪个队列
* &xReceivedStructure: 读到的数据复制到这个地址
* xTicksToWait: 没有数据就阻塞一会
*/
xStatus = xQueueReceive( xQueue, &buffer, xTicksToWait); /* 得到buffer地址,只是4字节 */
if( xStatus == pdPASS )
{
/* 读到了数据 */
printf("Get: %s", buffer);
}
else
{
/* 没读到数据 */
printf( "Could not receive from the queue.\r\n" );
}
}
}
运行结果如下图所示:
四、示例:邮箱(Mailbox)
本节代码为:FreeRTOS_11_queue_mailbox。
FreeRTOS的邮箱概念跟别的RTOS不一样,这里的邮箱称为"橱窗"也许更恰当:
-
它是一个队列,队列长度只有1
-
写邮箱:新数据覆盖旧数据,在任务中使用xQueueOverwrite(),在中断中 使用xQueueOverwriteFromISR()。
既然是覆盖,那么无论邮箱中是否有数据,这些函数总能成功写入数据。
-
读邮箱:读数据时,数据不会被移除;在任务中使用xQueuePeek(),在中 断中使用xQueuePeekFromISR()。
这意味着,第一次调用时会因为无数据而阻塞,一旦曾经写入数据,以后读邮箱 时总能成功。
main函数中创建了队列(队列长度为1)、创建了发送任务、接收任务:
- 发送任务的优先级为2,它先执行
- 接收任务的优先级为1
代码如下:
/* 队列句柄, 创建队列时会设置这个变量 */
QueueHandle_t xQueue;
int main( void )
{
prvSetupHardware();
/* 创建队列: 长度为1,数据大小为4字节(存放一个char指针) */
xQueue = xQueueCreate( 1, sizeof(uint32_t) );
if( xQueue != NULL )
{
/* 创建1个任务用于写队列
* 任务函数会连续执行,构造buffer数据,把buffer地址写入队列
* 优先级为2
*/
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
/* 创建1个任务用于读队列
* 优先级为1
百问网
*/
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
/* 启动调度器 */
vTaskStartScheduler();
}
else
{
/* 无法创建队列 */
}
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
发送任务、接收任务的代码和执行流程如下:
- A:发送任务先执行,马上阻塞
- BC:接收任务执行,这是邮箱无数据,打印"Could not …"。在发送任务阻塞过程中,接收任务多次执行、多次打印。
- D:发送任务从阻塞状态退出,立刻执行、写队列
- E:发送任务再次阻塞
- FG、HI、……:接收任务不断"偷看"邮箱,得到同一个数据,打印出多个"Get: 0"
- J:发送任务从阻塞状态退出,立刻执行、覆盖队列,写入1
- K:发送任务再次阻塞
- LM、……:接收任务不断"偷看"邮箱,得到同一个数据,打印出多个"Get: 1"
运行结果如下图所示:
五、队列集
假设有2个输入设备:红外遥控器、旋转编码器,它们的驱动程序应该专注于“产生硬件数据”,不应该跟“业务有任何联系”。比如:红外遥控器驱动程序里,它只应该把键值记录下来、写入某个队列,它不应该把键值转换为游戏的控制键。在红外遥控器的驱动程序里,不应该有游戏相关的代码,这样,切换使用场景时,这个驱动程序还可以继续使用。
把红外遥控器的按键转换为游戏的控制键,应该在游戏的任务里实现。
要支持多个输入设备时,我们需要实现一个“InputTask”,它读取各个设备的队列,得 到数据后再分别转换为游戏的控制键。
InputTask 如何及时读取到多个队列的数据?要使用队列集。
队列集的本质也是队列,只不过里面存放的是“队列句柄”。使用过程如下:
a. 创建队列A,它的长度是n1
b. 创建队列B,它的长度是n2
c. 创建队列集S,它的长度是“n1+n2”
d. 把队列A、B加入队列集S
e. 这样,写队列A的时候,会顺便把队列A的句柄写入队列集S
f. 这样,写队列B的时候,会顺便把队列B的句柄写入队列集S
g. InputTask 先读取队列集 S,它的返回值是一个队列句柄,这样就可以知道哪个队列有数据了;然后InputTask再读取这个队列句柄得到数据。
1、创建队列集
函数原型如下:
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength ) ;
参数 | 说明 |
---|---|
uxQueueLength | 队列集长度,最多能存放多少个数据(队列句柄) |
返回值 | 非0:成功,返回句柄,以后使用句柄来操作队列;NULL:失败,因为内存不足 |
2、把队列加入队列集
函数原型如下:
BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore,
QueueSetHandle_t xQueueSet );
参数 | 说明 |
---|---|
xQueueOrSemaphore | 队列句柄,这个队列要加入队列集 |
xQueueSet | 队列集句柄 |
返回值 | pdTRUE:成功;pdFALSE:失败 |
3、读取队列集
函数原型如下:
QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet,
TickType_t const xTicksToWait );
参数 | 说明 |
---|---|
xQueueSet | 队列集句柄 |
xTicksToWait | 如果队列集空则无法读出数据,可以让任务进入阻塞状态,xTicksToWait 表示阻塞的最大时间(Tick Count)。如果被设为0,无法读出数据时函数会立刻返回;如果被设为portMAX_DELAY,则会一直阻塞直到有数据可写 |
返回值 | NULL:失败;队列句柄:成功 |