单片机串口接收状态机STM32

ops/2024/11/14 16:14:29/

stm32_0">单片机串口接收状态机stm32

前言

项目的芯片stm32转国产,国产芯片的串口DMA接收功能测试不通过,所以要由原本很容易配置的串口空闲中断触发DMA接收数据的方式转为串口逐字节接收的状态机接收数据

两种方式各有优劣,不过我的芯片已经主频跑到72m,对于接收115200波特率的数据,绰绰有余。

给一张图,接收状态机就是设置串口每次接收1byte的数据就触发一次中断,在中断函数里面逐次统计数据,最后把有用的数据包放进缓存区给到处理区

协议图.drawio

1.首先现在cubemx的串口的dma接收关闭,具体配置看截图,然后导出下代码

51a112434e8285a60144985df54b.png" alt="image" />​

image

image

2.开始配置代码,这里我用的代码编辑器是vscode,个人认为vscode编辑代码+keil调试的方式是最舒服的,有兴趣的可以试试,绝对提一档。

51f415c331d45adb47a0b03b4ff95fe.png" alt="image" />​

先定义一个uint8_t的变量用来接收每次收到的数据,一定注意,只要你是用的cubemx生成的project,在生成的文件下,都有把你改的代码写到/* USER CODE BEGIN 0 */​下面,不然等你下次在cubemx更改底层配置的时候会把你改好的代码擦洗掉。

uint8_t USART1_rxdata = 0;

image

往下滑,找到这个串口1的初始化函数,加这个串口接收中断启动函数

HAL_UART_Receive_IT(&huart1, &USART1_rxdata, 1);

然后我们来到中断c文件下,在串口1中断里增加我们的接收状态机

image

  rec_buff_scan(USART1_rxdata);   HAL_UART_Receive_IT(&huart1, &USART1_rxdata, 1); 

这里的rec_buff_scan就是我们定义的函数,因为我的习惯是在我自己创建的文件下写代码,避免更改cube生成的东西,所以文件名可能就不一样,这里就只看代码就好了

首先先把定义全部cv下来,我们协议是[0XAA,0X55,命令包长度,…(命令包内容),校验和高位,校验和底位,的格式,如果你们的不一样就改下RecState里面的变量名

image

#define REC_BUFF_SIZE 100
uint8_t rec_buf_scan[REC_BUFF_SIZE];    // 数据接收缓冲区
uint8_t rec_index = 0;                  // 数据接收索引
extern uint8_t USART1_rxdata;                  // 存储接收的单个字节// 状态枚举
typedef enum {STATE_WAIT_FOR_HEAD1,      // 等待帧头AASTATE_WAIT_FOR_HEAD2,      // 等待帧头55STATE_WAIT_FOR_LENGTH,     // 等待命令包长度STATE_RECEIVE_DATA,        // 接收命令包数据STATE_WAIT_FOR_CHECKSUM1,  // 等待校验和字节1STATE_WAIT_FOR_CHECKSUM2   // 等待校验和字节2
} RecState;RecState rec_state = STATE_WAIT_FOR_HEAD1;  // 初始状态
uint8_t packet_length = 0;                  // 数据包长度
uint16_t checksum = 0;  

然后把状态机整段cv,状态机就是把接收部分分成一个个的状态,条件符合就会跳到下一段,最后会在校验和验证整段数据是否接收正常,正常的话就会送入数据处理的函数里面,我这里定义了一个rec_buf_scan[REC_BUFF_SIZE]是跟我之前的接收数据缓存区做兼容而已,你们可以只导入数据包的内容,不需要把头和校验和导入,这里其实就是我懒了嘻嘻

我这里的处理其实你们不用借鉴的 ,直接在STATE_WAIT_FOR_CHECKSUM2状态判断校验和成功后,把接收成功flag=true;然后在你另外的处理函数就可以处理数据包了,这里的memcpy一下,也是很有必要的,我们的处理缓存和接收缓存一定要区分开来,因为没有自锁,所以只能在成功的时候把接受的数据塞入处理缓存,至于我这里为什么塞了全部数据而不是只有数据包,主要是配合我以前的处理,无需借鉴,总之你上班就明白我的难处,要改来改去还要兼容是很麻烦的事情

image

void rec_buff_scan(uint8_t byte) {switch (rec_state) {case STATE_WAIT_FOR_HEAD1:if (byte == 0xAA) {rec_state = STATE_WAIT_FOR_HEAD2;rec_index = 0;  // 重置接收索引checksum = 0;   // 重置校验和rec_buf_scan[rec_index++] = byte;//可以不用导入}break;case STATE_WAIT_FOR_HEAD2:if (byte == 0x55) {rec_state = STATE_WAIT_FOR_LENGTH;rec_buf_scan[rec_index++] = byte;//可以不用导入} else {rec_state = STATE_WAIT_FOR_HEAD1;}break;case STATE_WAIT_FOR_LENGTH:packet_length = byte;rec_buf_scan[rec_index++] = byte;//可以不用导入rec_state = STATE_RECEIVE_DATA;break;case STATE_RECEIVE_DATA:if (rec_index-3 < packet_length) {//这里if里面的条件把-3去掉 rec_buf_scan[rec_index++] = byte;}if (rec_index-3 == packet_length) {//这里if里面的条件把-3去掉 checksum = Frame_CalculationChecksum(&rec_buf_scan[3], packet_length);  // 计算校验和rec_state = STATE_WAIT_FOR_CHECKSUM1;}break;case STATE_WAIT_FOR_CHECKSUM1:if (byte == ((checksum >> 8) & 0xFF)) {rec_state = STATE_WAIT_FOR_CHECKSUM2;rec_buf_scan[rec_index++] = byte;//可以不用导入} else {rec_state = STATE_WAIT_FOR_HEAD1;}break;case STATE_WAIT_FOR_CHECKSUM2:if (byte == (checksum & 0xFF)) {//这里做你自己的处理就好了 ,程序跑到这里就已经验证通过了rec_buf_scan[rec_index++] = byte;//可以不用导入memcpy(&rec_buff[0], &rec_buf_scan[0], rec_index);Device_data.device_state = JUDGE_FLAG;Data_queue_rx.Interrupt_Len = rec_index;}// 无论校验是否通过,回到初始状态rec_state = STATE_WAIT_FOR_HEAD1;break;}
}

用到的Frame_CalculationChecksum函数是校验和计算函数,具体操作就是把接收的数据需要校验的那一段的第一个元素地址放进去,把数据包长度放进去,他会算完把结果返回,我这里就是简单的数据包加和校验

uint16_t Frame_CalculationChecksum(uint8_t *pData, uint8_t u8Length)
{uint16_t u16check_sum = 0;uint8_t i;for (i = 0; i < u8Length; i++){u16check_sum = u16check_sum + pData[i];}return u16check_sum;
}

这样基本上就把接收状态机整完了,可以仿真下试试看。

3.仿真测试

可以看到断点在第二步的时候0xaa已经存入数据缓存去了

image

同理我们直接看到接收成功这里打断点,可以看到我们在把接收成功时,已经把所有数据都塞进处理缓存区了。这里再严谨点最好是在移完数据后把接收缓存清零一下,但是不清也不影响。

image

5180c344d0b8f70a5df95efbccc.png" alt="image" />​

4.小结

串口中断状态机是最基本的协议解析接收方式,作为一个嵌入式人员这个你必须要学会的,写法不限制,但是流程就是跟我的差不多的,具体根据协议来定的。

这种接收方式适合没有dma外设的单片机比如51单片机,国产单片机,在项目开发完成进行降本的时候也是可能会改到这种方式的,如果有不规范的地方请留言,我会更改。


http://www.ppmy.cn/ops/132885.html

相关文章

数据结构---排序总结

1.排序的时间复杂度&#xff08;均为平均值&#xff09; O(n^2) &#xff1a;冒泡排序&#xff0c;选择排序&#xff0c;插入排序。 O(n * log(n))&#xff1a;堆排序&#xff0c;快速排序&#xff0c;归并排序。 O(n^1.3)&#xff1a;希尔排序 2.空间复杂度&#xff1a; O(n) …

gitlab ci/cd搭建及使用笔记

记录下使用gitlab的ci/cd的devops构建过程中&#xff0c;一些易忘点或者踩坑点&#xff1a; 官方文档中英文&#xff08;建议英文&#xff09; https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html https://gitlab.cn/docs/jh/ci/pipelines/schedules.html为什么创建了…

Spring Boot实战:SSO和OAuth2.0

SSO和OAuth2.0 SSO&#xff08;单点登录&#xff09;和OAuth 2.0 是两个在认证和授权场景中常用的技术概念。它们的应用场景、目标和工作机制不同&#xff0c;但在一些方面也有联系。以下是它们的区别和联系的详细分析。 一、SSO&#xff08;Single Sign-On&#xff09; 1.1…

【vue2.0入门】初始化vue工程

目录 引言一、前期准备1. 熟悉如何调起控制台1.1 全局打开控制台1.2 指定目录打开控制台1.3 推荐使用vscode集成控制台 2. 设置npm镜像3. 安装vue脚手架工具 二、创建vue工程1. 利用vue-cli创建工程2. 启动项目3. 打包项目 三、总结 引言 本系列教程旨在帮助一些零基础的玩家快…

pdf添加目录标签python(手动配置)

先安装对应的库: pip install pypdf 代码分为两个部分,一部分是config.py,代码如下: offset=10 catgorys=[("第一章",12),("第二章",45), ] 需要自己手动更改offset,和目录列表 下面是主要代码: import pypdf # import sys from config import…

linux命令详解,存储管理相关

存储管理 一、内存使用量&#xff0c;free free 命令是一个用于显示系统中物理内存&#xff08;RAM&#xff09;和交换空间&#xff08;swap&#xff09;使用情况的工具 free -m free -m -s 5参数 -b 功能: 以字节&#xff08;bytes&#xff09;为单位显示内存使用情况。说…

软件工程 软考

开发大型软件系统适用螺旋模型或者RUP模型 螺旋模型强调了风险分析&#xff0c;特别适用于庞大而复杂的、高风险的管理信息系统的开发。喷泉模型是一种以用户需求为动力&#xff0c;以对象为为驱动的模型&#xff0c;主要用于描述面向对象的软件开发过程。该模型的各个阶段没有…

MFC中Excel的导入以及使用步骤

参考地址 在需要对EXCEL表进行操作的类中添加以下头文件&#xff1a;若出现大量错误将其放入stdafx.h中 #include "resource.h" // 主符号 #include "CWorkbook.h" //单个工作簿 #include "CRange.h" //区域类&#xff0c;对Excel大…