13、USART串口数据包

一、发送HEX数据包

1、固定包长,含包头包尾(包尾不是必须的)

img

2、可变包长,含包头包尾

img

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

  • 限制载荷数据的范围,以避免与包头包尾冲突,例如只发送特定范围的数据(0~100)。
  • 使用固定长度的数据包,这可以确保包头和包尾的位置始终一致。
  • 增加包头包尾的数量,以确保它们不会与载荷数据相重叠。

包头包尾数量的选择问题:

  • 在某些情况下,只需要一个包头而无需包尾,这取决于具体的通信需求和协议设计。

固定包长和可变包长的选择问题:

  • 对于HEX(十六进制)数据,如果存在与包头包尾重复的情况,最好选择固定包长,以避免接收到错误的数据。
  • 如果不会出现重复问题,可以选择可变包长以更有效地传输数据。

数据流的处理问题:

  • 可以将各种数据类型(例如16位整型、32位整型、浮点数、双精度浮点数、结构体)转化为字节数组,然后通过指向该字节数组的指针来发送数据。
  • 这允许您将各种数据类型转换为适合传输的字节数组,以便进行有效的数据流处理。

二、接收HEX数据包

img

每收到一个字节,函数都会进入一次中断,在中断函数中,可以拿到一个字节,但拿到字节之后,就得退出中断,故每拿到一个数据,都是一个独立的过程,而对数据包来说,有数据、包头、包尾三种状态,根据状态不同处理也不同

使用状态机收数据如上图

三、发送文本数据包

img

四、接收文本数据包

img

五、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)

1
2
3
4
5
6
7
8
uint8_t Serial_TxPacket[4];  

void Serial_SendPacket(void)
{
Serial_SendByte(0xFF);
Serial_SendArray(Serial_TxPacket, 4);
Serial_SendByte(0xFE);
}

(2)主函数中添加

1
2
3
4
5
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

1
extern uint8_t Serial_TxPacket[];

2、串口发送HEX数据包

(1) Serial.c

在接收中断函数中,需要用状态机来执行接收逻辑,接收数据包,然后把载荷数据存在RxPacket数组里

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include "stm32f4xx.h" // Include appropriate STM32 library

#define RX_STATE_WAITING_HEADER 0
#define RX_STATE_RECEIVING_DATA 1
#define RX_STATE_WAITING_TRAILER 2

uint8_t Serial_RxPacket[4];
uint8_t Serial_RxFlag = 0;

void USART1_IRQHandler(void) {
static uint8_t RxState = RX_STATE_WAITING_HEADER;
static uint8_t pRxPacket = 0;

if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
uint8_t RxData = USART_ReceiveData(USART1);

switch (RxState) {
case RX_STATE_WAITING_HEADER:
if (RxData == 0xFF) {
RxState = RX_STATE_RECEIVING_DATA;
pRxPacket = 0;
}
break;

case RX_STATE_RECEIVING_DATA:
Serial_RxPacket[pRxPacket] = RxData;
pRxPacket++;
if (pRxPacket >= 4) {
RxState = RX_STATE_WAITING_TRAILER;
}
break;

case RX_STATE_WAITING_TRAILER:
if (RxData == 0xFE) {
RxState = RX_STATE_WAITING_HEADER;
Serial_RxFlag = 1;
}
break;
}

USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}

(2)主函数显示发送数据

将单片机接收到的数据(即串口调试助手发送的数据)显示在OLED显示屏上

1
2
3
4
5
6
7
8
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

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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,在函数退出后,数据仍然有效,与全局变量不同的是,静态变量只能在本函数使用。