目录
- 0 前言
- 1 队列实验_分发数据给多个任务(赛车游戏)
- 2 赛车游戏
- 2.1 game.c
- 2.2 注册队列
- 2.3显示汽车
- 2.4隐藏汽车
- 2.5 CarTask
- 2.6 car_game
- 2.7 MX_FREERTOS_Init
- 3 总结
0 前言
学习视频:
【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS(FreeRTOS教程 基于STM32,以实际项目为导向)】 【精准空降到 01:25】 https://www.bilibili.com/video/BV1Jw411i7Fz/?p=38&share_source=copy_web&vd_source=8af85e60c2df9af1f0fd23935753a933&t=85
参考《FreeRTOS入门与工程实践(基于DshanMCU-103).pdf》
1 队列实验_分发数据给多个任务(赛车游戏)
本节源码:在"16_queueset_game_mpu6050"的基础上,改出"17_queue_car_dispatch"
红外遥控器的中断函数解析出按键值后,写入3个队列:3个赛车任务读取其中一个队列得到按键数据。
任务:
写一个简陋版赛车游戏,在红外的中断函数里,我们解析按键值,以前是写一个队列,现在是把键值写三个队列,一共有三个任务,分别控制三辆车。
2 赛车游戏
2.1 game.c
编写game2.c,先测试图像是否能正常显示
/*
* Project: N|Watch
* Author: Zak Kemble, contact@zakkemble.co.uk
* Copyright: (C) 2013 by Zak Kemble
* License: GNU GPL v3 (see License.txt)
* Web: http://blog.zakkemble.co.uk/diy-digital-wristwatch/
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.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"
#include "game2.h"
#define NOINVERT false
#define INVERT true
#define CAR_COUNT 3
#define CAR_WIDTH 12
#define CAR_LENGTH 15
#define ROAD_SPEED 6
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 g_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 clearImg[30] ={0};
static const byte roadMarking[] ={
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
};
#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);
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 *pcar)
{
draw_bitmap(pcar->x, pcar->y, carImg, 15, 16, NOINVERT, 0);
draw_flushArea(pcar->x, pcar->y, 15, 16);
}
static void HideCar(struct car *pcar)
{
draw_bitmap(pcar->x, pcar->y, clearImg, 15, 16, NOINVERT, 0);
draw_flushArea(pcar->x, pcar->y, 15, 16);
}
void car_game(void)
{
int x;
int i, j;
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);
}
}
draw_bitmap(0, 0, carImg, 15, 16, NOINVERT, 0);
draw_flushArea(0, 0, 15, 16);
}
然后,将这个car_game放到初始化代码MX_FREERTOS_Init里
观察现象:
现在,我们就绘制出来了第一辆赛车和三条分割线!
显示三辆汽车
void car_game(void)
{
int x;
int i, j;
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);
}
}
/* 创建3个汽车任务 */
for (i = 0; i < 3; i++)
{
draw_bitmap(g_cars[i].x, g_cars[i].y, carImg, 15, 16, NOINVERT, 0);
draw_flushArea(g_cars[i].x, g_cars[i].y, 15, 16);
}
}
同一个键值,写三个队列,这三个任务都可以分别读各自的队列,得到同一份数据
由驱动程序分发给不同的队列
2.2 注册队列
void RegisterQueueHandle(QueueHandle_t queueHandle)
{
if (g_queue_cnt < 10)
{
g_xQueues[g_queue_cnt] = queueHandle;
g_queue_cnt++;
}
}
static void DispatchKey(struct ir_data *pidata)
{
#if 0
extern QueueHandle_t g_xQueueCar1;
extern QueueHandle_t g_xQueueCar2;
extern QueueHandle_t g_xQueueCar3;
xQueueSendFromISR(g_xQueueCar1, pidata, NULL);
xQueueSendFromISR(g_xQueueCar2, pidata, NULL);
xQueueSendFromISR(g_xQueueCar3, pidata, NULL);
#else
int i;
for (i = 0; i < g_queue_cnt; i++)
{
xQueueSendFromISR(g_xQueues[i], pidata, NULL);
}
#endif
}
这个注册队列,就是把底层驱动程序的某一个数组里,以后等硬件捕获到数据之后,它会自动分发数据
在中断函数里,检测到重复码之后,就会调用这个分发函数,把一个数据,写入多个队列
void IRReceiver_IRQ_Callback(void)
{
uint64_t time;
static uint64_t pre_time = 0;
struct ir_data data;
/* 1. 记录中断发生的时刻 */
time = system_get_ns();
/* 一次按键的最长数据 = 引导码 + 32个数据"1" = 9+4.5+2.25*32 = 85.5ms
* 如果当前中断的时刻, 举例上次中断的时刻超过这个时间, 以前的数据就抛弃
*/
if (time - pre_time > 100000000)
{
g_IRReceiverIRQ_Cnt = 0;
}
pre_time = time;
g_IRReceiverIRQ_Timers[g_IRReceiverIRQ_Cnt] = time;
/* 2. 累计中断次数 */
g_IRReceiverIRQ_Cnt++;
/* 3. 次数达标后, 解析数据, 放入buffer */
if (g_IRReceiverIRQ_Cnt == 4)
{
/* 是否重复码 */
if (isRepeatedKey())
{
/* device: 0, val: 0, 表示重复码 */
//PutKeyToBuf(0);
//PutKeyToBuf(0);
/* 写队列 */
data.dev = 0;
data.val = 0; //如果有重复码,就上报上一次的按键值
DispatchKey(&data);
g_IRReceiverIRQ_Cnt = 0;
}
}
if (g_IRReceiverIRQ_Cnt == 68)
{
IRReceiver_IRQTimes_Parse();
g_IRReceiverIRQ_Cnt = 0;
}
}
或者是解析到实际的键值之后,也写多个队列
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; //修改成 ir_data
/* 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];
idata.val = datas[2];
DispatchKey(&idata);
xQueueSendToBackFromISR(g_xQueueIR, &idata, NULL);
return 0;
}
2.3显示汽车
这是显示一辆汽车的代码:
static void ShowCar(struct car *pcar)
{
draw_bitmap(pcar->x, pcar->y, carImg, 15, 16, NOINVERT, 0);
draw_flushArea(pcar->x, pcar->y, 15, 16);
}
2.4隐藏汽车
这是隐藏一辆汽车的代码:
static void HideCar(struct car *pcar)
{
draw_bitmap(pcar->x, pcar->y, clearImg, 15, 16, NOINVERT, 0);
draw_flushArea(pcar->x, pcar->y, 15, 16);
}
2.5 CarTask
编写CarTask任务函数
static void CarTask(void *params)
{
struct car *pcar = params;
struct ir_data idata;
/* 创建自己的队列 */
QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));
/* 注册队列 */
RegisterQueueHandle(xQueueIR);
/* 显示汽车 */
ShowCar(pcar);
while (1)
{
/* 读取按键值:读队列 */
xQueueReceive(xQueueIR, &idata, portMAX_DELAY);
/* 控制汽车往右移动 */
if (idata.val == pcar->control_key)
{
if (pcar->x < g_xres - CAR_LENGTH)
{
/* 隐藏汽车 */
HideCar(pcar);
/* 调整位置 */
pcar->x += 20;
if (pcar->x > g_xres - CAR_LENGTH)
{
pcar->x = g_xres - CAR_LENGTH;
}
/* 重新显示汽车 */
ShowCar(pcar);
}
}
}
}
2.6 car_game
在car_game里实现所有的操作
void car_game(void)
{
int x;
int i, j;
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);
}
}
/* 创建3个汽车任务 */
#if 0
for (i = 0; i < 3; i++)
{
draw_bitmap(g_cars[i].x, g_cars[i].y, carImg, 15, 16, NOINVERT, 0);
draw_flushArea(g_cars[i].x, g_cars[i].y, 15, 16);
}
#endif
xTaskCreate(CarTask, "car1", 128, &g_cars[0], osPriorityNormal, NULL);
xTaskCreate(CarTask, "car2", 128, &g_cars[1], osPriorityNormal, NULL);
xTaskCreate(CarTask, "car3", 128, &g_cars[2], osPriorityNormal, NULL);
}
最后修改初始化的部分
2.7 MX_FREERTOS_Init
这是初始化部分,我们在初始化中,调用car_game();这一个函数即可!
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
LCD_Init();
LCD_Clear();
IRReceiver_Init();
RotaryEncoder_Init();
LCD_PrintString(0, 0, "Starting");
/* 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(PlayMusic, "MusicTask", 128, NULL, osPriorityNormal, NULL);
car_game(); //使用这个函数来创建任务
/* USER CODE END RTOS_THREADS */
/* USER CODE BEGIN RTOS_EVENTS */
/* add events, ... */
/* USER CODE END RTOS_EVENTS */
}
3 总结
我们在驱动程序里面,想使用同一个输入数据,控制多个任务!
我们可以在这个驱动程序里面,写多个队列,写哪些队列呢?这个决定权可以交给应用程序,应用程序调用一个注册函数,把它的句柄告诉驱动程序,驱动程序会把它记录下来,这样就可以实现分发数据给多个任务~