下面的代码实现了串口通信的可靠传输:
#include "stm32f10x.h"
#include <string.h>
#include <stdlib.h>
// 协议配置
#define MAX_PACKET_SIZE 128
#define WINDOW_SIZE 4 // 滑动窗口大小
#define RETRY_TIMEOUT 200 // 重试超时(ms)
#define MAX_RETRIES 3 // 最大重试次数
// 协议帧结构
#pragma pack(push, 1)
typedef struct {
uint8_t start; // 起始符(0xAA)
uint16_t seq_num; // 序列号
uint8_t type; // 包类型
uint16_t length; // 数据长度
uint8_t data[MAX_PACKET_SIZE];
uint16_t crc; // CRC16校验
uint8_t end; // 结束符(0x55)
} UART_Frame;
#pragma pack(pop)
// 包类型定义
typedef enum {
DATA_PACKET, // 数据包
ACK_PACKET, // 确认包
NAK_PACKET, // 否定确认
FLOW_CONTROL_PACKET // 流控包
} PacketType;
// 发送控制结构
typedef struct {
UART_Frame frame;
uint32_t timestamp;
uint8_t retries;
uint8_t acked;
} TxControl;
// 全局变量
TxControl tx_window[WINDOW_SIZE];
uint16_t tx_seq = 0;
uint16_t rx_seq = 0;
// CRC16计算(改进版)
uint16_t crc16(const uint8_t *data, size_t length) {
uint16_t crc = 0xFFFF;
while (length--) {
crc ^= *data++;
for (int i = 0; i < 8; i++) {
if (crc & 1) crc = (crc >> 1) ^ 0xA001;
else crc >>= 1;
}
}
return crc;
}
// 发送窗口管理
void manage_tx_window(void) {
static uint8_t window_index = 0;
// 检查超时包
for (int i = 0; i < WINDOW_SIZE; i++) {
if (tx_window[i].acked == 0 &&
(HAL_GetTick() - tx_window[i].timestamp) > RETRY_TIMEOUT) {
if (tx_window[i].retries < MAX_RETRIES) {
// 重传
USART_SendData(USART1, (uint8_t*)&tx_window[i].frame, sizeof(UART_Frame));
tx_window[i].retries++;
tx_window[i].timestamp = HAL_GetTick();
} else {
// 超过最大重试次数
tx_window[i].acked = 2; // 标记为失败
}
}
}
// 寻找可用的窗口位置
if (tx_window[window_index].acked != 0) {
// 准备新包
tx_seq = (tx_seq + 1) % 0xFFFF;
window_index = (window_index + 1) % WINDOW_SIZE;
}
}
// 数据分包处理
void send_data_packet(uint8_t *data, uint16_t total_len) {
uint16_t offset = 0;
while (total_len > 0) {
uint16_t chunk_size = (total_len > MAX_PACKET_SIZE) ? MAX_PACKET_SIZE : total_len;
// 填充帧
UART_Frame frame = {
.start = 0xAA,
.seq_num = tx_seq,
.type = DATA_PACKET,
.length = chunk_size,
.end = 0x55
};
memcpy(frame.data, data + offset, chunk_size);
frame.crc = crc16((uint8_t*)&frame.seq_num, sizeof(frame) - 4); // 计算CRC
// 加入发送窗口
memcpy(&tx_window[tx_seq % WINDOW_SIZE].frame, &frame, sizeof(frame));
tx_window[tx_seq % WINDOW_SIZE].timestamp = HAL_GetTick();
tx_window[tx_seq % WINDOW_SIZE].retries = 0;
tx_window[tx_seq % WINDOW_SIZE].acked = 0;
USART_SendData(USART1, (uint8_t*)&frame, sizeof(frame));
offset += chunk_size;
total_len -= chunk_size;
manage_tx_window();
}
}
// 接收状态机
void USART1_IRQHandler(void) {
static uint8_t rx_buffer[sizeof(UART_Frame)];
static uint16_t rx_index = 0;
static enum {IDLE, RECEIVING} state = IDLE;
if (USART_GetITStatus(USART1, USART_IT_RXNE)) {
uint8_t byte = USART_ReceiveData(USART1);
switch (state) {
case IDLE:
if (byte == 0xAA) {
rx_index = 0;
rx_buffer[rx_index++] = byte;
state = RECEIVING;
}
break;
case RECEIVING:
rx_buffer[rx_index++] = byte;
if (rx_index >= sizeof(UART_Frame)) {
state = IDLE;
UART_Frame *frame = (UART_Frame*)rx_buffer;
// 校验帧结构
if (frame->end == 0x55 &&
frame->crc == crc16((uint8_t*)&frame->seq_num, sizeof(UART_Frame) - 4)) {
// 序列号检查
if (frame->seq_num == rx_seq) {
// 处理数据
process_application_data(frame);
rx_seq++;
send_ack(frame->seq_num);
} else if (frame->seq_num < rx_seq) {
// 重复包,重新发送ACK
send_ack(frame->seq_num);
} else {
// 乱序包,发送NAK
send_nak(rx_seq);
}
} else {
send_nak(rx_seq);
}
}
break;
}
}
}
// 流量控制处理
void flow_control_handler(void) {
static uint8_t last_credit = WINDOW_SIZE;
uint8_t current_credit = WINDOW_SIZE - get_used_window();
if (current_credit != last_credit) {
UART_Frame flow_frame = {
.start = 0xAA,
.type = FLOW_CONTROL_PACKET,
.data[0] = current_credit, // 可用窗口大小
.end = 0x55
};
flow_frame.crc = crc16((uint8_t*)&flow_frame.seq_num, sizeof(flow_frame) - 4);
USART_SendData(USART1, (uint8_t*)&flow_frame, sizeof(flow_frame));
last_credit = current_credit;
}
}
// 应用层协议处理
void process_application_data(UART_Frame *frame) {
switch (frame->type) {
case DATA_PACKET:
// 数据存储或处理
break;
case ACK_PACKET:
// 更新发送窗口状态
for (int i = 0; i < WINDOW_SIZE; i++) {
if (tx_window[i].frame.seq_num == frame->seq_num) {
tx_window[i].acked = 1;
break;
}
}
break;
case NAK_PACKET:
// 触发快速重传
for (int i = 0; i < WINDOW_SIZE; i++) {
if (tx_window[i].frame.seq_num == frame->seq_num) {
tx_window[i].retries = MAX_RETRIES;
break;
}
}
break;
case FLOW_CONTROL_PACKET:
// 更新发送窗口大小
adjust_window_size(frame->data[0]);
break;
}
}
// 辅助函数
void send_ack(uint16_t seq_num) {
UART_Frame ack_frame = {
.start = 0xAA,
.seq_num = seq_num,
.type = ACK_PACKET,
.end = 0x55
};
ack_frame.crc = crc16((uint8_t*)&ack_frame.seq_num, sizeof(ack_frame) - 4);
USART_SendData(USART1, (uint8_t*)&ack_frame, sizeof(ack_frame));
}
void send_nak(uint16_t seq_num) {
UART_Frame nak_frame = {
.start = 0xAA,
.seq_num = seq_num,
.type = NAK_PACKET,
.end = 0x55
};
nak_frame.crc = crc16((uint8_t*)&nak_frame.seq_num, sizeof(nak_frame) - 4);
USART_SendData(USART1, (uint8_t*)&nak_frame, sizeof(nak_frame));
}
// 主函数
int main(void) {
// 初始化代码...
while (1) {
manage_tx_window();
flow_control_handler();
// 其他任务...
}
}
这个增强版协议包含以下改进:
滑动窗口协议:
支持WINDOW_SIZE定义的并发传输包数量
实现选择性重传(SACK)
包含序列号管理(16位循环序列)
重传机制:
基于超时的重传(RETRY_TIMEOUT)
最大重试次数限制(MAX_RETRIES)
快速重传机制(收到NAK时立即重传)
流量控制:
动态窗口大小调整
基于信用量的流控机制
接收方主动发送窗口更新
数据分包处理:
自动处理大数据分包
支持最大包长(MAX_PACKET_SIZE)
自动处理分包序列号
增强的错误处理:
CRC校验失败自动重传
序列号异常检测
超时连接管理
无效帧丢弃机制
应用层协议:
定义多种包类型(数据、ACK、NAK、流控)
支持扩展应用层指令
提供数据处理回调接口
使用说明:
硬件连接:
确保USART1已正确配置(波特率、时钟等)
建议启用硬件流控(RTS/CTS)以获得更好的流控效果
发送数据:
uint8_t data[512];
// 填充数据...
send_data_packet(data, sizeof(data));
接收处理:
在process_application_data函数中实现具体的数据处理逻辑
参数调优:
根据实际需求调整:
MAX_PACKET_SIZE(最大传输单元)
WINDOW_SIZE(网络带宽时延积)
RETRY_TIMEOUT(根据链路质量设置)
MAX_RETRIES(可靠性要求)
扩展应用协议:
通过扩展PacketType枚举和修改process_application_data函数来实现具体应用逻辑
该实现使用了以下关键技术:
滑动窗口协议
CRC16校验
超时重传机制
选择性确认
信用量流量控制
状态机解析
中断驱动I/O
注意:需要根据实际使用的硬件平台完善硬件抽象层(HAL)相关函数,并配置正确的时钟系统。建议配合看门狗定时器使用以提高系统可靠性。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/chencichang/article/details/147546613
|
|