【STM32单片机】#10.5 串口数据包
主要参考学习资料:B站@江协科技STM32入门教程-2023版 细致讲解 中文字幕开发资料下载链接:https://pan.baidu.com/s/1h_UjuQKDX9IpP-U1Effbsw?pwd=dspb单片机套装:STM32F103C8T6开发板单片机C6T6核心板 实验板最小系统板套件科协。
主要参考学习资料:
B站@江协科技
STM32入门教程-2023版 细致讲解 中文字幕
开发资料下载链接:https://pan.baidu.com/s/1h_UjuQKDX9IpP-U1Effbsw?pwd=dspb
单片机套装:STM32F103C8T6开发板单片机C6T6核心板 实验板最小系统板套件科协
实验:
- 串口收发HEX数据包
- 串口收发文本数据包
目录
数据包
数据包的作用是进行多字节的数据通信,将属于同一批的数据进行打包和分割,以便接收方识别。打包和分割的方法可以自行设计。
HEX数据包
HEX数据包直接传输十六进制数据,本实验规定使用FF作为包头、FE作为包尾的方式打包和分割数据包。
针对包头包尾和载荷数据重复的问题,一种解决方法是限制载荷数据的范围,在发送时对数据进行限幅,使其不会重复;第二种方法是尽量使用固定长度的数据包,只要通过包头包尾对齐了数据,就可以严格知道哪个数据时包头包尾,哪个数据是载荷数据;第三种方法是增加包头包尾的数量,并让尽量使用载荷数据不会出现的状态,例如用FF、FE作为包头,FD、FC作为包尾。
包头和包尾并非都需要,可以只保留包头,删除包尾,但载荷与包头重复的问题也会更严重。
文本数据包
在文本数据包中,每个字节经过了一层编码和译码,以文本格式表现。字符在包头包尾的选择中可以有效避免与载荷数据重复的问题,本实验规定以@作为包头,以换行字符\r\n作为包尾。载荷数据接收为字符串,软件通过对字符串判断和操作可以实现各种指令控制功能。
HEX数据包的优点是传输直接,解析简单,适合陀螺仪、传感器等模块发送原始数据,缺点是灵活性不足,载荷容易和包头包尾重复。文本数据包的优点是数据直观易理解,非常灵活,适合通过指令进行人机交互的场合,缺点是解析效率低。
数据包收发流程
数据包发送的过程很简单,只需定义数组,填充数据并发送。此处主要介绍接收流程。
HEX数据包
在接收数据中,针对包头、数据载荷和包尾三种状态需要不同的处理逻辑,因此我们使用状态机思维设计程序,通过一个能记住不同状态的机制,在不同状态执行不同的操作,同时还要进行状态的合理转移。上图为接收固定包长含包头包尾的HEX数据包的状态转移图,变量S标志不同的状态,在满足相应的条件时跟随箭头方向转移。
文本数据包
实验22 串口收发HEX数据包
接线图
Serial驱动
在实验21的基础上更改。
Serial.h
#ifndef __SERIAL_H
#define __SERIAL_H
#include <stdio.h>
//允许外部调用发送和接收数据包
extern uint8_t Serial_TxPacket[];
extern uint8_t Serial_RxPacket[];
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);
void Serial_SendPacket(void);
uint8_t Serial_GetRxFlag(void);
#endif
Serial.c
只展示更改部分:
//在驱动中暂存接受数据包
uint8_t Serial_TxPacket[4];
uint8_t Serial_RxPacket[4];
uint8_t Serial_RxFlag;
//新增发送数据包函数
void Serial_SendPacket(void)
{
//发送包头
Serial_SendByte(0xFF);
//发送数据载荷
Serial_SendArray(Serial_TxPacket, 4);
//发送包尾
Serial_SendByte(0xFE);
}
//删去Serial_GetRxData函数
//更改中断函数
void USART1_IRQHandler(void)
{
//状态变量
static uint8_t RxState = 0;
//指示数据接收到第几个
static uint8_t pRxPacket = 0;
if(USART_GetITStatus(USART1, USART_IT_RXNE))
{
//接收单个字节
uint8_t RxData = USART_ReceiveData(USART1);
//根据状态不同进入不同的处理程序
switch(RxState)
{
//等待包头
case 0:
//收到包头转移状态
if(RxData == 0xFF)
RxState = 1;
break;
//接收数据
case 1:
Serial_RxPacket[pRxPacket] = RxData;
pRxPacket ++;
//接收满4个数据
if(pRxPacket >= 4)
{
RxState = 2;
pRxPacket = 0;
}
break;
//等待包尾
case 2:
//收到包尾
if(RxData == 0xFE)
{
RxState = 0;
//置接收标志位
Serial_RxFlag = 1;
}
break;
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
主程序
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Key.h"
uint8_t KeyNum;
int main(void)
{
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)
{
KeyNum = Key_GetNum();
//每次按下按键使发送数据包加一并发送
if(KeyNum)
{
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())
{
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);
}
}
}
实验23 串口收发文本数据包
实现功能:使用串口发送文本指令控制LED亮灭。
接线图
Serial驱动
在实验22的基础上修改。
Serial.h
#ifndef __SERIAL_H
#define __SERIAL_H
#include <stdio.h>
extern char Serial_RxPacket[];
//声明标志位为外部可调用
extern uint8_t Serial_RxFlag;
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);
#endif
Serial.c
只展示更改部分:
//接收数据类型改为char,上限100个字符
char Serial_RxPacket[100];
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))
{
uint8_t RxData = USART_ReceiveData(USART1);
switch(RxState)
{
case 0:
//更改包头
//为防止程序处理数据包不及时导致数据包错位
//需判断接收标志位清零后再接收下一个数据包
if(RxData == '@' && !Serial_RxFlag)
{
RxState = 1;
pRxPacket = 0;
}
break;
case 1:
//判断包尾是否到来
if(RxData == '\r')
RxState = 2;
else
{
Serial_RxPacket[pRxPacket] = RxData;
pRxPacket ++;
}
//无需检测数据数量
break;
case 2:
//等待第二个包尾
if(RxData == '\n')
{
RxState = 0;
//添加字符串结束标志
Serial_RxPacket[pRxPacket] = '\0';
Serial_RxFlag = 1;
}
break;
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
主程序
#include "stm32f10x.h"
#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_RxFlag)
{
//由于字符串长度不确定,需擦除显示行再覆写下一个字符串
OLED_ShowString(4, 1, " ");
OLED_ShowString(4, 1, Serial_RxPacket);
//字符串相等返回0
//开灯指令
if(!strcmp(Serial_RxPacket, "LED_ON"))
{
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"))
{
LED1_OFF();
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");
}
//清空标志位
Serial_RxFlag = 0;
}
}
}
更多推荐
所有评论(0)