这个是全网最详细的STM32项目教学视频。
第一篇CSDN文章在这里在这里:
75
V3:HAL库开发、手把手教学下面功能:PID速度控制、PID循迹、PID跟随、遥控、避障、PID角度控制、openmv视觉控制、电磁循迹、FreeRTOS、K210视觉智能车(更新中)、K230视觉智能车(更新中)、MSPM0G3507视觉智能车(更新中)
23.1.3-消息队列编程测试
创建消息队列
复制22-6_LED_FreeRTOS的工程命名为23-1_LED_FreeRTOS
消息队列可以自己写代码创建也可以使用STM32cubemx创建
这里说一下如何使用STM32cubemx创建任务
Queue Name: 队列名称 myQueueMode
Queue Size: 队列能够存储的最大单元数目,即队列深度 我们这里用来保存当前模式一个就够了 所以设置1
Queue Size: 队列中数据单元的长度,以字节为单位 我们之前模式变量定义的是八位 所以这里也设置8位
Allocation: 分配方式:Dynamic 动态内存创建 我们动态创建
Buffer Name: 缓冲区名称
Buffer Size: 缓冲区大小
Conrol Block Name: 控制块名称
设置完成后然后生成代码
打开代码
可以看到有自动生成的消息队列句柄
创建消息队列 myQueueMode 并返回句柄 myQueueModeHandle。
中断中向消息队列发送数据
修改按键中断
我们需要在按键中断中通过消息队列设置当前模式,先在前面包含相关头文件和声明一下 模式消息队列
#include "FreeRTOS.h"//包含相关头文件
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
extern osMessageQId myQueueModeHandle; //声明一下 模式消息队列的句柄
因为我们的逻辑是按下按键1 会在当前模式下跳转下一个模式,所以我们的按键就需要获取当前模式。
按键中断的代码如下
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE; // 用于中断后的任务调度标志 pdFALSE表示不进行任务调度
uint8_t currentMode = 0; // 当前模式
uint8_t newMode = 0; // 新模式
if(GPIO_Pin == KEY1_Pin) //判断一下那个引脚触发中断
{
/*注意现在是在外部中断 要调用HAL_Delay,会使用Systick定时器中断 所以Systick优先级要高于外部中断*/
HAL_Delay(10);//延时消抖
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_SET)//判断KEY1引脚仍为高电平
{
// 获取当前模式:从消息队列中读取模式(使用xQueueReceiveFromISR)
if (xQueueReceiveFromISR(myQueueModeHandle, ¤tMode, &xHigherPriorityTaskWoken) != pdPASS)
{
currentMode = 0; // 如果队列为空,则默认为模式0
}
// 根据当前模式和按键动作更新模式
if (currentMode == 6)
{
newMode = 1; // 如果当前模式是6,按键按下后切换到1
}
else{
newMode = currentMode + 1; // 否则模式加1
}
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // 反转LED
// 将新模式发送到消息队列(使用xQueueSendFromISR)
xQueueSendFromISR(myQueueModeHandle, &newMode, &xHigherPriorityTaskWoken); // 将新模式发送到队列
}
}
if(GPIO_Pin == KEY2_Pin) //判断一下那个引脚触发中断
{
HAL_Delay(10);//延时消抖
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET)//判断KEY1引脚仍为低电平
{
// 重置模式为0
newMode = 0;
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // 反转LED
// 将新模式发送到消息队列
//xQueueOverwriteFromISR 中断中写入消息队列数据-且强制覆盖原有数据
xQueueOverwriteFromISR(myQueueModeHandle, &newMode, &xHigherPriorityTaskWoken); // 将新模式发送到队列
}
}
}
修改串口中断
因为串口中断中也有当前模式设置的功能
所以我们把串口中断设置模式的也改一下,处理修改逻辑是按键中断基本一样
先声明和定义相关文件和句柄
#include "FreeRTOS.h"//包含相关头文件
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
extern osMessageQId myQueueModeHandle; //声明一下 模式消息队列的句柄
因为是局部变量、所以几个变量名字一样就可以了。
BaseType_t xHigherPriorityTaskWoken = pdFALSE; // 用于中断后的任务调度标志 pdFALSE表示不进行任务调度
uint8_t currentMode = 0; // 当前模式
uint8_t newMode = 0; // 新模式
串口接收消息设置模式和按键按下设置逻辑也基本一致
if(g_ucUsart3ReceiveData == 'J') //改变模式
{
// 获取当前模式:从消息队列中读取模式(使用xQueueReceiveFromISR)
if (xQueueReceiveFromISR(myQueueModeHandle, ¤tMode, &xHigherPriorityTaskWoken) != pdPASS)
{
currentMode = 0; // 如果队列为空,则默认为模式0
}
// 根据当前模式和按键动作更新模式
if (currentMode == 6)
{
newMode = 1; // 如果当前模式是6,按键按下后切换到1
}
else{
newMode = currentMode + 1; // 否则模式加1
}
// 将新模式发送到消息队列(使用xQueueSendFromISR)
xQueueSendFromISR(myQueueModeHandle, &newMode, &xHigherPriorityTaskWoken); // 将新模式发送到队列
}
if(g_ucUsart3ReceiveData == 'K') //设置为显示模式
{
// 重置模式为0
newMode = 0;
// 将新模式发送到消息队列
//xQueueOverwriteFromISR 中断中写入消息队列数据-且强制覆盖原有数据
xQueueOverwriteFromISR(myQueueModeHandle, &newMode, &xHigherPriorityTaskWoken); // 将新模式发送到队列
}
任务中读取消息队列
我们写完了 中断中写入消息队列代码,下面我们写一下 在任务中读取消息队列
先写一下 电机停止任务中的根据模式控制电机
void StartStopTask(void const * argument)
{
uint8_t receivedMode; // 定义临时变量 用来存储读取到的当前模式
/* USER CODE BEGIN StartStopTask */
/* Infinite loop */
for(;;)
{
// UBaseType_t stackHighWaterMark = uxTaskGetStackHighWaterMark(NULL);// 获取当前任务的栈高水位值
// printf("StartLedTask Mark: %u wordsn", (unsigned int)stackHighWaterMark);
// size_t freeHeapSize = xPortGetFreeHeapSize();// 获取系统的可用堆空间
// printf("StartLedTask Free Heap Size: %u bytesn", (unsigned int)freeHeapSize);
// xQueuePeek从队列查看数据 但是不将数据移除。
//不等待队列中的数据,队列为空 则赋值receivedMode 为零
if (xQueuePeek(myQueueModeHandle, &receivedMode, 0) == errQUEUE_EMPTY)
{
receivedMode = 0; //返回errQUEUE_EMPTY 表示 消息队列为空 赋值receivedMode 为零
}
if(receivedMode == 0)//如果当前模式为0 则设置小车速度为零
{
motorPidSetSpeed(0,0); //设置小车速度为0
}
osDelay(10);
}
/* USER CODE END StartStopTask */
}
然后我们修改一下 多模式任务代码
至于那个保存读取出来的临时变量 怎么命名都可以receivedMode或者readMode均可,这里我们命名readMode
注意,我们定义的时候,是把readMode 定义在每个任务的内部,所以现在readMode是局部变量 这和我们之前使用的全局变量g_ucMode是不一样的,我们现在是通过消息队列 myQueueMode 完成同步的。
void StartMultiModTask(void const * argument)
{
uint8_t readMode; // 定义临时变量 用来存储读取到的当前模式
/* USER CODE BEGIN StartMultiModTask */
/* Infinite loop */
for(;;)
{
// UBaseType_t stackHighWaterMark = uxTaskGetStackHighWaterMark(NULL);// 获取当前任务的栈高水位值
// printf("StartMultiModTask Mark: %u wordsn", (unsigned int)stackHighWaterMark);
// size_t freeHeapSize = xPortGetFreeHeapSize();// 获取系统的可用堆空间
// printf("StartMultiModTask Free Heap Size: %u bytesn", (unsigned int)freeHeapSize);
// xQueuePeek 从队列读取数据,且不将数据移除
//不等待队列中的数据,队列为空时立即返回
//如果队列为空则赋值 当前模式receivedMode 为 0
if (xQueuePeek(myQueueModeHandle, &readMode, 0) == errQUEUE_EMPTY)
{
readMode = 0; //如果返回errQUEUE_EMPTY 就是消息队列为空就赋值 readMode = 0
}
if(readMode == 1)
{
///**** 红外PID循迹功能******************/
g_ucaHW_Read[0] = READ_HW_OUT_1;//读取红外对管状态、这样相比于写在if里面更高效
g_ucaHW_Read[1] = READ_HW_OUT_2;
g_ucaHW_Read[2] = READ_HW_OUT_3;
g_ucaHW_Read[3] = READ_HW_OUT_4;
if(g_ucaHW_Read[0] == 0&&g_ucaHW_Read[1] == 0&&g_ucaHW_Read[2] == 0&&g_ucaHW_Read[3] == 0 )
{
// printf("应该前进rn");//注释掉更加高效,减少无必要程序执行
g_cThisState = 0;//前进
}
else if(g_ucaHW_Read[0] == 0&&g_ucaHW_Read[1] == 1&&g_ucaHW_Read[2] == 0&&g_ucaHW_Read[3] == 0 )//使用else if更加合理高效
{
// printf("应该右转rn");
g_cThisState = -1;//应该右转
}
else if(g_ucaHW_Read[0] == 1&&g_ucaHW_Read[1] == 0&&g_ucaHW_Read[2] == 0&&g_ucaHW_Read[3] == 0 )
{
// printf("快速右转rn");
g_cThisState = -2;//快速右转
}
else if(g_ucaHW_Read[0] == 1&&g_ucaHW_Read[1] == 1&&g_ucaHW_Read[2] == 0&&g_ucaHW_Read[3] == 0)
{
// printf("快速右转rn");
g_cThisState = -3;//快速右转
}
else if(g_ucaHW_Read[0] == 0&&g_ucaHW_Read[1] == 0&&g_ucaHW_Read[2] == 1&&g_ucaHW_Read[3] == 0 )
{
// printf("应该左转rn");
g_cThisState = 1;//应该左转
}
else if(g_ucaHW_Read[0] == 0&&g_ucaHW_Read[1] == 0&&g_ucaHW_Read[2] == 0&&g_ucaHW_Read[3] == 1 )
{
// printf("快速左转rn");
g_cThisState = 2;//快速左转
}
else if(g_ucaHW_Read[0] == 0&&g_ucaHW_Read[1] == 0&&g_ucaHW_Read[2] == 1&&g_ucaHW_Read[3] == 1)
{
// printf("快速左转rn");
g_cThisState = 3;//快速左转
}
g_fHW_PID_Out = PID_realize(&pidHW_Tracking,g_cThisState);//PID计算输出目标速度 这个速度,会和基础速度加减
g_fHW_PID_Out1 = 3 + g_fHW_PID_Out;//电机1速度=基础速度+循迹PID输出速度
g_fHW_PID_Out2 = 3 - g_fHW_PID_Out;//电机1速度=基础速度-循迹PID输出速度
if(g_fHW_PID_Out1 >5) g_fHW_PID_Out1 =5;//进行限幅 限幅速度在0-5之间
if(g_fHW_PID_Out1 <0) g_fHW_PID_Out1 =0;
if(g_fHW_PID_Out2 >5) g_fHW_PID_Out2 =5;//进行限幅 限幅速度在0-5之间
if(g_fHW_PID_Out2 <0) g_fHW_PID_Out2 =0;
if(g_cThisState != g_cLastState)//如何这次状态不等于上次状态、就进行改变目标速度和控制电机、在定时器中依旧定时控制电机
{
motorPidSetSpeed(g_fHW_PID_Out1,g_fHW_PID_Out2);//通过计算的速度控制电机
}
g_cLastState = g_cThisState;//保存上次红外对管状态
}
if(readMode == 2)
{
//***************遥控模式***********************//
//遥控模式的控制在串口三的中断里面
}
if(readMode == 3)
{
//******超声波避障模式*********************//
避障逻辑
if(HC_SR04_Read() > 25)//前方无障碍物
{
motorPidSetSpeed(1,1);//前运动
osDelay(100);
}
else{ //前方有障碍物
motorPidSetSpeed(-1,1);//右边运动 原地
osDelay(500);
if(HC_SR04_Read() > 25)//右边无障碍物
{
motorPidSetSpeed(1,1);//前运动
osDelay(100);
}
else{//右边有障碍物
motorPidSetSpeed(1,-1);//左边运动 原地
osDelay(1000);
if(HC_SR04_Read() >25)//左边无障碍物
{
motorPidSetSpeed(1,1);//前运动
osDelay(100);
}
else{
motorPidSetSpeed(-1,-1);//后运动
osDelay(1000);
motorPidSetSpeed(-1,1);//右边运动
osDelay(50);
}
}
}
}
if(readMode == 4)
{
//**********PID跟随功能***********//
g_fHC_SR04_Read=HC_SR04_Read();//读取前方障碍物距离
if(g_fHC_SR04_Read < 60){ //如果前60cm 有东西就启动跟随
g_fFollow_PID_Out = PID_realize(&pidFollow,g_fHC_SR04_Read);//PID计算输出目标速度 这个速度,会和基础速度加减
if(g_fFollow_PID_Out > 6) g_fFollow_PID_Out = 6;//对输出速度限幅
if(g_fFollow_PID_Out < -6) g_fFollow_PID_Out = -6;
motorPidSetSpeed(g_fFollow_PID_Out,g_fFollow_PID_Out);//速度作用与电机上
}
else motorPidSetSpeed(0,0);//如果前面60cm 没有东西就停止
osDelay(10);//读取超声波传感器不能过快
}
if(readMode == 5)
{
//*************MPU6050航向角 PID转向控制*****************//
sprintf((char *)Usart3String,"pitch:%.2f roll:%.2f yaw:%.2frn",pitch,roll,yaw);//显示6050数据 俯仰角 横滚角 航向角
HAL_UART_Transmit(&huart3,( uint8_t *)Usart3String,strlen(( const char *)Usart3String),0xFFFF);//通过串口三输出字符 strlen:计算字符串大小
// mpu_dmp_get_data(&pitch,&roll,&yaw);//返回值:0,DMP成功解出欧拉角
// while(mpu_dmp_get_data(&pitch,&roll,&yaw)!=0){} //这个可以解决经常读不出数据的问题
g_fMPU6050YawMovePidOut = PID_realize(&pidMPU6050YawMovement,yaw);//PID计算输出目标速度 这个速度,会和基础速度加减
g_fMPU6050YawMovePidOut1 = 1.5 + g_fMPU6050YawMovePidOut;//基础速度加减PID输出速度
g_fMPU6050YawMovePidOut2 = 1.5 - g_fMPU6050YawMovePidOut;
if(g_fMPU6050YawMovePidOut1 >3.5) g_fMPU6050YawMovePidOut1 =3.5;//进行限幅
if(g_fMPU6050YawMovePidOut1 <0) g_fMPU6050YawMovePidOut1 =0;
if(g_fMPU6050YawMovePidOut2 >3.5) g_fMPU6050YawMovePidOut2 =3.5;//进行限幅
if(g_fMPU6050YawMovePidOut2 <0) g_fMPU6050YawMovePidOut2 =0;
motorPidSetSpeed(g_fMPU6050YawMovePidOut1,g_fMPU6050YawMovePidOut2);//将最后计算的目标速度 通过motorPidSetSpeed控制电机
}
if(readMode == 6)
{
// sprintf((char*)OledString, "lHW:%d ", g_lHW_State);//视觉识别结果
// OLED_ShowString(0,7,OledString,12);//这个是oled驱动里面的,是显示位置的一个函数,
g_fHW_PID_Out = PID_realize(&pidOpenmv_Tracking,g_cThisState);//PID计算输出目标速度 这个速度,会和基础速度加减
g_fHW_PID_Out1 = 0.5 + g_fHW_PID_Out;//电机1速度=基础速度+循迹PID输出速度
g_fHW_PID_Out2 = 0.5 - g_fHW_PID_Out;//电机1速度=基础速度-循迹PID输出速度
if(g_fHW_PID_Out1 >1.2) g_fHW_PID_Out1 =1.2;//进行限幅 限幅速度在0-1.2之间
if(g_fHW_PID_Out1 <0) g_fHW_PID_Out1 =0;
if(g_fHW_PID_Out2 >1.2) g_fHW_PID_Out2 =1.2;//进行限幅 限幅速度在0-1.2之间
if(g_fHW_PID_Out2 <0) g_fHW_PID_Out2 =0;
if(g_cThisState != g_cLastState)//如何这次状态不等于上次状态、就进行改变目标速度和控制电机、在定时器中依旧定时控制电机
{
motorPidSetSpeed(g_fHW_PID_Out1,g_fHW_PID_Out2);//通过计算的速度控制电机
}
g_cLastState = g_cThisState;//保存上次红外对管状态
}
osDelay(1);
}
/* USER CODE END StartMultiModTask */
}
然后显示部分也要修改,显示的模式变量要是从消息队列中获得才可以
void StartOledTask(void const * argument)
{
uint8_t readMode; // 定义临时变量 用来存储读取到的当前模式
/* USER CODE BEGIN StartOledTask */
/* Infinite loop */
for(;;)
{
// UBaseType_t stackHighWaterMark = uxTaskGetStackHighWaterMark(NULL);// 获取当前任务的栈高水位值
// printf("StartOledTask Mark: %u wordsn", (unsigned int)stackHighWaterMark);
// size_t freeHeapSize = xPortGetFreeHeapSize();// 获取系统的可用堆空间
// printf("StartOledTask Free Heap Size: %u bytesn", (unsigned int)freeHeapSize);
// xQueuePeek从队列读取数据,且不将数据移除
//不等待队列中的数据,队列为空时立即返回
//如果队列为空 则赋值当前模式receivedMode 为 0
if (xQueuePeek(myQueueModeHandle, &readMode, 0) == errQUEUE_EMPTY)
{
readMode = 0; //如果返回errQUEUE_EMPTY 就是消息队列为空就赋值 readMode = 0
}
sprintf((char *)OledString," g_ucMode:%d",readMode);//显示g_ucMode 当前模式
OLED_ShowString(0,6,OledString,12); //显示在OLED上
sprintf((char*)OledString, "lHW:%d ", g_lHW_State);//视觉识别结果
OLED_ShowString(0,7,OledString,12);//这个是oled驱动里面的,是显示位置的一个函数,
sprintf((char *)Usart3String," g_ucMode:%d",readMode);//蓝牙APP显示
HAL_UART_Transmit(&huart3,( uint8_t *)Usart3String,strlen(( const char *)Usart3String),50);//阻塞式发送通过串口三输出字符 strlen:计算字符串大小
好的我们这里调试了23-1_LED_FreeRTOS 的代码六个模式
0-电机停止模式-测试正常
1-PID红外循迹-测试正常
2-蓝牙遥控模式-测试正常
3-超声波避障模式-测试正常
4-超声波PID跟随模式-测试正常
5-MPU6050航向角 PID转向控制-这个没有测试,我们把6050代码都注释掉了
6-摄像头循迹模式-测试正常
下一章节我们再加入MPU6050