【STM32】USART串口收发HEX数据包收发文本数据包
有关串口知识参考:【STM32】USART串口协议&串口外设-学习笔记-CSDN博客
- HEX模式/十六进制模式/二进制模式:以原始数据的形式显示
- 文本模式/字符模式:以原始数据编码后的形式显示 参考上面文章查看ASCII编码表
HEX数据包
包头包尾和载荷数据重复问题的解决方法:解决思路方法
文本数据包
文本模式有大量的字符可以作为包头包尾,可以有效避免载荷数据和包头包尾重复的问题
HEX数据包和文本数据包两者的优缺点:供参考
HEX数据包接收
使用到了状态机的编程思想,关于状态机可以参考这个视频C语言之状态机编程_01_状态机基本工作原理
文本数据包接收
同样用状态机思想理解
USART串口收发HEX数据包
初始化代码参考【江协科技STM32】串口发送&串口发送+接收(学习笔记)-CSDN博客
串口发送HEX数据包代码:
uint8_t Serial_TxPack[4]; //定义发送数据包数组,数据包格式:FF 01 02 03 04 FE
/**
* 函 数:串口发送数据包
* 参 数:无
* 返 回 值:无
* 说 明:调用此函数后,Serial_TxPacket数组的内容将加上包头(FF)包尾(FE)后,作为数据包发送出去
*/
void Serial_SendPack(void)
{
Serial_SendByte(0xFF);
Serial_SendArray(Serial_TxPack,4);
Serial_SendByte(0xFE);
}
记得再头文件外部调用extern uint8_t Serial_TxPacket[];
发送HEX数据包main函数:
int main(void)
{
OLED_Init();
Serial_Init();
Serial_TxPack[0] = 0x01;
Serial_TxPack[1] = 0x02;
Serial_TxPack[2] = 0x03;
Serial_TxPack[3] = 0x04;
Serial_SendPack();
while(1)
{
}
}
硬件复位串口收到数据:
中断函数:
uint8_t Serial_RxPacket[4]; //定义接收数据包数组
uint8_t Serial_RxFlag; //定义接收数据包标志位
void USART1_IRQHandler(void)
{
static uint8_t RxState = 0; //定义表示当前状态机状态的静态变量
static uint8_t pRxPacket = 0; //定义表示当前接收数据位置的静态变量
if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断
{
uint8_t RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量
/*使用状态机的思路,依次处理数据包的不同部分*/
/*当前状态为0,接收数据包包头*/
if(RxState == 0)
{
if(RxData == 0xFF) //如果数据确实是包头
{
RxState = 1; //置下一个状态
pRxPacket = 0; //数据包的位置归零
}
}
/*当前状态为1,接收数据包数据*/
else if(RxState == 1)
{
Serial_RxPack[pRxPacket] = RxData; //将数据存入数据包数组的指定位置
pRxPacket++; //数据包的位置自增
if(pRxPacket >= 4) //如果收够4个数据
{
RxState = 2; //置下一个状态
}
}
/*当前状态为2,接收数据包包尾*/
else if(RxState == 2)
{
if(RxData == 0xFE) //如果数据确实是包尾部
{
RxState = 0; //状态归0
RX_Flag = 1; //接收数据包标志位置1,成功接收一个数据包
}
}
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
记得再头文件外部调用extern uint8_t Serial_RxPacket[];和extern uint8_t Serial_RxFlag;
获取串口接收数据包标志位:
uint8_t Serial_GerRXFlag(void)
{
if(RX_Flag == 1)
{
RX_Flag = 0;
return 1;
}
return 0;
}
发送HEX数据包和接收数据包main函数:
uint8_t KeyNum;
int main(void)
{
OLED_Init();
Serial_Init();
Key_Init();
OLED_ShowString(1,1,"TxData:");
OLED_ShowString(3,1,"RxData:");
Serial_TxPack[0] = 0x01;
Serial_TxPack[1] = 0x02;
Serial_TxPack[2] = 0x03;
Serial_TxPack[3] = 0x04;
while(1)
{
KeyNum = Key_GetNum();
if(KeyNum == 1)
{
Serial_TxPack[0]++;
Serial_TxPack[1]++;
Serial_TxPack[2]++;
Serial_TxPack[3]++;
Serial_SendPack(); //串口发送数据包Serial_TxPacket
OLED_ShowHexNum(2,1,Serial_TxPack[0],2);
OLED_ShowHexNum(2,4,Serial_TxPack[1],2);
OLED_ShowHexNum(2,7,Serial_TxPack[2],2);
OLED_ShowHexNum(2,10,Serial_TxPack[3],2);
}
if(Serial_GerRXFlag() == 1) //如果接收到数据包
{
OLED_ShowHexNum(4,1,Serial_RxPack[0],2);
OLED_ShowHexNum(4,4,Serial_RxPack[1],2);
OLED_ShowHexNum(4,7,Serial_RxPack[2],2);
OLED_ShowHexNum(4,10,Serial_RxPack[3],2);
}
}
}
收发文本数据包
程序只写接收部分,因为发送的话,不方便像HEX数组一样一个个更改, 所以发送直接在主函数调用Send_String函数或者printf
中断函数:
//定义接收数据包数组,数据包格式"@MSG\r\n"
//定义接收数据包标志位
void USART1_IRQHandler(void)
{
static uint8_t RxState = 0; //定义表示当前状态机状态的静态变量
static uint8_t pRxPacket = 0; //定义表示当前接收数据位置的静态变量
if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断
{
uint8_t RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量
/*使用状态机的思路,依次处理数据包的不同部分*/
/*当前状态为0,接收数据包包头*/
if(RxState == 0)
{
if(RxData == '@') //如果数据确实是包头
{
RxState = 1; //置下一个状态
pRxPacket = 0; //数据包的位置归零
}
}
/*当前状态为1,接收数据包数据*/
else if(RxState == 1)
{
if(RxData == '\r') //如果收到第一个包尾
{
RxState = 2;
}
else
{
Serial_RxPack[pRxPacket] = RxData; //将数据存入数据包数组的指定位置
pRxPacket++; //数据包的位置自增
}
}
/*当前状态为2,接收数据包包尾*/
else if(RxState == 2)
{
if(RxData == '\n') //如果收到第二个包尾
{
RxState = 0; //状态归0
Serial_RxPack[pRxPacket] = '\0'; //将收到的字符数据包添加一个字符串结束标志
RX_Flag = 1; //接收数据包标志位置1,成功接收一个数据包
}
}
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
自己复现编写代码时出现的错误:
char Serial_RxPacket[100]; 数组没改为100,导致单片机接收溢出,接收不到长字符串
main函数:
int main(void)
{
OLED_Init();
LED_Init();
Serial_Init();
OLED_ShowString(1,1,"TxData:");
OLED_ShowString(3,1,"RxData:");
while(1)
{
if(Serial_GerRXFlag() == 1) //如果接收到数据包
{
OLED_ShowString(4,1," ");//其实就是OLED上面会累积旧数据,你得清屏,把旧数据清空,
//再显示新的,要不旧的多余部分不会被覆盖
OLED_ShowString(4,1,Serial_RxPack);
if(strcmp(Serial_RxPack,"LED_ON") == 0)
{
LED1_ON(); //点亮LED
Serial_SendString("LED_ON_OK\r\n"); //串口回传一个字符串LED_ON_OK
OLED_ShowString(2,1," ");
OLED_ShowString(2,1,"LED_ON_OK"); //OLED清除指定位置,并显示LED_ON_OK
}
else if(strcmp(Serial_RxPack,"LED_OFF") == 0)
{
LED1_OFF(); //关闭LED
Serial_SendString("LED_OFF_OK\r\n");
OLED_ShowString(2,1," ");
OLED_ShowString(2,1,"LED_OFF_OK");
}
else //上述所有条件均不满足,即收到了未知指令
{
Serial_SendString("ERROR_COMAND\r\n"); //串口回传一个字符串ERROR_COMMAND
OLED_ShowString(2,1," ");
OLED_ShowString(2,1,"ERROR_COMAND");
}
}
}
}
自己复现编写代码时出现的错误:
①将操作LED灯部分写到判断标志位外面去了,就是每个if独立开来,导致单片机连续接收数据
②串口回传完一个字符串后知识一个简单的OLED显示
需要注意的问题:
如果连续发送数据包,程序处理不及时,可能导致数据包错位,所以要修改一下程序,等每次处理完成之后再接收下一个数据包。怎么修改呢?这里利用Serial_GerRxFlag,主函数就不使用读取Flag标志位就立刻清除的策略了,而是:
①修改中断函数,在中断这里只有Serial_RxFlag ==0了,才会继续接收下一个数据包,这样写数据和读数据完全严格分开,不会同时进行
②读取标志位清零函数直接不要了
③把uint8_t Serial_RxFlag ; 声明为外部可调用,不封装了
④直接判断 Serial_RxFlag ==1,然后处理完再清除标志位
void USART1_IRQHandler(void)
{
static uint8_t RxState = 0; //定义表示当前状态机状态的静态变量
static uint8_t pRxPacket = 0; //定义表示当前接收数据位置的静态变量
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断
{
uint8_t RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量
/*使用状态机的思路,依次处理数据包的不同部分*/
/*当前状态为0,接收数据包包头*/
if (RxState == 0)
{
if (RxData == '@' && Serial_RxFlag == 0) //如果数据确实是包头,并且上一个数据包已处理完毕
{
RxState = 1; //置下一个状态
pRxPacket = 0; //数据包的位置归零
}
}
/*当前状态为1,接收数据包数据,同时判断是否接收到了第一个包尾*/
else if (RxState == 1)
{
if (RxData == '\r') //如果收到第一个包尾
{
RxState = 2; //置下一个状态
}
else //接收到了正常的数据
{
Serial_RxPacket[pRxPacket] = RxData; //将数据存入数据包数组的指定位置
pRxPacket ++; //数据包的位置自增
}
}
/*当前状态为2,接收数据包第二个包尾*/
else if (RxState == 2)
{
if (RxData == '\n') //如果收到第二个包尾
{
RxState = 0; //状态归0
Serial_RxPacket[pRxPacket] = '\0'; //将收到的字符数据包添加一个字符串结束标志
Serial_RxFlag = 1; //接收数据包标志位置1,成功接收一个数据包
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除标志位
}
}
main:
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
LED_Init(); //LED初始化
Serial_Init(); //串口初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "TxPacket");
OLED_ShowString(3, 1, "RxPacket");
while (1)
{
if (Serial_RxFlag == 1) //如果接收到数据包
{
OLED_ShowString(4, 1, " ");
OLED_ShowString(4, 1, Serial_RxPacket); //OLED清除指定位置,并显示接收到的数据包
/*将收到的数据包与预设的指令对比,以此决定将要执行的操作*/
if (strcmp(Serial_RxPacket, "LED_ON") == 0) //如果收到LED_ON指令
{
LED1_ON(); //点亮LED
Serial_SendString("LED_ON_OK\r\n"); //串口回传一个字符串LED_ON_OK
OLED_ShowString(2, 1, " ");
OLED_ShowString(2, 1, "LED_ON_OK"); //OLED清除指定位置,并显示LED_ON_OK
}
else if (strcmp(Serial_RxPacket, "LED_OFF") == 0) //如果收到LED_OFF指令
{
LED1_OFF(); //熄灭LED
Serial_SendString("LED_OFF_OK\r\n"); //串口回传一个字符串LED_OFF_OK
OLED_ShowString(2, 1, " ");
OLED_ShowString(2, 1, "LED_OFF_OK"); //OLED清除指定位置,并显示LED_OFF_OK
}
else //上述所有条件均不满足,即收到了未知指令
{
Serial_SendString("ERROR_COMMAND\r\n"); //串口回传一个字符串ERROR_COMMAND
OLED_ShowString(2, 1, " ");
OLED_ShowString(2, 1, "ERROR_COMMAND"); //OLED清除指定位置,并显示ERROR_COMMAND
}
Serial_RxFlag = 0; //处理完成后,需要将接收数据包标志位清零,否则将无法接收后续数据包
}
}
}
有关数据包的收发,其实还是有着非常多的问题需要考虑的。实际应用要多想想。