[应用相关] STM32CubeMX在FreeRTOS下使用串口进行数据收发

[复制链接]
4599|31
 楼主| 观海 发表于 2021-8-3 15:26 | 显示全部楼层 |阅读模式
STM32CubeMX->FreeRTOS+USART接收不定长数据
由于本人做的一个项目功能相对复杂,要求使用操作系统,且项目工程中有很多需要串口操作的外设,所以需要对串口设计不定长的收发功能,裸机跑惯了的孩子就是比较野,一天瞎吉尔弄,现在是不是GG,这里先批评一下自己。为了解决这一问题并做个笔记,就有了这篇博客。

实现思路:
​ 使用串口、定时器记录接收到的数据,每5ms进入一次处理函数(一条数据处理完毕)
​ 使用一个计数位、一个标志位、一个暂存buff
​ 标志位置1则认为数据处理完成,将数据打印出来
使用硬件功能:串口3及中断,定时器7及中断
使用操作系统元素:一个互斥量
使能RTOS后会RTOS会强制调用系统时钟,所以在使能了操作系统之后需要将系统时钟换为定时器TIM1

操作步骤
1.启动STM32CubeMX并对芯片选型
主要设置

  • 串口3及中断使能,115200,8,None,1
  • 定时器7及中断使能,预分频9000-1,自动重载50-1(时钟树APB1、2为90MHz)
  • SYS时钟源设为定时器1
  • 使能FreeRTOS,创建1个串口任务、创建1个互斥量
  • 使能两个GPIO输出驱动LED

以上都清楚的大佬转到代码功能实现继续
2.使能RCC时钟源为外部时钟


watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FTV2F0ZXJiZW5iZW4=,size_16,color_FFFFFF,t_70.jpg

 楼主| 观海 发表于 2021-8-3 15:27 | 显示全部楼层

3.设置SYS调试方式为串口,时钟源改为定时器1

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FTV2F0ZXJiZW5iZW4=,size_16,color_FFFFFF,t_70.jpg


 楼主| 观海 发表于 2021-8-3 15:37 | 显示全部楼层
4.设置时钟树,是APB1、2为90MHz,或者自行设定,在后面定时器工作会用到
watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FTV2F0ZXJiZW5iZW4=,size_16,color_FFFFFF,t_70.jpg
 楼主| 观海 发表于 2021-8-3 15:38 | 显示全部楼层

5.使能串口3,并启用串口3中断,串口3设置为115200,8Bit,None,1(不知道什么意思的就没有必要看下去了)

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FTV2F0ZXJiZW5iZW4=,size_16,color_FFFFFF,t_70.jpg


 楼主| 观海 发表于 2021-8-3 15:41 | 显示全部楼层

6. 使能定时器7,并使能中断,定时器预分频系数与之前的90MHz相关,需要通过预分频系数和自动重载值将定时时间设为5ms【200Hz】,不会算的看我定时器博客(这里我用了一个不常用的定时器,用哪个都可以,只要注意好定时器隶属哪根时钟线就行)

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FTV2F0ZXJiZW5iZW4=,size_16,color_FFFFFF,t_70.jpg


 楼主| 观海 发表于 2021-8-3 15:41 | 显示全部楼层

7. 使能FreeRTOS并创建一个串口任务和一个互斥量(互斥量用于串口数据接收完成后进入串口任务)

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FTV2F0ZXJiZW5iZW4=,size_16,color_FFFFFF,t_70.jpg
watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FTV2F0ZXJiZW5iZW4=,size_16,color_FFFFFF,t_70.jpg


 楼主| 观海 发表于 2021-8-3 15:42 | 显示全部楼层

8. 设置两个IO输出作为一个可视化依据

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FTV2F0ZXJiZW5iZW4=,size_16,color_FFFFFF,t_70.jpg
9. 项目命名、选择使用的程序编辑软件我的是MDK_ARM V5、分离.c.h文件,点击右上角GENERATE CODE生成工程
watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FTV2F0ZXJiZW5iZW4=,size_16,color_FFFFFF,t_70.jpg

  • 打开项目,先编译没有错误之后再进行修改

 楼主| 观海 发表于 2021-8-3 16:32 | 显示全部楼层
代码功能实现
首先在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 */



 楼主| 观海 发表于 2021-8-3 16:33 | 显示全部楼层
串口中断回调函数,在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 */


 楼主| 观海 发表于 2021-8-3 16:34 | 显示全部楼层
在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 */
}


 楼主| 观海 发表于 2021-8-3 16:54 | 显示全部楼层
实验结果
编译下载程序,打开串口调试助手,用串口转USB模块将串口3接入电脑,使用串口调试助手测试
519636108ffc5215c9.png
上图为测试结果,发送ASWaterbenben后回复相同内容,每帧数据用换行符隔开,至此,实验成功!

DS0灯200ms反转一次,每次接收到一帧完整数据DS1灯就会反转一次。这个效果难以展示,你们自己试试就好!


51xlf 发表于 2021-8-4 20:26 | 显示全部楼层
用FreeRTOS+DMA吧  
i1mcu 发表于 2021-8-4 20:26 | 显示全部楼层
freertos有多少个api函数
pmp 发表于 2021-8-4 20:26 | 显示全部楼层
怎么设置freertos的最大任务数  
mmbs 发表于 2021-8-4 20:26 | 显示全部楼层
freertos能用全局变量吗
1988020566 发表于 2021-8-4 20:27 | 显示全部楼层
stm32 freertos 之串口中断   
lzbf 发表于 2021-8-4 20:27 | 显示全部楼层
你要带FreeRTOS的  
youtome 发表于 2021-8-4 20:28 | 显示全部楼层
使用MODBUS-RTU标准协议通过串口进行通信  
cemaj 发表于 2021-8-4 20:28 | 显示全部楼层
FreeRTOS多任务共用串口?
jimmhu 发表于 2021-8-4 20:29 | 显示全部楼层
FreeRTOS中串口接收数据中断  
您需要登录后才可以回帖 登录 | 注册

本版积分规则

153

主题

4385

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部