0:事由
最近開發,上位機Qt與下位機通訊的時候發現通訊規則有些不一樣,這里簡單記錄一下 。所有代碼基于元寶生成,屬于偽代碼不保證真實可用,啊但是邏輯是這么個邏輯。
1:底層通訊規則
????????以STM32向上位機通訊通訊基礎為例:
#include "main.h"
#include "usart.h"
#include "gpio.h"void SystemClock_Config(void);int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();uint8_t data_to_send[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xFF}; // 8個字節數據uint16_t data_length = sizeof(data_to_send); // 數據長度=8while (1){// 發送多個字節HAL_UART_Transmit(&huart1, data_to_send, data_length, HAL_MAX_DELAY);// 延時1秒(可選)HAL_Delay(1000);}
}
還有一種發送的案例如下:
#include "main.h"
#include "usart.h"
#include "gpio.h"void SystemClock_Config(void);int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();uint16_t sensor_data_u16 = 0x1234; // 16位數據uint32_t sensor_data_u32 = 0x12345678; // 32位數據uint8_t u16_data_bytes[2];uint8_t u32_data_bytes[4];// 拆分u16數據(大端模式)u16_data_bytes[0] = (sensor_data_u16 >> 8) & 0xFF; // 高字節u16_data_bytes[1] = sensor_data_u16 & 0xFF; // 低字節// 拆分u32數據(大端模式)u32_data_bytes[0] = (sensor_data_u32 >> 24) & 0xFF; // 最高字節u32_data_bytes[1] = (sensor_data_u32 >> 16) & 0xFF;u32_data_bytes[2] = (sensor_data_u32 >> 8) & 0xFF;u32_data_bytes[3] = sensor_data_u32 & 0xFF; // 最低字節while (1){// 發送u16數據(2字節)HAL_UART_Transmit(&huart1, u16_data_bytes, 2, HAL_MAX_DELAY);// 發送u32數據(4字節)HAL_UART_Transmit(&huart1, u32_data_bytes, 4, HAL_MAX_DELAY);// 延時1秒HAL_Delay(1000);}
}
這里簡單說一下他們通訊的原理:底層向上位機通訊的是以字節為單位發送的,一個字節(8位)可以用兩個十六進制(Hex)字符表示。你想發送多少個字節就多少個字節乘以二發送多少個16進制的數。在底層有三個特別經典的數據類型:
uint8_t flag = 0x01; //兩個16進制 8位數據 一個字節
uint16_t sensor_data_u16 = 0x1234; //四個16進制 16位數據 兩個字節
uint32_t sensor_data_u32 = 0x12345678; //八個16進制 32位數據 四個字節
然后如果想發送多個字節的話,以下面的代碼為例
#include "usart.h"void send_sensor_data() {uint8_t flag = 0x01; // 1字節uint16_t sensor_data_u16 = 0x1234; // 2字節uint32_t sensor_data_u32 = 0x12345678; // 4字節uint8_t tx_buffer[7]; // 總字節數: 1 + 2 + 4 = 7uint8_t index = 0;// 拼接數據(注意字節序!)tx_buffer[index++] = flag; // 直接拷貝1字節// 拆分uint16_t為2字節(假設使用大端序)tx_buffer[index++] = (sensor_data_u16 >> 8) & 0xFF; // 高字節tx_buffer[index++] = sensor_data_u16 & 0xFF; // 低字節// 拆分uint32_t為4字節(大端序)tx_buffer[index++] = (sensor_data_u32 >> 24) & 0xFF; // 最高字節tx_buffer[index++] = (sensor_data_u32 >> 16) & 0xFF;tx_buffer[index++] = (sensor_data_u32 >> 8) & 0xFF;tx_buffer[index++] = sensor_data_u32 & 0xFF; // 最低字節// 通過UART發送HAL_UART_Transmit(&huart1, tx_buffer, sizeof(tx_buffer), HAL_MAX_DELAY);
}
2:上位機處理數據
上位機通過串口接收數據的時候可能會有各方面的問題,比如說需要設置串口的波特率、串口、比校驗碼、當然最主要還是需要借助串口調試助手工具來進行調試。
但無論如何只要你能接收到的數據你就會發現你的數據其實是很亂的因為你不知道從哪個地方開始然后就需要下沉向上層傳輸數據時在頭部引入兩個沒有任何意義的字節,即引入一個頭部。根據這個頭部來確定發送數據的長度。一般接受的邏輯是這樣一個結構
[Header1][Header2][Length][Flag][u16_Data][u32_Data][CRC][Footer]
- ??Header1/Header2??:?
0xAA 0x55
(起始標志)- ??Length??: 數據部分長度(固定為?
7
?字節,1+2+4
)- ??Flag??: 用戶標志(
uint8_t
)- ??u16_Data/u32_Data??: 傳感器數據
- ??CRC??: 校驗碼(簡單求和校驗)
- ??Footer??:?
0xBB
(結束標志)
當然這個接收也是以字節為單位進行接收的(偽代碼)。
while (1) {// 1. 從串口讀取數據到緩存(偽代碼,需替換為實際串口讀取函數)uint8_t byte;if (serial_read_byte(&byte)) { // 假設serial_read_byte()從串口讀取1字節serial_buf.buffer[serial_buf.index++] = byte;// 2. 檢查緩存中是否有完整幀(從頭部開始)while (serial_buf.index >= 2) { // 至少需要Header1/Header2// 查找頭部起始位置(簡化版:假設頭部在緩存開頭)if (serial_buf.buffer[0] == HEADER1 && serial_buf.buffer[1] == HEADER2) {// 3. 檢查緩存是否足夠容納一幀uint8_t frame_length = 2 + 1 + EXPECTED_LEN + 1 + 1; // 頭+長度+CRC+尾if (serial_buf.index >= frame_length) {// 4. 提取完整幀uint8_t frame[256] = {0};memcpy(frame, serial_buf.buffer, frame_length);// 5. 解析幀if (parse_frame(frame, &sensor_data) == 0) {// 6. 處理有效數據(示例:打印)printf("解析成功: flag=0x%02X, u16=0x%04X, u32=0x%08X\n",sensor_data.flag, sensor_data.u16_data, sensor_data.u32_data);}// 7. 移除已處理的數據(滑動窗口)serial_buf.index -= frame_length;memmove(serial_buf.buffer, &serial_buf.buffer[frame_length], serial_buf.index);} else {break; // 數據不足,等待更多字節}} else {// 頭部不匹配,丟棄第一個字節(避免死循環)memmove(serial_buf.buffer, &serial_buf.buffer[1], --serial_buf.index);}}}
上面的代碼其實還是比較難懂的主要就是說干了以下幾個事情:
- 首先是根據下位機傳送出來的數據,并放到一個數組中。這這主要是根據頭部碼以及長度來確定的
- 其次是取出其中的數據具體就是提取從哪一字節到哪一個字節再取出來,然后進行分析解碼。
QByteArray pressureData = frame.mid(4, 4);
如果你有一個 frame 的16進制表示(比如 "01 02 03 04 05 06 07 08 09 0a 0b 0c"),那么:
第4個字節(索引4)是 05,
接下來的4個字節是 05 06 07 08。
所以 pressureData 就是 05 06 07 08(16進制)。
????????存在一個問題就是這個數據傳輸到底是第一個字節是高位還是后一個字節是高位的問題,也就是所謂的小端字節跟大端字節的一個問題
QByteArray frame = QByteArray::fromHex("0102030405060708090a0b0c"); // 示例數據
QByteArray pressureData = frame.mid(4, 4); // 提取第4~7字節(索引4開始)??小端解析??:0x08 0x07 0x06 0x05 → 0x08070605 → 84281096
??大端解析??:0x05 0x06 0x07 0x08 → 0x05060708 → 134810149// 打印提取的16進制數據
qDebug() << "Extracted data (hex):" << pressureData.toHex(); // 輸出 "05060708"// 轉換為十進制(假設是小端字節序)
quint32 pressureValue = static_cast<quint32>(pressureData[0]) |(static_cast<quint32>(pressureData[1]) << 8) |(static_cast<quint32>(pressureData[2]) << 16) |(static_cast<quint32>(pressureData[3]) << 24);
qDebug() << "Pressure value (decimal, little endian):" << pressureValue; // 輸出 84281096// 如果是大端字節序
quint32 pressureValueBigEndian = static_cast<quint32>(pressureData[3]) |(static_cast<quint32>(pressureData[2]) << 8) |(static_cast<quint32>(pressureData[1]) << 16) |(static_cast<quint32>(pressureData[0]) << 24);
qDebug() << "Pressure value (decimal, big endian):" << pressureValueBigEndian; // 輸出 134810149
當然一般來說他就是按照大端解析進行的,就是按照正常人的思維從左至右從大到小(當然了是按照正常人的思維進行的考慮到數據加密的話可能會有一點不一樣)。?