目录
- [0 前言](#0 前言)
- [1 任务](#1 任务)
- [2 code](#2 code)
-
- [2.1 创建队列](#2.1 创建队列)
- [2.2 写队列](#2.2 写队列)
- [2.3 创建任务](#2.3 创建任务)
- [3 勘误](#3 勘误)
0 前言
学习视频:
【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS(FreeRTOS教程 基于STM32,以实际项目为导向)】 【精准空降到 03:20】 https://www.bilibili.com/video/BV1Jw411i7Fz/?p=33\&share_source=copy_web\&vd_source=8af85e60c2df9af1f0fd23935753a933\&t=200
参考《FreeRTOS入门与工程实践(基于DshanMCU-103).pdf》
https://rtos.100ask.net/zh/FreeRTOS/DShanMCU-F103/chapter11.html
1 任务
1.1 本节源码
在"03_参考的源码/DshanMCU-F103/02_nwatch_game_freertos.7z"的基础上
- "14_queue_game_multi_input": 在"13_queue_game"的基础上,增加旋转编码控制功能
1.2实验目的
使用红外遥控器、旋转编码器玩游戏。
1.3实现方案
- 游戏任务:读取队列A获得控制信息,用来控制游戏
- 红外遥控器驱动:在中断函数里解析出按键后,写队列A
- 旋转编码器:
- 它的中断函数里解析出旋转编码器的状态,写队列B;
- 它的任务函数里,读取队列B,构造好数据后写队列A
2 code
2.1 创建队列
在game1.c的void game1_task(void *params)中添加
c
/* 创建队列:平台任务从里面读到旋转编码器数据,... */
g_xQueueRotary = xQueueCreateStatic(
10, // 长度 10 随意给的
sizeof(struct rotary_data), // 大小
g_ucQueueRotaryBuf, // 保存数据的buffer
&g_xQueueRotaryStaticStruct // 结构体的地址
);// 静态创建
定义结构体
c
struct rotary_data {
int32_t cnt; //设备
int32_t speed; //值
uint8_t dir; //方向
};
定义队列
c
QueueHandle_t g_xQueueRotary; /* 旋转编码器队列 */
static uint8_t g_ucQueueRotaryBuf[10*sizeof(struct rotary_data)]; //存放10个rotary_data结构体这么大小的buf
static StaticQueue_t g_xQueueRotaryStaticStruct; //队列的结构体
后面还需要创建旋转编码器的任务,这个任务就读取本队列,处理数据,再写队列
2.2 写队列
在旋转编码器的中断服务函数里写队列,写哪个队列? >>> g_xQueueRotary
在14_queue_game_multi_input\Drivers\DshanMCU-F103\driver_rotary_encoder.c路径下写先添加头文件
c
#include "FreeRTOS.h"
#include "queue.h"
#include "typedefs.h"
再外部声明这个队列
c
extern QueueHandle_t g_xQueueRotary; /* 旋转编码器队列 */
在中断回调函数添加写队列的代码,后面注释了很多加号的就是新增的部分
c
/**********************************************************************
* 函数名称: RotaryEncoder_IRQ_Callback
* 功能描述: 旋转编码器的中断回调函数
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 无
* 修改日期: 版本号 修改人 修改内容
* -----------------------------------------------
* 2023/08/04 V1.0 韦东山 创建
***********************************************************************/
void RotaryEncoder_IRQ_Callback(void)
{
uint64_t time;
static uint64_t pre_time = 0;
struct rotary_data rdata; //定义这个结构体++++++++++++++++++++++++++++++++++++++++++++++
/* 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 = 0 - g_speed;
}
pre_time = time;
/* 写队列 */ //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
rdata.cnt = g_count;
rdata.speed = g_speed;
xQueueSendFromISR(g_xQueueRotary, &rdata, NULL); //把这个结构体写入队列g_xQueueRotary
}
2.3 创建任务
创建旋转编码器的任务
c
/* 创建旋转编码器的任务 */
xTaskCreate(RotaryEncoderTask, "RotaryEncoderTask", 128, NULL, osPriorityNormal, NULL);
编写旋转编码器任务函数
c
static void RotaryEncoderTask(void *params)
{
struct rotary_data rdata; //定义结构体 读取
struct input_data idata; //定义结构体 写入
uint8_t left;
uint8_t i, cnt;
while(1)
{
/* 1.读取旋转编码器队列 */
xQueueReceive(g_xQueueRotary,&rdata, portMAX_DELAY); //读到的数据保存到rdata结构体里
/* 2.处理队列 */
// 判断速度:-表示左移,+表示右移
if (rdata.speed < 0)
{
left = 1;
rdata.speed = 0 - rdata.speed; //负数,改成正数
}
else
{
left = 0;
}
if (rdata.speed > 100)
cnt = 5;
else if (rdata.speed > 100)
cnt = 3;
else
cnt = 1;
/* 3.写入挡球板队列 */
idata.dev = 1;
idata.val = left ? 0xe0 : 0x90;
for (i = 0; i < cnt; i ++) //速度快就多写几遍
{
xQueueSend(g_xQueuePlatform, &idata, 0); //这里要么成功\要么失败,不能阻塞
}
}
}
烧录运行这个代码是有bug的,旋转编码器只能右移
而且左右移的数据和红外的数据是关联的,我们需要对他们两个解耦,分别控制
队列A里用的是红外的格式,我们修改一下即可实现解耦
在这个函数里static int IRReceiver_IRQTimes_Parse(void),写队列的时候,我们修改一下,不写红外的键值了,写左右
c
/* 写队列 */
idata.dev = datas[0];
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;
g_last_val = idata.val; // 重复码处理
xQueueSendToBackFromISR(g_xQueuePlatform, &idata, NULL);
旋转编码器的左右
c
static void RotaryEncoderTask(void *params)
{
struct rotary_data rdata; //定义结构体 读取
struct input_data idata; //定义结构体 写入
uint8_t left;
uint8_t i, cnt;
while(1)
{
/* 1.读取旋转编码器队列 */
xQueueReceive(g_xQueueRotary,&rdata, portMAX_DELAY); //读到的数据保存到rdata结构体里
/* 2.处理队列 */
// 判断速度:-表示左移,+表示右移
if (rdata.speed < 0)
{
left = 1;
rdata.speed = 0 - rdata.speed; //负数,改成正数
}
else
{
left = 0;
}
if (rdata.speed > 100)
cnt = 5;
else if (rdata.speed > 100)
cnt = 3;
else
cnt = 1;
/* 3.写入挡球板队列 */
idata.dev = 1;
idata.val = left ? UPT_MOVE_LEFT : UPT_MOVE_RIGHT; //+++++++++++++++++++++++
for (i = 0; i < cnt; i ++) //速度快就多写几遍
{
xQueueSend(g_xQueuePlatform, &idata, 0); //这里要么成功\要么失败,不能阻塞
}
}
}
判断重复码部分,如果有重复码,就上报上一次的按键值
c
/* 是否重复码 */
if (isRepeatedKey())
{
/* device: 0, val: 0, 表示重复码 */
//PutKeyToBuf(0);
//PutKeyToBuf(0);
/* 写队列 */
idata.dev = 0;
idata.val = g_last_val; //如果有重复码,就上报上一次的按键值
xQueueSendToBackFromISR(g_xQueuePlatform, &idata, NULL);
g_IRReceiverIRQ_Cnt = 0;
}
3 勘误
bug:旋转编码器不好用,旋转编码器只能控制挡球板右移,移到最右边,就不能动了,不能往左移动!
错误代码:
c
void RotaryEncoder_IRQ_Callback(void)
{
uint64_t time;
static uint64_t pre_time = 0;
struct rotary_data rdata; //定义这个结构体++++++++++++++++++++++++++++++++++++++++++++++
/* 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 = 0 - g_speed;
}
pre_time = time;
/* 写队列 */ //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
rdata.cnt = g_count;
rdata.speed = g_speed;
xQueueSendFromISR(g_xQueueRotary, &rdata, NULL); //把这个结构体写入队列g_xQueueRotary
}
消抖用的mdelay(2),跳过2ms,这样是错误的,假设抖动大于2ms,后面的抖动的数据也被采集进去了,这样就错误了
修改之后的代码,具体看注释当前时间 - 上次中断的时间 < 2ms 认为是抖动!就返回,不处理!
c
/**********************************************************************
* 函数名称: RotaryEncoder_IRQ_Callback
* 功能描述: 旋转编码器的中断回调函数
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 无
* 修改日期: 版本号 修改人 修改内容
* -----------------------------------------------
* 2023/08/04 V1.0 韦东山 创建
***********************************************************************/
void RotaryEncoder_IRQ_Callback(void)
{
uint64_t time;
static uint64_t pre_time = 0;
struct rotary_data rdata; //定义这个结构体++++++++++++++++++++++++++++++++++++++++++++++
/* 1. 记录中断发生的时刻 */
time = system_get_ns();
/* 上升沿触发: 必定是高电平
* 防抖
*/
//mdelay(2); //没有意义
if (time - pre_time < 2000000) //当前时间 - 上次中断的时间 < 2ms 认为是抖动!
{
pre_time = time;
return; //就返回,不处理
}
if (!RotaryEncoder_Get_S1())
return;
/* S1上升沿触发中断
* S2为0表示逆时针转, 为1表示顺时针转
*/
g_speed = (uint64_t)1000000000/(time - pre_time);
if (g_speed == 0) // 如果后面的时间足够大,这个speed可能=0,0很小,不能移动,我们给赋值1,很小的速度移动!
g_speed = 1;
if (RotaryEncoder_Get_S2())
{
g_count++;
}
else
{
g_count--;
g_speed = 0 - g_speed; //反转吗
}
pre_time = time;
/* 写队列 */ //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
rdata.cnt = g_count;
rdata.speed = g_speed;
xQueueSendFromISR(g_xQueueRotary, &rdata, NULL); //把这个结构体写入队列g_xQueueRotary
}
我们继续优化一下队列的处理部分
c
static void RotaryEncoderTask(void *params)
{
struct rotary_data rdata; //定义结构体 读取
struct input_data idata; //定义结构体 写入
// uint8_t left;
uint8_t i, cnt;
while(1)
{
/* 1.读取旋转编码器队列 */
xQueueReceive(g_xQueueRotary,&rdata, portMAX_DELAY); //读到的数据保存到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); //这里要么成功\要么失败,不能阻塞
}
}
}
现在就好使了!