
接下来我按照上图格式来添加串口收发中Hex数据包的部分,固定包长,含包头包尾,其中包头为FF,载荷数据固定4字节,包尾为FE,之前初始化的函数都不需要更改
为了定义数据包,先定义两个缓冲区的数组:
uint8_t Serial_TxPacket[4];
uint8_t Serial_RxPacket[4];//这四个数据只存储发送或接收的载荷数据,不存包头包尾
接下来定义发送数据包函数:
void Serial_SendPacket(void)//调用该数组,TxPacket数组的4个数据就会自动加上包头包尾发送出去。
{
Serial_SendByte(0xFF);//发送包头
Serial_SendArray(Serial_TxPacket, 4);//依次把4个载荷数据发出去
Serial_SendByte(0xFE);//发送包尾
}
在主函数中只要填充数据包,再使用该函数即可:
Serial_TxPacket[0] = 0x01;
Serial_TxPacket[1] = 0x02;
Serial_TxPacket[2] = 0x03;
Serial_TxPacket[3] = 0x04;
Serial_SendPacket();

接下来我们来写接受这个数据包的函数,在接收中断函数里,我们就需要用状态机来执行接收逻辑了,接收数据包,然后把载荷数据存在RxPacket数组里。
根据上面的状态转移图,我们需要先定义一个标志当前状态的变量S,我们可以在中断函数里定义一个静态变量:
static uint8_t RxState = 0;//类似于全局变量,函数进入只会初始化一次0,在函数退出之后,数据仍然有效,与全局变量不同的是,静态变量只能在本函数使用。
根据RxState的不同,我们需要进入不同的处理程序
void USART1_IRQHandler(void)
{
static uint8_t RxState = 0;//状态机指示
static uint8_t pRxPacket = 0;//载荷数据接收数量指示
if (USART_GetITStatus(USART1, USART_IT_RXNE))//如果读取了DR,就会自动清除,如果没读取,就要手动清除
{
uint8_t RxData = USART_ReceiveData(USART1);
if (RxState == 0)//进入等待包头的程序
{
if (RxData == 0xFF)//收到包头
{
RxState = 1;
pRxPacket = 0;//在接收数据之前清零
}
}
else if (RxState == 1)//进入接收数据的程序
{
Serial_RxPacket[pRxPacket++] = RxData;
if (pRxPacket >= 4)//四个载荷数据已经收完了
{
RxState = 2;
}
}
else if (RxState == 2)//等待包尾的程序
{
if (RxData == 0xFE)
{
RxState = 0;
Serial_RxFlag = 1;//置一个接收标志位
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
主函数死循环中:
if (Serial_GetRxFlag() == 1)
{
OLED_ShowHexNum(1, 1, Serial_RxPacket[0], 2);
OLED_ShowHexNum(1, 4, Serial_RxPacket[1], 2);
OLED_ShowHexNum(1, 7, Serial_RxPacket[2], 2);
OLED_ShowHexNum(1, 10, Serial_RxPacket[3], 2);
}


这个程序还有一个隐藏问题,这个RxPacket数组,它是一个同时被写入又读出的数组,在中断函数中,我们会依次写入它,在主函数里又依次读出它,数据包之间可能会混在一起,比如我们读出的过程太慢了,前面两个数据刚读出来,等了一会才继续往后读取,这时后面的数据就可能会刷新为下一个数据包的数据,也就是我们读出的数据可能一部分属于上一个数据包,一部分属于下一个数据包。解决方法,可以在接收部分加入判断,在每个数据包读取处理完毕后,再接受下一个数据包。
#include “stm32f10x.h” // Device header
#include “Delay.h”
#include “OLED.h”
#include “Serial.h”
#include “Key.h”
uint8_t KeyNum;
int main(void)
{
OLED_Init();
Serial_Init();
Key_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)
{
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);
}
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);
}
}
}
使用按键控制数据包发送,并且没发送一次增加一次数据数值。
接下来是文本数据包收发:
是可变包长,含包头包尾,以’@’为包头,以‘\r’和’\n’为包尾,中间载荷数据不固定
主要实现接受部分,发送部分比较多变,直接在主函数中SendString或printf就行。
#include “stm32f10x.h” // Device header
#include “Delay.h”
#include “OLED.h”
#include “Serial.h”
#include <Led.h>
#include <string.h>
int main(void)
{
OLED_Init();
LED_Init();
Serial_Init();
OLED_ShowString(1, 1, “TxPacket”);
OLED_ShowString(3, 1, “RxPacket”);
while (1)
{
if (Serial_GetRxFlag() == 1)
{
//显示之前需要清除一下第4行,因为字符串长度不确定,如果先显示一个长的,再显示一个短的,那长的字符串后面就会漏出来
OLED_ShowString(4, 1, ” “);
OLED_ShowString(4, 1, Serial_RxPacket);
if (strcmp(Serial_RxPacket, “LED_ON”) == 0)//判断输入字符串是否为开灯指示
{
LED1_ON();
Serial_SendString(“LED_ON_OK\r\n”);
OLED_ShowString(2, 1, ” “);
OLED_ShowString(2, 1, “LED_ON_OK”);
}
else if (strcmp(Serial_RxPacket, “LED_OFF”) == 0)//判断输入字符串是否为关灯指示
{
LED1_ON();
Serial_SendString(“LED_OFF_OK\r\n”);
OLED_ShowString(2, 1, ” “);
OLED_ShowString(2, 1, “LED_OFF_OK”);
}
else
{
Serial_SendString(“ERROR_COMMAND\r\n”);
OLED_ShowString(2, 1, ” “);
OLED_ShowString(2, 1, “ERROR_COMMAND”);
}
}
}
}
同样还是之前的问题,如果连续发送数据包,程序处理不及时,可能导致数据包错位,在这里,文本数据包每个都是独立的,不存在连续,如果错位了,问题就比较大,我们可以等每次处理完之后再接收下一个数据包
if (RxState == 0)//进入等待包头的程序
{
if (RxData == ‘@’ && Serial_RxFlag == 0)//收到包头
{
RxState = 1;
pRxPacket = 0;//在接收数据之前清零
}
}
在数据包接到包头时判断一下标志位是否等于0。
直接在主函数中使用Serial_RxFlag,在主函数中进行清零,这样写数据和读数据就是严格分开的,不会同时进行。