主要參考學習資料:
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;}}
}