STM32CubeMX->FreeRTOS+USART接收不定长数据
由于本人做的一个项目功能相对复杂,要求使用操作系统,且项目工程中有很多需要串口操作的外设,所以需要对串口设计不定长的收发功能,裸机跑惯了的孩子就是比较野,一天瞎吉尔弄,现在是不是GG,这里先批评一下自己。为了解决这一问题并做个笔记,就有了这篇博客。
实现思路:
使用串口、定时器记录接收到的数据,每5ms进入一次处理函数(一条数据处理完毕)
使用一个计数位、一个标志位、一个暂存buff
标志位置1则认为数据处理完成,将数据打印出来
使用硬件功能:串口3及中断,定时器7及中断
使用操作系统元素:一个互斥量
使能RTOS后会RTOS会强制调用系统时钟,所以在使能了操作系统之后需要将系统时钟换为定时器TIM1
操作步骤
-
启动STM32CubeMX并对芯片选型
主要设置- 串口3及中断使能,115200,8,None,1
- 定时器7及中断使能,预分频9000-1,自动重载50-1(时钟树APB1、2为90MHz)
- SYS时钟源设为定时器1
- 使能FreeRTOS,创建1个串口任务、创建1个互斥量
- 使能两个GPIO输出驱动LED
以上都清楚的大佬转到代码功能实现继续
-
使能RCC时钟源为外部时钟
3. 设置SYS调试方式为串口,时钟源改为定时器1
4. 设置时钟树,是APB1、2为90MHz,或者自行设定,在后面定时器工作会用到
5. 使能串口3,并启用串口3中断,串口3设置为115200,8Bit,None,1(不知道什么意思的就没有必要看下去了)
6. 使能定时器7,并使能中断,定时器预分频系数与之前的90MHz相关,需要通过预分频系数和自动重载值将定时时间设为5ms【200Hz】,不会算的看我定时器博客(这里我用了一个不常用的定时器,用哪个都可以,只要注意好定时器隶属哪根时钟线就行)
7. 使能FreeRTOS并创建一个串口任务和一个二值量(二值信号量用于串口数据接收完成后进入串口任务)
8. 设置两个IO输出作为一个可视化依据
9. 项目命名、选择使用的程序编辑软件我的是MDK_ARM V5、分离.c.h文件,点击右上角GENERATE CODE生成工程
- 打开项目,先编译没有错误之后再进行修改
代码功能实现
-
首先在usart.c文件对串口3进行printf函数重定义,使串口3可用printf函数输出
内容加入到/* USER CODE BEGIN 0 */框架内
/* USER CODE BEGIN 0 */ /********************************************************************************/ #include "stdio.h" //加入以下代码,支持printf函数,而不需要选择use MicroLIB //#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #if 1 #pragma import(__use_no_semihosting) //标准库需要的支持函数 struct __FILE { int handle; }; FILE __stdout; //定义_sys_exit()以避免使用半主机模式 void _sys_exit(int x) { x = x; } //重定义fputc函数 int fputc(int ch, FILE *f) { while((USART3->SR&0X40)==0);//循环发送,直到发送完毕 USART3->DR = (uint8_t) ch; return ch; } #endif /********************************************************************************/ /* USER CODE END 0 */
-
定义串口使用的变量
主要包括串口数据存储BUFF,串口数据长度计数BUFF,串口接收成功标志位(此处借鉴正点原子的串口处理方法)
在main.c文件下的定义/* USER CODE BEGIN PV */框架内定义
/* USER CODE BEGIN PV */ /********************************************************************************/ uint8_t RxBuffer[MAX_REC_LENGTH] = {0}; //串口数据存储BUFF 长度2048 uint8_t RxFlag = 0; //串口接收完成标志符 uint16_t RxCounter = 0; //串口长度计数 uint8_t RxTemp[REC_LENGTH] = {0}; //串口数据接收暂存BUFF 长度1 extern osSemaphoreId myBinarySem01Handle; //操作系统定义的互斥量 /********************************************************************************/ /* USER CODE END PV */
并在usart.h文件下的/* USER CODE BEGIN Private defines */框架下定义长度和链接串口用变量
/* USER CODE BEGIN Private defines */ /********************************************************************************/ #define REC_LENGTH 1 #define MAX_REC_LENGTH 2048 extern uint8_t RxBuffer[MAX_REC_LENGTH]; extern uint8_t RxFlag; extern uint16_t RxCounter; extern uint8_t RxTemp[REC_LENGTH]; /********************************************************************************/ /* USER CODE END Private defines */
-
串口中断回调函数,在main.c的/* USER CODE BEGIN 4 */框架下添加串口回调函数
/********************************************************************************/ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//串口3接收完成回调函数 { if(huart->Instance == USART3) { __HAL_TIM_SET_COUNTER(&htim7,0); //清除定时器7计数值 if(0 == RxCounter) //如果是首字符(每帧数据开头)则开启定时器 { __HAL_TIM_CLEAR_FLAG(&htim7, TIM_FLAG_UPDATE); //清除中断标志位 HAL_TIM_Base_Start_IT(&htim7); //开启基本定时器 } RxBuffer[RxCounter] = RxTemp[0]; //缓存数据放入接收数组 RxCounter++; //计数器加1 HAL_UART_Receive_IT(&huart3,(uint8_t *)RxTemp, REC_LENGTH); //重新使能中断 } } /********************************************************************************/
-
定时器周期回调函数,由于在设置中系统时钟使用了定时器1,所以在main.c文件存在
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
在函数 /* USER CODE BEGIN Callback 1 */框架下添加定时器7的回调函数
/* USER CODE BEGIN Callback 1 */ /********************************************************************************/ if(htim->Instance == TIM7) { HAL_GPIO_TogglePin(DS1_GPIO_Port, DS1_Pin); //LED反转一次 RxFlag = 1; //接收标志位置1 HAL_TIM_Base_Stop_IT(&htim7); //关闭定时器 osSemaphoreRelease(myBinarySem01Handle); //释放二值信号量,进入串口任务 } /********************************************************************************/ /* USER CODE END Callback 1 */
-
对freeRTOS添加任务
首先在freertos.c文件的/* USER CODE BEGIN Includes */ 框架下添加串口使用的头文件
/* USER CODE BEGIN Includes */ /********************************************************************************/ #include "usart.h" #include "stdio.h" /********************************************************************************/ /* USER CODE END Includes */
-
在freertos主任务中启动串口中断以及添加LED闪烁任务
/* USER CODE END Header_StartDefaultTask */ void StartDefaultTask(void const * argument) { /* USER CODE BEGIN StartDefaultTask */ HAL_UART_Receive_IT(&huart3,(uint8_t *)RxTemp, REC_LENGTH); /* Infinite loop */ for(;;) { HAL_GPIO_TogglePin(DS0_GPIO_Port,DS0_Pin); osDelay(200); } /* USER CODE END StartDefaultTask */ }
-
在freertos串口子任务中添加串口数据打印功能
/* USER CODE END Header_Start_U3_Task */ void Start_U3_Task(void const * argument) { /* USER CODE BEGIN Start_U3_Task */ /* Infinite loop */ for(;;) { /********************************************************************************/ osSemaphoreWait(myBinarySem01Handle,100); //等待二值信号量 if(RxFlag == 1) //数据接收完成 { for(int i = 0; i<RxCounter; i++) //打印接收数组存储的内容 printf("%c",RxBuffer[i]); printf("\r\n"); //打印完成换行 RxFlag = 0; //接收标志清零 RxCounter = 0; //接收计数清零 memset(RxBuffer ,0, MAX_REC_LENGTH); //清空接收数组 } /********************************************************************************/ osDelay(1); } /* USER CODE END Start_U3_Task */ }
实验结果
编译下载程序,打开串口调试助手,用串口转USB模块将串口3接入电脑,使用串口调试助手测试
上图为测试结果,发送ASWaterbenben后回复相同内容,每帧数据用换行符隔开,至此,实验成功!
DS0灯200ms反转一次,每次接收到一帧完整数据DS1灯就会反转一次。这个效果难以展示,你们自己试试就好!
如果本博客对您有帮助,希望点赞关注走一波,今后会看心情更新!哈哈哈哈哈哈哈哈哈