目录
- 0 前言
- 1. 队列实验_多设备玩游戏
- 2 回顾程序
- 3 程序改进
- 3.1 创建队列
- 3.1.1 方法
- 3.1.2 实践
- 3.2 读队列
- 3.2.1 方法
- 3.2.2 实践
- 3.3 写队列
- 3.3.1 方法
- 3.3.2 实践
0 前言
学习视频:
【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS(FreeRTOS教程 基于STM32,以实际项目为导向)】 【精准空降到 00:34】 https://www.bilibili.com/video/BV1Jw411i7Fz/?p=32&share_source=copy_web&vd_source=8af85e60c2df9af1f0fd23935753a933&t=34
参考《FreeRTOS入门与工程实践(基于DshanMCU-103).pdf》
1. 队列实验_多设备玩游戏
本次实验现象:一边听音乐,一边打游戏!
2 回顾程序
这个任务不断读取环形Buffer,很占CPU资源,导致音乐播放很卡顿
我们现在要改造这代码,改成读取队列的形式
3 程序改进
3.1 创建队列
3.1.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 );
}
3.1.2 实践
在哪里创建队列???
- 先定义一个全局指针
QueueHandle_t g_xQueuePlatform; /* 挡球板队列 */
- 再创建队列
/* 创建队列:平台任务从里面读到红外数据,... */
g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data)); // 队列有多少个数据(大概10个),每个数据有多大
// 这里为了精简代码,并没有判断返回值
在13_queue_game\nwatch\typedefs.h路径下定义结构体
struct input_data {
uint32_t dev; //设备
uint32_t val //值
};
3.2 读队列
3.2.1 方法
使用 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:读取失败,因为队列空了。 |
3.2.2 实践
先在static void platform_task(void *params)中定义结构体
struct input_data idata;
然后再读队列
/* 读取红外遥控器 */
//if (0 == IRReceiver_Read(&dev, &data))
if (pdPASS == xQueueReceive(g_xQueuePlatform, &idata, portMAX_DELAY))
{
data = idata.val;
// …… 其他代码都一致
被注释掉的就是读取环形Buff,新的代码就是读队列!
3.3 写队列
3.3.1 方法
更新中…………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………
3.3.2 实践
代码路径:13_queue_game\Drivers\DshanMCU-F103\driver_ir_receiver.c
先声明外部变量
extern QueueHandle_t g_xQueuePlatform; /* 挡球板队列 */
修改后的代码如下:
void IRReceiver_IRQ_Callback(void)
{
uint64_t time;
static uint64_t pre_time = 0;
struct input_data idata; // 定义
/* 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);
/* 写队列 */
idata.dev = 0;
idata.val = 0;
xQueueSend(g_xQueuePlatform, &idata, 0);
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 input_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];
idata.val = datas[2];
xQueueSend(g_xQueuePlatform, &idata, 0);
return 0;
}
编译运行,代码被卡死了,不能正常运行
……
……
……
写队列,我们是在中断里写的,要调用xQueueSendToBackFromISR函数,替换写队列的函数
xQueueSendToBackFromISR(g_xQueuePlatform, &idata, NULL);
开启的两个任务
extern void PlayMusic(void *params);
xTaskCreate(PlayMusic, "MusicTask", 128, NULL, osPriorityNormal, NULL);
xTaskCreate(game1_task, "GameTask", 128, NULL, osPriorityNormal, NULL);
再次烧写运行,没有问题