目录
- 0 前言
- 1 事件组实验_改进姿态控制
- 2 改进思路
- 2.1 创建事件
- 2.2 等待事件
- 2.3 设置事件
- 2.4 Debug
- 2.5 设置MPU6050寄存器
- 3 总结
0 前言
学习视频:
【FreeRTOS 入门与工程实践 --由浅入深带你学习 FreeRTOS(FreeRTOS 教程 基于 STM32,以实际项目为导向)】 【精准空降到 00:28】 https://www.bilibili.com/video/BV1Jw411i7Fz/?p=43&share_source=copy_web&vd_source=8af85e60c2df9af1f0fd23935753a933&t=28
参考《FreeRTOS 入门与工程实践(基于 DshanMCU-103).pdf》
- 《第 10 章 同步互斥与通信》
- 《第 14 章 事件组(event group)》
1 事件组实验_改进姿态控制
本节源码:在"25_eventgroup_and"的基础上,改出:26_eventgroup_mpu6050
2 改进思路
以前的程序框图
- 这里的思路不是使用中断来操作MPU6050,我们是创建了一个任务,读取I2C,但是读取I2C这个操作很耗时间,就需要放到任务里去完成,这没有问题的
- 那我们怎么样让这个任务及时获得我们摇晃的动作呢?
- 我们用的是vTaskDelay,使其50ms
改进思路如下:
- 我们可以使用中断,在中断里唤醒任务,在任务里读取I2C,读到数据之后再写队列,把数据写到队列3去,这里会同时写队列集,这个InputTask就会获得队列3的句柄,然后读取队列3得到摇晃的数据,最后去控制游戏。
- 现在我们需要增加一个中断,在中断里怎么唤醒任务?
- 我们可以用事件组来唤醒:在中断里设置事件,在读I2C之前等待事件(读不到事件就一直阻塞)
这样和之前的程序相比较,现在就少了很多无谓的操作了
2.1 创建事件
static EventGroupHandle_t g_xEventMPU6050; //定义MPU6050事件
/* 然后再到MPU6050_Init创建事件 */
int MPU6050_Init(void)
{
MPU6050_WriteRegister(MPU6050_PWR_MGMT_1, 0x00); //解除休眠状态
MPU6050_WriteRegister(MPU6050_PWR_MGMT_2, 0x00);
MPU6050_WriteRegister(MPU6050_SMPLRT_DIV, 0x09);
MPU6050_WriteRegister(MPU6050_CONFIG, 0x06);
MPU6050_WriteRegister(MPU6050_GYRO_CONFIG, 0x18);
MPU6050_WriteRegister(MPU6050_ACCEL_CONFIG, 0x18);
g_xQueueMPU6050 = xQueueCreate(MPU6050_QUEUE_LEN, sizeof(struct mpu6050_data));
g_xEventMPU6050 = xEventGroupCreate();
return 0;
}
2.2 等待事件
然后在任务里等待事件
void MPU6050_Task(void *params)
{
int16_t AccX;
struct mpu6050_data result;
int ret;
extern void GetI2C(void);
extern void PutI2C(void);
while (1)
{
/* 等待事件 */
/* 等待bit0 and bit1事件 */
xEventGroupWaitBits(g_xEventMPU6050, (1<<0), // 等待bit0 and bit1事件
pdTRUE, // pdTRUE: 清除uxBitsToWaitFor指定的位
pdTRUE, // 都为1才可以
portMAX_DELAY); // 一定等到成功才返回
/* 读数据 */
GetI2C(); // 使用LCD之前,先获得互斥量。会一直等待
ret = MPU6050_ReadData(&AccX, NULL, NULL, NULL, NULL, NULL);
PutI2C(); // 使用LCD之后,再释放互斥量
if (0 == ret)
{
/* 解析数据 */
MPU6050_ParseData(AccX, 0, 0, 0, 0, 0, &result);
/* 写队列 */
xQueueSend(g_xQueueMPU6050, &result, 0);
}
/* delay */
vTaskDelay(50);
}
}
加入必要的头文件
#include "FreeRTOS.h" // ARM.FreeRTOS::RTOS:Core
#include "task.h" // ARM.FreeRTOS::RTOS:Core
#include "event_groups.h" // ARM.FreeRTOS::RTOS:Event Groups
2.3 设置事件
根据原理图,我们使用PB5的外部中断用来触发,设置事件。
MPU6050的INT引脚是外部中断引脚,触发中断是高电平信号,INT引脚静息状态为低电平。所以这里我们配置上升沿触发,上拉输入
void EXTI9_5_IRQHandler(void)
{
/* 设置事件组:bit0 */
xEventGroupSetBits(g_xEventMPU6050, (1<<0));
}
2.4 Debug
编译下载程序,发现不能运行,我们先来检查是否使能了中断,再检查是否进入了中断,打开CubeMX发现确实没使能中断,
然后就报错了,说我们重复定义了
01_freertos_template\01_freertos_template.axf:
Error: L6200E: Symbol EXTI9_5_IRQHandler multiply defined
(by driver_mpu6050.o and stm32f1xx_it.o).
然后编写MPU6050的设置事件组的代码
void MPU6050_Callback(void)
{
/* 设置事件组:bit0 */
xEventGroupSetBitsFromISR(g_xEventMPU6050, (1<<0), NULL); //在中断里设置事件
}
声明这个文件,并且在中断回调函数里添加 case GPIO_PIN_5的情况!
extern void MPU6050_Callback(void);
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
switch (GPIO_Pin)
{
case GPIO_PIN_5:
{
MPU6050_Callback();
break;
}
case GPIO_PIN_10:
{
IRReceiver_IRQ_Callback();
break;
}
case GPIO_PIN_12:
{
RotaryEncoder_IRQ_Callback();
break;
}
default:
{
break;
}
}
}
2.5 设置MPU6050寄存器
设置寄存器,做初始化工作:配置中断引脚、使能中断
#define MPU6050_INT_PIN_CFG 0x37 //引脚配置
#define MPU6050_INT_ENABLE 0x38 //中断使能
int MPU6050_Init(void)
{
MPU6050_WriteRegister(MPU6050_PWR_MGMT_1, 0x00); //解除休眠状态
MPU6050_WriteRegister(MPU6050_PWR_MGMT_2, 0x00);
MPU6050_WriteRegister(MPU6050_SMPLRT_DIV, 0x09);
MPU6050_WriteRegister(MPU6050_CONFIG, 0x06);
MPU6050_WriteRegister(MPU6050_GYRO_CONFIG, 0x18);
MPU6050_WriteRegister(MPU6050_ACCEL_CONFIG, 0x18);
/* 配置中断引脚 */
MPU6050_WriteRegister(MPU6050_INT_PIN_CFG, 0);
/* 使能中断 */
MPU6050_WriteRegister(MPU6050_INT_ENABLE, 0xFF);
g_xQueueMPU6050 = xQueueCreate(MPU6050_QUEUE_LEN, sizeof(struct mpu6050_data));
g_xEventMPU6050 = xEventGroupCreate(); //创建事件
return 0;
}
烧录程序,现在就能正常运行了,但是运行的特别快,我们在MPU6050_Task的最后取消注释vTaskDelay(50);
现在就很丝滑了
3 总结
我们实现了在中断里使用事件组改造这个程序,这个任务只有当中断写了事件之后,得到了事件,才会读取I2C,避免了一直读取I2C的数据,节省资源。