文章目錄
- 一、場景描述
- 二、框架搭建
- 設計思路
- 通信協議設計
- 2號單片機通訊框架
- 框架優化建議
- 三、2號單片機的通訊框架如何處理消息丟失和重傳?
- 消息丟失與重傳機制設計
- 改進的通信協議
- 重傳機制實現
- 關鍵機制說明
- 優化建議
一、場景描述
有3個單片機進行通訊,分別為1號,2號,3號。
1號與2號可以通訊,2號與3號可以通訊。
那么如果1號想通過2號,與3號進行通訊。
如何搭建2號的通訊框架,保證1號的通訊能夠及時給3號,同時3號也能及時應答給1號。使用串口通訊,協議自定
二、框架搭建
設計思路
為了實現三個單片機之間的可靠中繼通信,2號單片機需要設計一個高效的通訊框架。關鍵在于如何處理串口接收、消息轉發和應答機制,同時保證消息的實時性和可靠性。
通信協議設計
首先需要定義一個簡單但功能完整的通信協議:
消息格式: [SOF][源地址][目標地址][消息ID][長度][數據][CRC]- SOF: 起始字節 (0xAA)- 源地址: 發送方ID (1,2,3)- 目標地址: 接收方ID (1,2,3)- 消息ID: 標識消息類型 (0x01-命令, 0x02-數據, 0x03-應答)- 長度: 數據字段長度- 數據: 消息內容- CRC: 校驗字節
2號單片機通訊框架
下面是2號單片機的通訊框架實現,使用狀態機處理消息接收,使用雙緩沖區實現消息轉發:
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>// 定義系統常量
#define SOF 0xAA
#define DEVICE_ID_1 0x01
#define DEVICE_ID_2 0x02
#define DEVICE_ID_3 0x03// 消息類型定義
#define MSG_CMD 0x01
#define MSG_DATA 0x02
#define MSG_ACK 0x03// 消息緩沖區大小
#define MAX_MSG_LENGTH 64
#define BUFFER_SIZE 5// 消息結構體
typedef struct {uint8_t src_addr;uint8_t dst_addr;uint8_t msg_id;uint8_t length;uint8_t data[MAX_MSG_LENGTH];uint8_t crc;
} Message;// 接收狀態機枚舉
typedef enum {RX_STATE_IDLE,RX_STATE_SOF,RX_STATE_SRC,RX_STATE_DST,RX_STATE_MSGID,RX_STATE_LENGTH,RX_STATE_DATA,RX_STATE_CRC
} RxState;// 全局變量
static Message rx_buffer[BUFFER_SIZE];
static Message tx_buffer[BUFFER_SIZE];
static uint8_t rx_head = 0;
static uint8_t rx_tail = 0;
static uint8_t tx_head = 0;
static uint8_t tx_tail = 0;
static RxState rx_state = RX_STATE_IDLE;
static uint8_t rx_index = 0;
static uint8_t crc_calculated = 0;// 串口初始化函數
void UART1_Init(void) {// 配置1號串口(與1號單片機通信)// 波特率、中斷等配置
}void UART2_Init(void) {// 配置2號串口(與3號單片機通信)// 波特率、中斷等配置
}// CRC校驗計算
uint8_t calculate_crc(uint8_t *data, uint8_t length) {uint8_t crc = 0;for (uint8_t i = 0; i < length; i++) {crc ^= data[i];}return crc;
}// 發送消息函數
void send_message(Message *msg, uint8_t uart_id) {uint8_t buffer[MAX_MSG_LENGTH + 6];uint8_t index = 0;buffer[index++] = SOF;buffer[index++] = msg->src_addr;buffer[index++] = msg->dst_addr;buffer[index++] = msg->msg_id;buffer[index++] = msg->length;for (uint8_t i = 0; i < msg->length; i++) {buffer[index++] = msg->data[i];}buffer[index++] = calculate_crc(buffer, index);// 根據uart_id選擇發送串口if (uart_id == 1) {// 通過UART1發送buffer} else {// 通過UART2發送buffer}
}// 1號串口接收中斷處理函數
void UART1_RX_IRQHandler(void) {uint8_t data = UART1_ReadByte();switch (rx_state) {case RX_STATE_IDLE:if (data == SOF) {rx_state = RX_STATE_SOF;crc_calculated = data;}break;case RX_STATE_SOF:rx_buffer[rx_head].src_addr = data;rx_state = RX_STATE_SRC;crc_calculated ^= data;break;case RX_STATE_SRC:rx_buffer[rx_head].dst_addr = data;rx_state = RX_STATE_DST;crc_calculated ^= data;break;case RX_STATE_DST:rx_buffer[rx_head].msg_id = data;rx_state = RX_STATE_MSGID;crc_calculated ^= data;break;case RX_STATE_MSGID:rx_buffer[rx_head].length = data;rx_state = RX_STATE_LENGTH;crc_calculated ^= data;rx_index = 0;break;case RX_STATE_LENGTH:rx_buffer[rx_head].data[rx_index++] = data;crc_calculated ^= data;if (rx_index >= rx_buffer[rx_head].length) {rx_state = RX_STATE_CRC;}break;case RX_STATE_CRC:if (data == crc_calculated) {// 消息接收成功,加入接收緩沖區rx_head = (rx_head + 1) % BUFFER_SIZE;}rx_state = RX_STATE_IDLE;break;}
}// 3號串口接收中斷處理函數(與UART1_RX_IRQHandler類似)
void UART2_RX_IRQHandler(void) {// 實現類似UART1_RX_IRQHandler的邏輯// 將接收到的消息存入rx_buffer
}// 主循環中的消息處理函數
void process_messages(void) {Message msg;// 處理從1號單片機接收的消息while (rx_tail != rx_head) {msg = rx_buffer[rx_tail];// 如果消息是給3號的,轉發到3號if (msg.dst_addr == DEVICE_ID_3) {// 修改源地址為2號msg.src_addr = DEVICE_ID_2;send_message(&msg, 2); // 通過UART2發送到3號}// 如果消息是給1號的(來自3號的應答),轉發到1號else if (msg.dst_addr == DEVICE_ID_1) {// 修改源地址為2號msg.src_addr = DEVICE_ID_2;send_message(&msg, 1); // 通過UART1發送到1號}rx_tail = (rx_tail + 1) % BUFFER_SIZE;}
}// 主函數
int main(void) {UART1_Init();UART2_Init();// 啟用中斷UART1_EnableIRQ();UART2_EnableIRQ();while (1) {// 處理消息轉發process_messages();// 其他任務// ...}
}
框架優化建議
-
優先級機制:為緊急消息設置高優先級,優先處理和轉發。
-
流量控制:當緩沖區滿時,發送忙信號給發送方,防止消息丟失。
-
超時重傳:對重要消息實現超時重傳機制,確保消息可靠傳輸。
-
狀態監控:添加通信狀態監控,統計消息收發成功率,便于故障排查。
-
中斷處理優化:盡量減少中斷處理時間,關鍵數據復制到緩沖區后盡快退出中斷。
通過以上設計,2號單片機可以高效地實現1號和3號之間的消息中繼,同時保證通信的實時性和可靠性。
三、2號單片機的通訊框架如何處理消息丟失和重傳?
消息丟失與重傳機制設計
為了處理消息丟失和實現可靠重傳,2號單片機的通訊框架需要增加確認機制、超時處理和消息緩存功能。以下是具體實現方案:
改進的通信協議
在原有協議基礎上增加序列號和確認應答:
消息格式: [SOF][源地址][目標地址][消息ID][序列號][長度][數據][CRC]- 序列號: 8位遞增數字(0-255),用于標識消息- 確認應答(ACK)格式: [SOF][源地址][目標地址][MSG_ACK][序列號][0][CRC]
重傳機制實現
以下是改進后的2號單片機通訊框架代碼,增加了消息緩存和重傳機制:
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>// 定義系統常量
#define SOF 0xAA
#define DEVICE_ID_1 0x01
#define DEVICE_ID_2 0x02
#define DEVICE_ID_3 0x03// 消息類型定義
#define MSG_CMD 0x01
#define MSG_DATA 0x02
#define MSG_ACK 0x03// 消息緩沖區大小
#define MAX_MSG_LENGTH 64
#define TX_BUFFER_SIZE 10
#define RX_BUFFER_SIZE 10
#define MAX_RETRIES 3
#define RETRY_TIMEOUT 100 // 毫秒// 消息結構體
typedef struct {uint8_t src_addr;uint8_t dst_addr;uint8_t msg_id;uint8_t seq_num;uint8_t length;uint8_t data[MAX_MSG_LENGTH];uint8_t crc;uint32_t timestamp; // 發送時間戳uint8_t retries; // 重試次數bool awaiting_ack; // 是否等待確認
} Message;// 接收狀態機枚舉
typedef enum {RX_STATE_IDLE,RX_STATE_SOF,RX_STATE_SRC,RX_STATE_DST,RX_STATE_MSGID,RX_STATE_SEQ,RX_STATE_LENGTH,RX_STATE_DATA,RX_STATE_CRC
} RxState;// 全局變量
static Message tx_buffer[TX_BUFFER_SIZE]; // 發送緩沖區(用于重傳)
static Message rx_buffer[RX_BUFFER_SIZE]; // 接收緩沖區
static uint8_t tx_head = 0;
static uint8_t tx_tail = 0;
static uint8_t rx_head = 0;
static uint8_t rx_tail = 0;
static RxState rx_state = RX_STATE_IDLE;
static uint8_t rx_index = 0;
static uint8_t crc_calculated = 0;
static uint8_t next_seq_num = 0; // 下一個發送的序列號// 串口初始化函數
void UART1_Init(void) {// 配置1號串口(與1號單片機通信)// 波特率、中斷等配置
}void UART2_Init(void) {// 配置2號串口(與3號單片機通信)// 波特率、中斷等配置
}// CRC校驗計算
uint8_t calculate_crc(uint8_t *data, uint8_t length) {uint8_t crc = 0;for (uint8_t i = 0; i < length; i++) {crc ^= data[i];}return crc;
}// 發送消息函數
void send_message(Message *msg, uint8_t uart_id, bool store_for_retry) {uint8_t buffer[MAX_MSG_LENGTH + 7];uint8_t index = 0;buffer[index++] = SOF;buffer[index++] = msg->src_addr;buffer[index++] = msg->dst_addr;buffer[index++] = msg->msg_id;buffer[index++] = msg->seq_num;buffer[index++] = msg->length;for (uint8_t i = 0; i < msg->length; i++) {buffer[index++] = msg->data[i];}buffer[index++] = calculate_crc(buffer, index);// 根據uart_id選擇發送串口if (uart_id == 1) {// 通過UART1發送buffer} else {// 通過UART2發送buffer}// 如果需要存儲用于重傳if (store_for_retry && msg->msg_id != MSG_ACK) {// 復制消息到發送緩沖區memcpy(&tx_buffer[tx_head], msg, sizeof(Message));tx_buffer[tx_head].timestamp = get_current_time(); // 獲取當前時間tx_buffer[tx_head].retries = 0;tx_buffer[tx_head].awaiting_ack = true;tx_head = (tx_head + 1) % TX_BUFFER_SIZE;}
}// 1號串口接收中斷處理函數
void UART1_RX_IRQHandler(void) {uint8_t data = UART1_ReadByte();switch (rx_state) {case RX_STATE_IDLE:if (data == SOF) {rx_state = RX_STATE_SOF;crc_calculated = data;}break;// 其他狀態處理...(與之前類似)case RX_STATE_CRC:if (data == crc_calculated) {// 消息接收成功,加入接收緩沖區if (rx_buffer[rx_head].msg_id == MSG_ACK) {// 處理確認應答,標記對應消息已確認process_ack(&rx_buffer[rx_head]);} else {// 普通消息,轉發并發送本地ACKforward_message(&rx_buffer[rx_head]);send_ack(rx_buffer[rx_head].src_addr, rx_buffer[rx_head].seq_num);}rx_head = (rx_head + 1) % RX_BUFFER_SIZE;}rx_state = RX_STATE_IDLE;break;}
}// 3號串口接收中斷處理函數
void UART2_RX_IRQHandler(void) {// 與UART1_RX_IRQHandler類似,處理來自3號的消息
}// 處理確認應答
void process_ack(Message *ack) {uint8_t index = tx_tail;while (index != tx_head) {if (tx_buffer[index].awaiting_ack && tx_buffer[index].seq_num == ack->seq_num &&tx_buffer[index].dst_addr == ack->src_addr) {// 標記消息已確認tx_buffer[index].awaiting_ack = false;break;}index = (index + 1) % TX_BUFFER_SIZE;}
}// 轉發消息
void forward_message(Message *msg) {Message forward_msg;memcpy(&forward_msg, msg, sizeof(Message));if (msg->dst_addr == DEVICE_ID_3) {// 轉發到3號forward_msg.src_addr = DEVICE_ID_2;send_message(&forward_msg, 2, true);} else if (msg->dst_addr == DEVICE_ID_1) {// 轉發到1號forward_msg.src_addr = DEVICE_ID_2;send_message(&forward_msg, 1, true);}
}// 發送確認應答
void send_ack(uint8_t dst_addr, uint8_t seq_num) {Message ack_msg;ack_msg.src_addr = DEVICE_ID_2;ack_msg.dst_addr = dst_addr;ack_msg.msg_id = MSG_ACK;ack_msg.seq_num = seq_num;ack_msg.length = 0;if (dst_addr == DEVICE_ID_1) {send_message(&ack_msg, 1, false);} else {send_message(&ack_msg, 2, false);}
}// 檢查并重傳超時消息
void check_and_retransmit(void) {uint32_t current_time = get_current_time();uint8_t index = tx_tail;while (index != tx_head) {if (tx_buffer[index].awaiting_ack && (current_time - tx_buffer[index].timestamp > RETRY_TIMEOUT)) {// 檢查重試次數if (tx_buffer[index].retries >= MAX_RETRIES) {// 達到最大重試次數,處理失敗handle_transmission_failure(&tx_buffer[index]);tx_buffer[index].awaiting_ack = false;} else {// 重傳消息tx_buffer[index].retries++;tx_buffer[index].timestamp = current_time;if (tx_buffer[index].dst_addr == DEVICE_ID_1) {send_message(&tx_buffer[index], 1, false);} else {send_message(&tx_buffer[index], 2, false);}}}index = (index + 1) % TX_BUFFER_SIZE;}// 清理已確認的消息while (tx_tail != tx_head && !tx_buffer[tx_tail].awaiting_ack) {tx_tail = (tx_tail + 1) % TX_BUFFER_SIZE;}
}// 處理傳輸失敗
void handle_transmission_failure(Message *msg) {// 可以實現錯誤日志記錄或通知上層應用
}// 獲取當前時間(偽代碼,需根據實際硬件實現)
uint32_t get_current_time(void) {// 返回系統時間(毫秒)return 0;
}// 主循環中的消息處理函數
void process_messages(void) {// 檢查并重傳超時消息check_and_retransmit();// 處理接收緩沖區中的消息while (rx_tail != rx_head) {// 處理消息...rx_tail = (rx_tail + 1) % RX_BUFFER_SIZE;}
}// 主函數
int main(void) {UART1_Init();UART2_Init();// 啟用中斷UART1_EnableIRQ();UART2_EnableIRQ();while (1) {// 處理消息轉發和重傳process_messages();// 其他任務// ...}
}
關鍵機制說明
-
序列號管理:
- 每個消息分配唯一序列號(0-255循環)
- 用于識別重復消息和匹配ACK應答
-
確認應答機制:
- 接收方收到消息后發送ACK應答
- ACK包含原始消息的序列號
- 發送方維護待確認消息列表
-
超時重傳:
- 設置合理的超時時間(RETRY_TIMEOUT)
- 超過時間未收到ACK則重傳
- 限制最大重試次數(MAX_RETRIES)
-
消息緩存:
- 使用環形緩沖區存儲待確認消息
- 消息包含發送時間戳和重試次數
優化建議
-
滑動窗口協議:
- 擴展當前實現,支持多消息并發發送
- 增加窗口大小參數,提高吞吐量
-
動態超時調整:
- 根據網絡狀況動態調整超時時間
- 實現往返時間(RTT)測量
-
流量控制:
- 當發送緩沖區滿時拒絕接收新消息
- 實現基于窗口的流量控制機制
-
錯誤恢復:
- 實現序列號回滾機制
- 處理序列號溢出情況
通過以上機制,2號單片機可以有效處理消息丟失問題,確保1號和3號之間的通信可靠性。