主要参考学习资料:
B站@江协科技
STM32入门教程-2023版 细致讲解 中文字幕
开发资料下载链接:https://pan.baidu.com/s/1h_UjuQKDX9IpP-U1Effbsw?pwd=dspb
单片机套装:STM32F103C8T6开发板单片机C6T6核心板 实验板最小系统板套件科协
实验:
- 串口收发HEX数据包
- 串口收发文本数据包
目录
- 数据包
- HEX数据包
- 文本数据包
- 数据包收发流程
- HEX数据包
- 文本数据包
- 实验22 串口收发HEX数据包
- 接线图
- Serial驱动
- 主程序
- 实验23 串口收发文本数据包
- 接线图
- Serial驱动
- 主程序
数据包
数据包的作用是进行多字节的数据通信,将属于同一批的数据进行打包和分割,以便接收方识别。打包和分割的方法可以自行设计。
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;}}
}