打印
[STM32F1]

在stm32f103C8T6开发板上实现串口通信的可靠传输

[复制链接]
227|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Zhiniaocun|  楼主 | 2025-5-9 20:16 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
下面的代码实现了串口通信的可靠传输:

#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

使用特权

评论回复
沙发
磨砂| | 2025-6-6 16:53 | 只看该作者
缩短串口通信线路长度,避免长距离传输导致的信号衰减或干扰

使用特权

评论回复
板凳
晓伍| | 2025-6-6 19:08 | 只看该作者
在长线传输中加入终端电阻,减少信号反射

使用特权

评论回复
地板
八层楼| | 2025-6-6 21:21 | 只看该作者
使用屏蔽双绞线或光电隔离模块,防止外部电磁干扰和地线环流问题

使用特权

评论回复
5
观海| | 2025-6-7 07:21 | 只看该作者
确保发送端和接收端的波特率完全一致

使用特权

评论回复
6
guanjiaer| | 2025-6-7 09:55 | 只看该作者
根据接口标准选择合适的电平转换芯片

使用特权

评论回复
7
heimaojingzhang| | 2025-6-7 12:31 | 只看该作者
将数据接收过程分为多个状态,通过状态机逐字节解析数据帧

使用特权

评论回复
8
keaibukelian| | 2025-6-7 14:43 | 只看该作者
通过串口接收中断实时处理数据,减少CPU资源占用

使用特权

评论回复
9
paotangsan| | 2025-6-7 17:57 | 只看该作者
单片机串口通信的可靠传输需要综合硬件防护、软件协议设计和数据处理策略

使用特权

评论回复
10
renzheshengui| | 2025-6-7 19:37 | 只看该作者
在非实时场景中,采用轮询方式定期检查接收缓冲区

使用特权

评论回复
11
wowu| | 2025-6-7 21:57 | 只看该作者
可以使用CRC16校验确保数据完整性。或者通过ACK机制确认接收,超时则重传

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

42

主题

170

帖子

1

粉丝