STM32学习12--串口2收发数据包

本文编写代码,部门C语言复习,参考江科大,侵删!

一、状态机

1、定义:同一时间只能处在某一种固定状态,收到外部触发条件后,切换到另外一种状态,每个状态做固定动作。关键词如下:

有限:状态数量是固定可数的,不会无限多。状态必须提前定义,无法运行时新增。

单一:任意时刻,只有一个当前状态,不能多状态共存。

触发:必须有事件/条件才会切换状态,不会自己乱跳,即状态不会自动跳转;

举例:电灯

状态:1、灯灭;2、灯亮;

触发事件:按开关;

状态流转:

当前 =灯灭 +按开关→切换为灯亮;

当前=灯亮 +按开关→切换为灯灭;

2、四大组成要素

1)状态集合S:所有能存在的模式;(灭,亮)

2)输入/事件结合E:触发状态转换的信号、条件等;(按下开关)

3)状态转移规则T:当前状态+输入 →下一个状态;(灯灭 +按开关→灯亮,灯亮 +按开关→灯灭)

4)输出/动作A :停留在这个状态要执行的行为;(亮状态输出"通电发光",灭状态输出"断电");

3、两种经典状态机

1)Moore状态机:动作/输出只由当前状态决定,和输入无关,比如:灯,只要状态是"亮",就一直发光,不管有没有按键输入;

2)Mealy状态机(常用)

动作/输出由当前状态+当前输入一起决定;比如电梯,状态:停靠1楼;输入:按下3楼按钮;动作:关门上行;即:同一个状态,输入不同,动作不一样。

4、常见逻辑

枚举列举所有状态→定义全局/结构体变量保存当前状态→循环里用switch判断当前状态,每个状态写对应的逻辑。这是最基础轮询式状态机,也是最通用的。

cpp 复制代码
typedef enum{

    State_Idle,//空闲
    State_Run,//运行
    State_Err//故障
}State;

State Curr_State = State_Idle;//定义全局变量初始化

int main()
{
	while(1)
	{
		switch(Curr_State)
		{
			case State_Idle:
                (*此处写空闲时的动作代码*)
				if(触发启动事件)
				{
					Curr_State = State_Run;//切换状态
				}					
				break;
			case State_Run:
                (*此处写运行时的动作代码*)
				if(故障信号)
				{
					Curr_State = State_Err;
				}
                if(运行完成)
                    Curr_State = State_Idle
				break;
			case State_Err:
            (*此处写故障处理时的动作代码*)
				if(复位按键)
				{
					Curr_State = State_Idle;
				}               
				break;
		}				
	}
}

5、状态机优势

1)替代多层if-else,逻辑分层清晰,易维护;

2)不会出现逻辑卡死,分支混乱;

3)自带异常处理分支,出错统一跳转到错误状态复位;

4)适配中断、定时器、通信协议;

6、状态转移表(以3段式设备举例)

|-------|------|-------|---------|
| 当前状态 | 触发事件 | 下一个状态 | 该状态动作 |
| IDLE | 启动信号 | RUN | 待机、低功耗 |
| RUN | 完成信号 | IDLE | 正常工作 |
| RUN | 故障信号 | ERROR | 停止工作 |
| ERROR | 复位信号 | IDLE | 报警、清理故障 |

7、注意点

1)状态是静态模式 ,事件是动态触发信号

2)状态不会自动跳转,必须满足条件才切换;

3)任何时刻只有一个当前状态,不存在同时处于两个状态;

4)有限状态机≠无限状态,状态必须提前定义好,不能动态新增;

二、串口接收HEX数据包和文本数据包

1、HEX数据包

1)固定包长:一整个数据包字节数固定不变。

包头:0xFF,起始标记,判断一帧开始;

包尾:0xFE:结束标记,判断一帧结束;

有效数据是中间的4个字节。

接收流程:

逐个字节接收,抓到字节0xFF,开始缓存→持续存入后续字节,直到遇到0xFE,停止接收→有效数据是中间4个字节;

2)可变长度数据包

包长度不固定,只靠包头+包尾来分割。格式:0xFF+任意长数据+0xFE

接收逻辑:收到0xFF,开始缓存数据,一直往后存,直到收到0xFE,就把这一段当成完整数据包;

可变包长坑点:如果有限数据里刚好包含了0xFF或0xFE,解析会错乱。

举例:0xFF,0x01,0xFE,0x02,0x03,0xFE,程序直接把0xFF~0xFE截断成一个包,剩下数据就错位了。即:数据和包头包尾重复,导致分包出错。

解决方法:

①限制有限数据的取值范围:不让数据出现0xFF、0xFE,只使用剩下的字节数据,从根源避免冲突;

②改用固定长度数据包:只要每个数据包字节长度固定,只靠包头+长度对齐,包尾可以不用,就不会错位。

2)程序编写(固定包长,含包头包尾),接收4个字节数据

Serial初始化

cpp 复制代码
#include "Serial.h"

uint8_t Serial_RxPacket[4];
uint8_t Serial_TxPacket[4];
uint8_t Receive_Flag;
uint8_t Serial_State;

void Serial_Init()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 ;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 ;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);

	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_Init(&NVIC_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Tx |USART_Mode_Rx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength =USART_WordLength_8b;
	USART_Init(USART1,&USART_InitStructure);
	USART_Cmd(USART1,ENABLE);
}
//标志位返回并清零
uint8_t Serial_RxFlag()
{
	if(Receive_Flag ==1)
	{
		Receive_Flag =0;
		return 1;
	}
	return 0;
}

 void USART1_IRQHandler()
 {
	 static uint8_t pRxNum;
	if(USART_GetITStatus(USART1,USART_IT_RXNE) ==SET)
	{
		
		if(Serial_State ==0)
		{
			if(USART_ReceiveData(USART1) ==0xFF)
			{
				Serial_State =1;
				pRxNum =0;
			}		
		}
		else if(Serial_State ==1)
		{
			Serial_RxPacket[pRxNum] =USART_ReceiveData(USART1);
			pRxNum ++;
			if(pRxNum >=4)
			{
				pRxNum =0 ;
				Serial_State =2;
			}
		}
		else if(Serial_State ==2)
		{
			if(USART_ReceiveData(USART1) ==0xFE)
			{
				Serial_State =0;
				Receive_Flag =1;
			}
		}
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);		
	}
 }
 

main函数:

cpp 复制代码
#include "stm32f10x.h"             
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

int main()
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	OLED_Init();
	Serial_Init();
	OLED_ShowString(3,1,"RxPacket");
	
	while(1)
	{
		if(Serial_RxFlag() ==1)
		{
			OLED_ShowHexNum(4,1,Serial_RxPacket[0],2);
			OLED_ShowHexNum(4,4,Serial_RxPacket[1],2);
			OLED_ShowHexNum(4,7,Serial_RxPacket[2],2);
			OLED_ShowHexNum(4,10,Serial_RxPacket[3],2);						
		}
	}
}

2、文本数据包

1)固定包长

整包字节总数严格固定,一整包=1字节包头+2字节数据+2字节包尾,总长:6个字符

接收逻辑:

找到包头@→再接收5个字符→收满6个字节,

2)可变包长(只靠包头+包尾分割)

格式:@+任意长度文本+\r\n,依靠@作为起始,\r\n作为结束切分字符串,但要避免文本内容里自带回车换行\r\n,否则会提前切断,数据乱序。

3)程序编写(可变包长,接收数据)

cpp 复制代码
#include "Serial.h"

char Serial_RxPacket[100];
uint8_t Receive_Flag;

void Serial_Init()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 ;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 ;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);

	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_Init(&NVIC_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Tx |USART_Mode_Rx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength =USART_WordLength_8b;
	USART_Init(USART1,&USART_InitStructure);
	USART_Cmd(USART1,ENABLE);
}
void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1,Byte);
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) ==RESET);
}
 void Serial_SendString(char* String)
 {
	while(*String )
	{
		Serial_SendByte(*String++);
	}
 }
 
 void Serial_SendArray(uint8_t *Array,uint8_t Length)
 {
	uint8_t i=0;
	 for(i=0;i<Length;i++)
	 {
		Serial_SendByte(Array[i]);
	 }
 }
  
uint8_t Serial_RxFlag()
{
	if(Receive_Flag ==1)
	{
		Receive_Flag =0;
		return 1;
	}
	return 0;
} 
 void USART1_IRQHandler()
 {
	 static uint8_t pRxNum =0;
	 static uint8_t Serial_State =0;
	if(USART_GetITStatus(USART1,USART_IT_RXNE) ==SET)
	{
        uint8_t GetData =USART_ReceiveData(USART1);
		if(Serial_State ==0)
		{
			if(GetData =='@')
			{
				Serial_State =1;
				pRxNum =0;
			}		
		}
		else if(Serial_State ==1)
		{
			if(GetData =='\r')
			{				
				Serial_State =2;
			}
			else
			{
				Serial_RxPacket[pRxNum] =GetData;
				pRxNum ++;								
			}
		}
		else if(Serial_State ==2)
		{
			if(GetData=='\n')
			{				
				Serial_State =0;
				Serial_RxPacket[pRxNum]= '\0';
				Receive_Flag =1;
			}
		}			
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);		
	}
 }

本文完!其他方式还在学习中......

相关推荐
Starmoon_dhw1 小时前
题解:P17078 夏日甜点
c++·学习·算法·图论
菩提树下的打坐2 小时前
接口测试利器:REST Assured + WireMock
学习
tyqtyq224 小时前
营养餐单规划:AI 科学饮食规划系统的鸿蒙实现
人工智能·学习·华为·职场和发展·生活·harmonyos
鱼听禅4 小时前
Ubuntu学习笔记-安装docker容器
学习·ubuntu·docker
Loving_enjoy5 小时前
DFT可测试性设计规则与检查:确保芯片可测试性的关键步骤
学习
r_oo_ki_e_5 小时前
Java反射机制学习笔记
java·笔记·学习
依然范特东6 小时前
深度学习入门学习笔记--感知机、神经网络
笔记·深度学习·学习
绝世番茄7 小时前
鸿蒙HarmonyOS ArkTS原生学习:缩放手势实现 PinchToZoom 深度解析
学习·华为·harmonyos·鸿蒙
a1117767 小时前
SLAM 学习笔记(四)后端(KF BA)
笔记·学习