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);		
	}
 }

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

相关推荐
my_daling1 小时前
紫光FPGA远程升级注意事项(关于GTP_CFGCLK原语使用问题)
笔记·学习·fpga开发
zr想努力1 小时前
GAS学习(UE5自用)
学习·ue5
她说..1 小时前
MySQL JSON 处理学习文档
学习·mysql·json
资讯综合2 小时前
一年级数学机构怎么选?与学习阶段相匹配的线上思维课程与线下机构差异分析
学习
M78佐菲2 小时前
51单片机学习笔记(2)
linux·笔记·嵌入式硬件·学习·51单片机
子不语1802 小时前
LabVIEW和PLC通过OPC通信
学习·自动化·plc
萧行之2 小时前
Observable Plot 源码深度解析——4 核心抽象逐项学习
前端·学习·数据可视化
浔溺2 小时前
al+大数据每日学习笔记37
大数据·笔记·学习
TheBestRucy3 小时前
PyTorch 基本使用学习笔记
pytorch·笔记·学习