目录
一、发送HEX数据包
1、固定包长,含包头包尾(包尾不是必须的)
2、可变包长,含包头包尾
1、包头包围和数据载荷重复的问题,传输的数据本身是FF和FE,可能引起误判
解决:限制载荷数据的范围,限幅(例如只发送0~100)
如果无法避免数据于包头包尾重复,则尽量使用固定长度的数据包
增加包头包尾的数量,尽量是其呈现出载荷数据出现不了的状态
2、包头包尾并不是全部都需要的,例如可以只要一个包头
3、固定包长和可变包长的选择问题
(1)对HEX来说,若载荷出现和包头包尾重复的情况,最好选择固定包长,避免接受错误
(2)若不重复,可以选择可变包长
4、各种数据转化为数据流的问题
数据包都是一个字节一个字节组成的,若想发送16位整型数据、32位整型数据,float、double、甚至是结构体(其内部都是由一个字节一个字节组成的),只需要用一个uint8_t的指针指向它,把数据当作字节数组发送即可
二、接收HEX数据包
每收到一个字节,函数都会进入一次中断,在中断函数中,可以拿到一个字节,但拿到字节之后,就得退出中断,故每拿到一个数据,都是一个独立的过程,而对数据包来说,有数据、包头、包尾三种状态,根据状态不同处理也不同
使用状态机收数据如上图
三、发送文本数据包
四、接收文本数据包
五、HEX数据包和文本数据包的比较
(1)在hex数据包中,数据都是以原始的字节数据本身呈现的
(2)在文本数据包中,每个字节就经过一层编码和译码,最终表现出文本格式(文本背后还是一个字节的HEX数据)
(3)hex数据包:传输直接、解析数据简单,适合一些模块发送原始的数据,比如一些使用串口通信的陀螺仪、温湿度传感器,但是灵活性不足、载荷容易和包头包尾重复
(4)文本数据包:数据直观易理解、灵活,适合一些输入指令进行人机交互,但解析效率低.
(5)发送100,hex直接发送一个字节100,而文本发送三个字节'1','0'.'0',收到之后还要把字符转换程数据,才能得到100。
六、串口收发HEX数据包&串口收发文本数据包
1、串口发送HEX数据包
(1)Serial.c
将uint8_t Serial_GetRxData(void)替换成void Serial_SendPacket(void)
uint8_t Serial_TxPacket[4];
void Serial_SendPacket(void)
{
Serial_SendByte(0xFF);
Serial_SendArray(Serial_TxPacket, 4);
Serial_SendByte(0xFE);
}
(2)主函数中添加
Serial_TxPacket[0] = 0x01;
Serial_TxPacket[1] = 0x02;
Serial_TxPacket[2] = 0x03;
Serial_TxPacket[3] = 0x04;
Serial_SendPacket();
将程序下载到开发板中,按下复位键盘,在串口调试助手中显示FF 01 02 03 04 FE
(3)Serial.h
extern uint8_t Serial_TxPacket[];
2、串口发送HEX数据包
(1) Serial.c
在接收中断函数中,需要用状态机来执行接收逻辑,接收数据包,然后把载荷数据存在RxPacket数组里
void USART1_IRQHandler(void)
{
static uint8_t RxState = 0;
static uint8_t pRxPacket = 0;
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
uint8_t RxData = USART_ReceiveData(USART1);
if (RxState == 0)
{
if (RxData == 0xFF)
{
RxState = 1;
pRxPacket = 0;
}
}
else if (RxState == 1)
{
Serial_RxPacket[pRxPacket] = RxData;
pRxPacket ++;
if (pRxPacket >= 4)
{
RxState = 2;
}
}
else if (RxState == 2)
{
if (RxData == 0xFE)
{
RxState = 0;
Serial_RxFlag = 1;
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
(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);
}
(3)主函数添加key
添加了key按键,将数据发送出去的数据显示在OLED,按一次健,发送数据都+1
KeyNum = Key_GetNum();
if (KeyNum == 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);
}
3、串口收发文本数据包
(1) Serial.c
char Serial_RxPacket[100];
void USART1_IRQHandler(void)
{
static uint8_t RxState = 0;
static uint8_t pRxPacket = 0;
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
uint8_t RxData = USART_ReceiveData(USART1);
if (RxState == 0)
{
if (RxData == '@' && Serial_RxFlag == 0)
{
RxState = 1;
pRxPacket = 0;
}
}
else if (RxState == 1)
{
if (RxData == '\r')
{
RxState = 2;
}
else
{
Serial_RxPacket[pRxPacket] = RxData;
pRxPacket ++;
}
}
else if (RxState == 2)
{
if (RxData == '\n')
{
RxState = 0;
把读取标志位然后立即清0的函数删掉,在主函数中,判断Serial_RxFlag == 1,表示接收到数据包,等操作完成之后,再清0,在中断函数中,只有Flag=0了,才会继续接收到下一个数据包。这样数据的读写就是严格分开,不会混淆,但是这样发送数据包的频率不能太快,否则会被丢弃。或者定义一个指令缓存区,把接受好的字符串放在这个指令缓存区进行排队
七、静态变量
函数进入只会初始化一次0,在函数退出后,数据仍然有效,与全局变量不同的是,静态变量只能在本函数使用。