应用场景
比赛需要
我准备电赛的时候参加了学校为了准备电赛而举办的的积分赛,队友通过树莓派用给stm32发送执行指令,而我在队里作为写单片机的就需要分析数据包,每一个数据包都比较大也比较复杂,而且不定长,用传统的一个字节一个字节接收数据的方式收串口在代码层面上就显得和很复杂,因此我需要一个能定长接收数据并分析的方法。
ESP-01s
在我之前用AT指令玩ESP-01s模块的时候,服务器下发的数据往往是不定长的,因此我也需要一个用单片机接收不定长数据的方式。
因此我在网上找了一些方法,整合了一些我认为比较优雅的基于stm32的解决方案。
废话少说,开始实现。
原理
因为我手上拥有最多的单片机是stm32f401ccu6,因此这里以stm32f401为例演示
根据stm32f4的数据手册
从这张表中可以看出stm32有RXNE(RX寄存器非空,代表有数据输入)中断,和IDLE(串口空闲,代表发送端发送结束)中断,那么我们只需要从有数据输入,一直接受直到串口出现空闲即可接收下整包不定长的数据。
但是在此之前,如果配置RXNE中断,发送端每发送一个字节,CPU就要进一次中断去处理该字节,那这样就和一个个字节接收无异了,那这时候就需要请出CPU的小弟:DMA出场,让每个字节进入单片机时都产生一个接收请求,然后让DMA一个个字节搬入内存,CPU在IDLE中断中再对所有数据进行处理,并重置DMA,则可大大减小CPU的占用,也方便了代码的编写。
STM32CUBEMX配置
配置Debug接口
配置HSE和LSE(LSE其实可以不配)
配置时钟树
配置USART1为异步模式
打开USART1全局中断
打开USART1 DMA接收请求,配置默认就好,主要的是Increment Address勾选Memery,Data Width外设和内存都选择BYTE(这两个必须相同)
然后接下来的就是工程配置,那个就按照自己的情况来,该怎么样就怎么样
代码编写
在USER CODE BEGIN Include之后加上两个头文件
#include "stdio.h"
#include "string.h"
点开魔术棒勾选Use MicroLIB
在工程的任意地方添加重定向代码(我这里添加在了/* USER CODE BEGIN 4 */的后面)
int fputc(int ch, FILE *f)
{
// HAL_UART_Transmit (&huart2 ,(uint8_t *)&ch,1,HAL_MAX_DELAY);
while((USART1->SR&0x40)==0);
USART1->DR = (uint8_t)ch;
//采用轮询方式发送一个字节的数据,没有发送成功就一直等待
return ch;
}
int fgetc(FILE *f)
//int fgetc(int ch, FILE *F)
{
uint8_t ch;
HAL_UART_Receive(&huart1 ,(uint8_t *)&ch,1,HAL_MAX_DELAY );
return ch;
}
在工程的任意位置添加串口接收事件服务函数(我这里添加在了/* USER CODE BEGIN 4 */的后面)
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart == &huart1)
{
//获取帧长
usart1_data_len = 1024 - huart->hdmarx->Instance->NDTR;
sscanf(usart1_rx_buf,"N%dM%d",&test1,&test2);
HAL_UART_Transmit(&huart1,usart1_rx_buf,usart1_data_len,100);
printf("%d,%d\r\n",test1,test2);
//使dma从头存数据
huart->hdmarx->Instance -> NDTR = 1024;
//重新打开dma接收,ILDE中断
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,usart1_rx_buf,1024);
}
}
在主函数的while(1)之前加上一句话
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,usart1_rx_buf,1024);
这句话是串口接收数据直至IDLE事件,记得定义usart1_rx_buf作为串口接收数据缓冲区,第三个参数是你定义的接收缓冲区的大小,建议用宏定义,我这里为了演示就懒得用宏定义了XD.
这样就程序就编写完成啦,目前的程序作用是:串口使用DMA接收数据至IDLE事件,当IDLE事件发生时进入中断服务函数对数据进行处理。
细心的小伙伴应该已经发现中断服务函数中不仅有一个
HAL_UART_Transmit(&huart1,usart1_rx_buf,usart1_data_len,100);
将接收的数据从新发送,还有一个
sscanf(usart1_rx_buf,“N%dM%d”,&test1,&test2);
用于按格式提取数据,当发送端是使用print来发送数据的,那么它每个字节都是用ASCII编码的,例如对方printf(“%d”,123)的时候,发送的数据为0X31 0X32 0X33,这就对我们接收端分析数据包时产生了障碍。
因此我们可以使用stdio.h中的sscanf函数对数据包按照格式提取数据
例如我这里格式为:“N%dM%d”
则发送端按照这个格式发送数据的话,我就可以把这两个%d位置的整数提取出来
所以以上代码的实际效果be like:
总结&扩展
总的来说,大致思路就是,打开USART1的中断,打开USART1的DMA接收请求,让DMA一个个字节搬运数据到内存中,然后在检测串口空闲,当串口空闲时,代表发送端发送完毕,则进入中断服务函数中处理数据包
看起来是不是很优雅?
但是这还是有部分不是很优雅的地方,例如:
当发送端因某些原因发送数据的速度较慢,使得每发送一个字节之间时间间隔较大,而在字节之间出现空闲,则目前这个方法就不适用,会出bug,大家可以自己想想为什么会出bug,这里就不过多赘述。
而有了这个方法,爱动脑子的小伙伴应该发现了这个方法的一个新用途。
既然可以按照格式发送接收数据,那么在两个单片机之间,用串口收发浮点数将不再是难事。
例如可以将中断服务函数改为:
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart == &huart1)
{
//获取帧长
usart1_data_len = 1024 - huart->hdmarx->Instance->NDTR;
sscanf(usart1_rx_buf,"N%fM%f",&test1,&test2);
HAL_UART_Transmit(&huart1,usart1_rx_buf,usart1_data_len,100);
printf("%.2f,%.2f\r\n",test1,test2);
//使dma从头存数据
huart->hdmarx->Instance -> NDTR = 1024;
//重新打开dma接收,ILDE中断
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,usart1_rx_buf,1024);
}
}
则在发送端和接收端之间则可以直接使用printf函数来实现浮点数的收发,而再也不需要将浮点数拆成一个个字节发送,然后再在接收端将数据拼起来了
效果be like:
文章来源:https://www.toymoban.com/news/detail-757460.html
好啦,此贴到这里就结束啦,希望各位能开发出更优雅的串口收发的方式,该贴为博主第一次写文章,若是有任何问题欢迎讨论 😄文章来源地址https://www.toymoban.com/news/detail-757460.html
到了这里,关于极度优雅的用stm32串口接收并分析不定长数据的方法(可用于发送和接收浮点数)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!