数据包:将多个数据打包,以便进行多个字节的通信。还要对这多个数据进行分割,防止连续发送数据包的时候接收数据错位。

串口数据包常用分割方式是添加包头包尾

如图,0xFF、@就是包头,接收到它们就知道后面是一个数据包了;0xFE、\r\n换行就是包尾,接收到说明数据结束了。包头包尾可以自己设置。

包头包尾和载荷数据重复问题

1. 可以限制载荷数据的范围,从几到几,而包头包尾在这个范围外,就能避免重复导致误判。

2. 如果不能限制范围就使用固定包长,这样就能分辨哪个地方开始是数据。

3. 还可以增加包头包尾的数量,就能降低重复率。

可以只要包头不要包尾,这样也能接收,但重复概率更大。

如果确定数据不会和包头包尾重复,那么可以选择可变包长;如果有重复几率,可变包长更容易导致误判,最后选择固定包长

HEX数据包容易遇到重复问题,因为是以原始数据显示的;但文本数据包是以字符形式显示,可以使用大量字符,遇到重复的概率很小,并且字符表达意思很直观。

HEX数据包优点是传输直接,数据解析也简单,适合模块发送原始数据,如陀螺仪,传感器;缺点是缺乏灵活性,数据容易和包头包尾重复

文本数据包优点是数据直观容易理解,具有灵活性,适合人机交互的场合;缺点是数据解析麻烦

数据包发送

发送比较简单,如果是HEX数据包,就定义一个数组,写入包头数据包尾,然后通过串口发送数组函数发送;如果是文本数据包,就把包头数据包尾写入一个字符串中,通过串口发送字符串函数发送。

数据包接收(状态机)

在不同状态下做不同工作,同时实现状态转移,就是状态机

例如接收固定包长HEX数据包:在状态S=0下接收包头0xFF,如果在中断里接收到数据是0xFF则跳转到状态S=1,然后退出中断;

下一次接收到数据进入中断,是状态S=1就说明是数据,将数据存入数组,然后退出中断,同时应该有一个变量来判断固定包长的数据收够没有,如果收够了就跳转到状态S=2,退出中断;

下一次进入中断时是S=2就可以接收包尾了,收到包尾0xFE就跳转到状态S=0,退出中断。

当不满足状态跳转条件时状态不改变。

又比如接收可变包长文本数据包:状态0到状态1都差不多,不同的是由于包长可变,所以在状态1下除了接收数据还要检测包尾,如果接收到包尾\r就跳转到状态2,去接收包尾\n,\n后跳转状态0。因为这里包尾有两个,如果只有一个就可以直接跳转回状态0

收发HEX数据包代码

串口.c文件

uint8_t serial_TXpacket[4];//存发送的数据包
uint8_t serial_RXpacket[4];//存接收的数据包
uint8_t RXflag=0;//接收数据标志位

void serial_init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//USART1的TX是PA9,RXPA10
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使用USART1
	
	//初始化GPIO
	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_IPU;//上拉输入,或者浮空输入,因为空闲时默认高电平
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//初始化USART
	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;//8位
	USART_Init(USART1,&USART_InitStructure);
	
	//打开中断
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	
	//配置NVIC
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	NVIC_Init(&NVIC_InitStructure);
	
	//开启USART
	USART_Cmd(USART1,ENABLE);
}

//发送数据包
void serial_sendpacket(void)
{
	serial_send(0xFF);
	serial_array(serial_TXpacket,4);
		serial_send(0xFE);
}

uint8_t serial_getRXflag(void)//返回值 标志位
{
	if(RXflag==1)
	{
		RXflag=0;
		return 1;
	}
	return 0;
}

void USART1_IRQHandler(void)//中断函数中使用状态机
{
	static uint8_t RXstate=0;
	static uint8_t count=0;
	if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)
	{
		uint8_t RXDATA=USART_ReceiveData(USART1);
		if(RXstate==0)
	{
		if(RXDATA==0xFF)
		{
			RXstate=1;
			count=0;
		}
	}
	else if(RXstate==1)
	{
		serial_RXpacket[count]=RXDATA;
		count++;
		if(count>=4)
		{
			RXstate=2;
		}
	}
	else if(RXstate==2)
	{
		if(RXDATA==0xFE)
		{
			RXstate=0;
			RXflag=1;
		}
	}
	}
}

串口.h文件

#ifndef __SERIAL_H_
#define __SERIAL_H_

void serial_init(void);
void serial_send(uint8_t byte);
void serial_array(uint8_t *array,uint16_t length);
void serial_string(uint8_t *string);
void serial_number(uint32_t num,uint16_t length);
uint8_t serial_receive(void);
void serial_sendpacket(void);
extern uint8_t serial_TXpacket[4];
uint8_t serial_getRXflag(void);
extern uint8_t serial_RXpacket[4];
#endif

主函数

uint8_t keynum;
int main()
{
	OLED_Init();
	key_init();
	serial_init();
	
	OLED_ShowString(1,1,"TXpacket:");
	OLED_ShowString(3,1,"RXpacket:");
	serial_TXpacket[0]=0x01;
	serial_TXpacket[1]=0x02;
	serial_TXpacket[2]=0x03;
	serial_TXpacket[3]=0x04;
 while(1)
 {
	 if(key_num()==1)
	 {
		serial_TXpacket[0]++;
		serial_TXpacket[1]++;
		serial_TXpacket[2]++;
		serial_TXpacket[3]++;
		serial_sendpacket();
		 OLED_ShowHexNum(2,1,serial_TXpacket[0],2);
		OLED_ShowHexNum(2,4,serial_TXpacket[1],2);
		OLED_ShowHexNum(2,7,serial_TXpacket[2],2);
		 OLED_ShowHexNum(2,10,serial_TXpacket[3],2);//按一下按键,数据包里的数据就++并发送数据包,同时显示在OLED
	 }

	if(serial_getRXflag()==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);
	}
 }
}

收发文本数据包代码

发数据包用发字符串的函数就可以了。

串口.c文件

char serial_RXpacket[100];//存接收的数据包
uint8_t RXflag=0;//接收数据标志位
void serial_init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//USART1的TX是PA9,RXPA10
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使用USART1
	
	//初始化GPIO
	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_IPU;//上拉输入,或者浮空输入,因为空闲时默认高电平
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//初始化USART
	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;//8位
	USART_Init(USART1,&USART_InitStructure);
	
	//打开中断
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	
	//配置NVIC
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	NVIC_Init(&NVIC_InitStructure);
	
	//开启USART
	USART_Cmd(USART1,ENABLE);
}

uint8_t serial_getRXflag(void)//返回值标志位
{
	if(RXflag==1)
	{
		RXflag=0;
		return 1;
	}
	return 0;
}

void USART1_IRQHandler(void)//中断函数中使用状态机
{
	static uint8_t RXstate=0;
	static uint8_t count=0;
	if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)
	{
		uint8_t RXDATA=USART_ReceiveData(USART1);
		if(RXstate==0)
	{
		if(RXDATA=='@')//包头
		{
			RXstate=1;
			count=0;
		}
	}
	else if(RXstate==1)
	{
		if(RXDATA=='\r')//包尾
		{
			RXstate=2;
		}
		else{
		serial_RXpacket[count]=RXDATA;
		count++;
	}
	}
	else if(RXstate==2)
	{
		if(RXDATA=='\n')//包尾
		{
			RXstate=0;
			RXflag=1;
			serial_RXpacket[count]='\0';
		}
	}
	}
}

串口.h文件

#ifndef __SERIAL_H_
#define __SERIAL_H_

void serial_init(void);
void serial_send(uint8_t byte);
void serial_array(uint8_t *array,uint16_t length);
void serial_string(uint8_t *string);
void serial_number(uint32_t num,uint16_t length);
uint8_t serial_receive(void);
uint8_t serial_getRXflag(void);
extern char serial_RXpacket[100];
#endif

主函数

int main()
{
	OLED_Init();
	serial_init();
	led_init();
	OLED_ShowString(1,1,"TXpacket:");
	OLED_ShowString(3,1,"RXpacket:");
 while(1)
 {
	if(serial_getRXflag()==1)
	{
		OLED_ShowString(4,1,"                ");
		OLED_ShowString(4,1,serial_RXpacket);
		if(strcmp(serial_RXpacket,"led on")==0)//接收到led on就开灯
		{
			led1_on();
		}
		else if(strcmp(serial_RXpacket,"led off")==0)//接收到led off就关灯
		{
			led1_off();
		}
		else
		{
			serial_string("error command");//接收到其他指令就显示错误并发送
			OLED_ShowString(2,1,"error command");
		}
	}
	
 }
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐