目录
1 队列集
这部分和上一篇文章相同,先复习一下队列集的知识
1.1 创建队列集
函数原型如下:
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength )
参数 | 说明 |
---|---|
uxQueueLength | 队列集长度,最多能存放多少个数据(队列句柄) |
返回值 | 非0:成功,返回句柄,以后使用句柄来操作队列NULL:失败,因为内存不足 |
1.2 把队列加入队列集
函数原型如下:
BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore,
QueueSetHandle_t xQueueSet );
参数 | 说明 |
---|---|
xQueueOrSemaphore | 队列句柄,这个队列要加入队列集 |
xQueueSet | 队列集句柄 |
返回值 | pdTRUE:成功pdFALSE:失败 |
1.3 读取队列集
函数原型如下:
QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet,
TickType_t const xTicksToWait );
参数 | 说明 |
---|---|
xQueueSet | 队列集句柄 |
xTicksToWait | 如果队列集空则无法读出数据,可以让任务进入阻塞状态,xTicksToWait表示阻塞的最大时间(Tick Count)。如果被设为0,无法读出数据时函数会立刻返回;如果被设为portMAX_DELAY,则会一直阻塞直到有数据可写 |
返回值 | NULL:失败,队列句柄:成功 |
2 修改代码
2.1 创建队列A队列B
先删除红外部分,不需要这部分代码了,我们要把软件控制操作和获取到的硬件数据解耦,通过队列、队列集的方式来访问这些数据,处理数据。
// if (datas[2] == 0xe0) 删除红外部分,直接上报队列
// idata.val = UPT_MOVE_LEFT; //左移
// else if (datas[2] == 0x90)
// idata.val = UPT_MOVE_RIGHT; //右移
// else
// idata.val = UPT_MOVE_NONE;
定义一个红外数据的结构体
struct ir_data {
uint32_t dev;
uint32_t val;
};
定义键值的宏,这里直接复制粘贴了
#define IR_KEY_POWER 0xa2
#define IR_KEY_MENU 0xe2
#define IR_KEY_TEST 0x22
#define IR_KEY_ADD 0x02
#define IR_KEY_RETURN 0xc2
#define IR_KEY_LEFT 0xe0
#define IR_KEY_PLAY 0xa8
#define IR_KEY_RIGHT 0x90
#define IR_KEY_0 0x68
#define IR_KEY_DEC 0x98
#define IR_KEY_C 0xb0
#define IR_KEY_1 0x30
#define IR_KEY_2 0x18
#define IR_KEY_3 0x7a
#define IR_KEY_4 0x10
#define IR_KEY_5 0x38
#define IR_KEY_6 0x5a
#define IR_KEY_7 0x42
#define IR_KEY_8 0x4a
#define IR_KEY_9 0x52
#define IR_KEY_REPEAT 0x00
修改解析数据的代码
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];
xQueueSendToBackFromISR(g_xQueuePlatform, &idata, NULL);
return 0;
}
在初始化时候,创建自己的队列
void IRReceiver_Init(void)
{
/* PA10在MX_GPIO_Init()中已经被配置为双边沿触发, 并使能了中断 */
#if 0
/*Configure GPIO pin : PB10 */
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_EVT_RISING_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
#endif
g_xQueueIR = xQueueCreate(10, sizeof(struct ir_data)); //创建队列
}
现在我们就实现了,解析出数据放到自己的队列里
2.2 创建队列集
static QueueHandle_t g_xQueueSetInput; //输入设备的队列集
#define IR_QUEUE_LEN 10 //队列长度
#define ROTARY_QUEUE_LEN 10 //队列长度
g_xQueueSetInput = xQueueCreateSet(IR_QUEUE_LEN + ROTARY_QUEUE_LEN); //创建队列集
创建队列集之后,将队列加入队列集
/* 创建队列,队列集,输入任务 InputTask */
g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data)); // 动态创建
g_xQueueSetInput = xQueueCreateSet(IR_QUEUE_LEN + ROTARY_QUEUE_LEN); //创建队列集
g_xQueueIR = GetQueueIR(); //获取这两个全局变量,句柄
g_xQueueRotary = GetQueueRotary();
xQueueAddToSet(g_xQueueIR, g_xQueueSetInput); // g_xQueueIR 加入队列集
xQueueAddToSet(g_xQueueRotary, g_xQueueSetInput); // g_xQueueRotary 加入队列集
/* 创建 InputTask 的任务 */
xTaskCreate(InputTask, "InputTask", 128, NULL, osPriorityNormal, NULL);
2.3 InputTask
我们要实现一个框架,底层都写自己的队列,在InputTask读队列集,根据不同队列句柄,分别调用不同的处理函数,处理函数里,都会把原始的硬件数据,转换成游戏的控制数据,写入队列,写入队列之后,挡球板的任务就可以读到数据,就实现控制挡球板了。
函数的任务
- 读队列集,得到有数据的队列句柄
- 读队列句柄得到数据
- 处理红外/旋转编码器/…数据
- 写挡球板队列
编写这个函数:
/**********************************************************************
* 函数名称: InputTask
* 功能描述: 输入任务,检测多个输入设备并调用对应处理函数
* 输入参数: params - 未使用
* 输出参数: 无
* 返 回 值: 无
* 修改日期: 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/08/20 V1.0 LoongFly 创建
***********************************************************************/
static void InputTask(void *params)
{
QueueSetMemberHandle_t xQueueHandle; // 队列集
while(1)
{
/* 1.读队列集,得到有数据的队列句柄 */
xQueueHandle = xQueueSelectFromSet(g_xQueueSetInput, portMAX_DELAY); // portMAX_DELAY得不到数据就一直等待
if (xQueueHandle) // 如果读到了句柄
{
/* 2.读队列句柄得到数据 */
if (xQueueHandle == g_xQueueIR) //红外的句柄
{
/* 3.处理红外数据 */
ProcessIRData();
}
else if (xQueueHandle == g_xQueueRotary) //旋转编码器的句柄
{
/* 3.处理旋转编码器数据 */
ProcessRotaryData();
}
/* 4.写挡球板队列 在函数里实现了*/
}
}
}
2.3.1 ProcessIRData 解析红外数据
/**********************************************************************
* 函数名称: ProcessIRData
* 功能描述: 读取红外遥控器键值并转换为游戏控制键,写入挡球板队列
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 无
* 修改日期: 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/08/20 V1.0 LoongFly 创建
***********************************************************************/
static void ProcessIRData(void)
{
struct ir_data irdata;
static struct input_data input;
xQueueReceive(g_xQueueIR, &irdata, 0); //读队列,不阻塞
if (irdata.val == IR_KEY_LEFT) //左移
{
input.dev = irdata.dev;
input.val = UPT_MOVE_LEFT;
}
else if (irdata.val == IR_KEY_RIGHT) //右移
{
input.dev = irdata.dev;
input.val = UPT_MOVE_RIGHT;
}
else if (irdata.val == IR_KEY_REPEAT) //重复码
{
; //保持不变
}
else
{
input.dev = irdata.dev;
input.val = UPT_MOVE_NONE;
}
/* 写挡球板队列 */
xQueueSend(g_xQueuePlatform, &input, 0);
}
2.3.2 ProcessRotaryData 解析旋转编码器数据
/**********************************************************************
* 函数名称: ProcessRotaryData
* 功能描述: 读取旋转编码器数据并转换为游戏控制键,写入挡球板队列
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 无
* 修改日期: 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/08/20 V1.0 LoongFly 创建
***********************************************************************/
static void ProcessRotaryData(void)
{
struct rotary_data rdata;
struct input_data idata;
int left;
int i, cnt;
/* 1.读取旋转编码器队列 */
xQueueReceive(g_xQueueRotary,&rdata, 0); //读到的数据保存到rdata结构体里,无需等待
/* 2.处理队列 */
/* 判断速度: 负数表示向左转动, 正数表示向右转动 */
if (rdata.speed < 0)
{
left = 1;
rdata.speed = 0 - rdata.speed;
}
else
{
left = 0;
}
if (rdata.speed > 50)
cnt = 5;
else if (rdata.speed > 40)
cnt = 4;
else if (rdata.speed > 30)
cnt = 3;
else if (rdata.speed > 20)
cnt = 2;
else
cnt = 1;
/* 3.写入挡球板队列 */
idata.dev = 1;
idata.val = left ? UPT_MOVE_RIGHT : UPT_MOVE_LEFT; //+++++++++++++++++++++++
for (i = 0; i < cnt; i ++) //速度快就多写几遍
{
xQueueSend(g_xQueuePlatform, &idata, 0); //这里要么成功\要么失败,不能阻塞
}
}
包含需要的头文件,最后会报错
01_freertos_template\01_freertos_template.axf: Error: L6218E: Undefined symbol xQueueAddToSet (referred from game1.o).
01_freertos_template\01_freertos_template.axf: Error: L6218E: Undefined symbol xQueueCreateSet (referred from game1.o).
01_freertos_template\01_freertos_template.axf: Error: L6218E: Undefined symbol xQueueSelectFromSet (referred from game1.o).
Not enough information to list image symbols.
Not enough information to list load addresses in the image map.
我们在15_queueset_game\Core\Inc\FreeRTOSConfig.h加一句
#define configUSE_QUEUE_SETS 1 //使用队列集
烧写程序之后,挡球板不见了
这里是内存不够用了
配置8000
我们现在就实现了用队列集改进程序框架
3 总结
改进程序框架
本节源码:在"14_queue_game_multi_input"的基础上,改出"15_queueset_game",具备更好的程序框架。
修改“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。