在HHD32F107上实现Xmodem协议固化程序(一)

 Xmodem-1K 协议介绍

Xmodem-1K 是 Xmodem 协议的扩展版本,主要特点是使用 1024字节(1K)的数据块 进行传输,以提高传输效率。以下是其传输机制、优点和缺点的详细介绍。

---

传输机制

1. 初始化传输
   - 接收方发送 NAK 信号,请求发送方开始传输。
   - 如果支持 Xmodem-1K,接收方会发送字符 `'C'`,表示要求使用 **CRC 校验** 和 **1024字节数据块**。

2. 数据包结构
   - 每个数据包包含以下部分:
     - 起始字符:`SOH`(表示128字节数据块)或 `STX`(表示1024字节数据块)。
     - 块编号:1字节,从1开始递增。
     - 块编号补码:1字节,块编号的按位取反。
     - 数据:1024字节的有效数据(不足部分用填充字节补全)。
     - 校验和:2字节的 CRC 校验值。

3. 数据传输
   - 发送方按顺序发送数据块。
   - 接收方收到数据块后,检查块编号、CRC 校验和数据完整性。
     - 如果数据正确,接收方发送 ACK,请求下一个数据块。
     - 如果数据错误,接收方发送 NAK,要求重传当前数据块。

4. 结束传输
   - 发送方发送 EOT信号,表示传输结束。
   - 接收方回复 ACK,确认传输完成。

 优点

1. 传输效率高

- 1024字节的数据块减少了协议开销(如起始字符、块编号、校验和等)的比例,适合传输大文件。

2. 适合高速通信
   - 在稳定的通信环境中,Xmodem-1K 可以充分利用带宽,缩短传输时间。

3. 支持 CRC 校验
   - 使用 2 字节的 CRC 校验,比标准 Xmodem 的 1 字节校验和更可靠,能检测更多类型的错误。

---

缺点

1. 不适合不稳定环境
   - 数据块较大,如果在噪声较大的通信链路中传输,出错概率增加,重传的数据量也更大。

2. 兼容性有限
   - 需要通信双方都支持 Xmodem-1K 协议,早期设备可能仅支持标准 Xmodem(128字节)。

3. 内存需求较高
   - 接收方需要缓存整个 1024 字节的数据块,对资源有限的嵌入式系统可能不友好。

4. 小文件传输效率低
   - 对于小文件,1024 字节的数据块可能导致填充字节过多,反而降低效率。

MCU初始化代码

来源于deepSeek生成,仅供参考

1.串口初始化

hhd32F107系列的单片机初始化函数,调用的库函数和下面有差异,但原理一致的,辛苦小伙伴们自己替换一下。

void USART_Init(void) {
    // 配置USART的波特率、数据位、停止位等
    // 例如:USART_InitTypeDef USART_InitStruct;
    // USART_InitStruct.BaudRate = 115200;
    // USART_InitStruct.WordLength = USART_WordLength_8b;
    // USART_InitStruct.StopBits = USART_StopBits_1;
    // USART_InitStruct.Parity = USART_Parity_No;
    // USART_InitStruct.Mode = USART_Mode_Rx | USART_Mode_Tx;
    // USART_Init(USART1, &USART_InitStruct);

    // 启用接收中断
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

    // 启用USART
    USART_Cmd(USART1, ENABLE);

    // 初始化环形缓冲区
    RingBuffer_Init(&rx_buffer);
}

2.环形缓冲区实现

环形缓冲区用于在中断服务函数中存储接收到的数据。

#define BUFFER_SIZE 1024

typedef struct {
    uint8_t buffer[BUFFER_SIZE];
    uint16_t head;
    uint16_t tail;
} RingBuffer;

RingBuffer rx_buffer;

void RingBuffer_Init(RingBuffer *rb) {
    rb->head = 0;
    rb->tail = 0;
}

uint8_t RingBuffer_IsFull(RingBuffer *rb) {
    return ((rb->head + 1) % BUFFER_SIZE) == rb->tail;
}

uint8_t RingBuffer_IsEmpty(RingBuffer *rb) {
    return rb->head == rb->tail;
}

void RingBuffer_Push(RingBuffer *rb, uint8_t data) {
    if (!RingBuffer_IsFull(rb)) {
        rb->buffer[rb->head] = data;
        rb->head = (rb->head + 1) % BUFFER_SIZE;
    }
}

uint8_t RingBuffer_Pop(RingBuffer *rb) {
    uint8_t data = 0;
    if (!RingBuffer_IsEmpty(rb)) {
        data = rb->buffer[rb->tail];
        rb->tail = (rb->tail + 1) % BUFFER_SIZE;
    }
    return data;
}

3.中断服务函数

在中断服务函数中,将接收到的数据存入环形缓冲区。

void USART1_IRQHandler(void) {
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        uint8_t data = USART_ReceiveData(USART1);
        RingBuffer_Push(&rx_buffer, data);
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    }
}

4.移植Xmodem协议代码

代码说明

  1. 数据包结构

    • 数据包大小为1029字节(STX + 包序号 + 包序号反码 + 1024字节数据 + 2字节CRC)。

    • 通过检查包序号和包序号反码来验证数据包的完整性。

    • 使用CRC-16校验数据包的有效性。

  2. 超时机制

    • 使用HAL_GetTick()获取当前时间(假设使用HAL库)。

    • 如果在规定时间内没有收到数据,则发送NAK并重新等待数据包。

  3. 环形缓冲区

    • 使用你定义的RingBuffer来存储串口接收到的数据。

    • 在中断服务函数中,数据被推入环形缓冲区;在主循环中,数据从环形缓冲区中弹出。

  4. ACK/NAK响应

    • 根据Xmodem协议规范,发送ACK或NAK响应。

#include <stdint.h>
#include <string.h>

#define XMODEM_PACKET_SIZE     1024  // Xmodem数据包大小
#define XMODEM_HEADER_SIZE     3     // 数据包头大小(STX + 包序号 + 包序号反码)
#define XMODEM_CRC_SIZE        2     // CRC校验大小
#define XMODEM_PACKET_FULL     (XMODEM_HEADER_SIZE + XMODEM_PACKET_SIZE + XMODEM_CRC_SIZE)

#define STX                    0x02  // 1024字节数据包起始标志
#define EOT                    0x04  // 传输结束标志
#define ACK                    0x06  // 确认响应
#define NAK                    0x15  // 否定响应

#define XMODEM_TIMEOUT_MS      1000  // 超时时间(1秒)

// 环形缓冲区操作函数(使用你定义的RingBuffer)
extern RingBuffer rx_buffer;
extern uint8_t RingBuffer_Pop(RingBuffer *rb);
extern uint8_t RingBuffer_IsEmpty(RingBuffer *rb);

// 串口发送函数
void USART_SendByte(uint8_t data) {
    USART_SendData(USART1, data);
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

// 计算CRC16校验
uint16_t crc16(uint8_t *data, uint16_t len) {
    uint16_t crc = 0;
    while (len--) {
        crc = crc ^ ((uint16_t)*data++ << 8);
        for (uint8_t i = 0; i < 8; i++) {
            if (crc & 0x8000)
                crc = (crc << 1) ^ 0x1021;
            else
                crc <<= 1;
        }
    }
    return crc;
}

// 超时检测函数
uint8_t xmodem_timeout(uint32_t start_time, uint32_t timeout_ms) {
    uint32_t current_time = HAL_GetTick();  // 获取当前时间(假设使用HAL库)
    return (current_time - start_time) >= timeout_ms;
}

// Xmodem接收函数
int xmodem_receive(uint8_t *dest, uint32_t dest_size) {
    uint8_t packet[XMODEM_PACKET_FULL];
    uint8_t packet_number = 1;  // 当前包序号
    uint32_t total_size = 0;    // 已接收数据总大小
    uint32_t start_time;

    // 发送NAK启动传输
    USART_SendByte(NAK);

    while (1) {
        // 等待数据包起始字节
        uint8_t start_byte;
        start_time = HAL_GetTick();  // 记录起始时间
        while (RingBuffer_IsEmpty(&rx_buffer)) {
            if (xmodem_timeout(start_time, XMODEM_TIMEOUT_MS)) {
                USART_SendByte(NAK);  // 超时,发送NAK
                start_time = HAL_GetTick();  // 重置超时计时
            }
        }
        start_byte = RingBuffer_Pop(&rx_buffer);

        if (start_byte == EOT) {
            // 收到EOT,发送ACK并结束传输
            USART_SendByte(ACK);
            return total_size;  // 返回接收到的总数据大小
        } else if (start_byte == STX) {
            // 接收完整数据包
            start_time = HAL_GetTick();  // 记录起始时间
            for (uint16_t i = 0; i < XMODEM_PACKET_FULL; i++) {
                while (RingBuffer_IsEmpty(&rx_buffer)) {
                    if (xmodem_timeout(start_time, XMODEM_TIMEOUT_MS)) {
                        USART_SendByte(NAK);  // 超时,发送NAK
                        goto next_packet;     // 跳转到下一个数据包
                    }
                }
                packet[i] = RingBuffer_Pop(&rx_buffer);
            }

            // 检查包序号和包序号反码
            uint8_t pkt_num = packet[0];
            uint8_t pkt_num_inv = packet[1];
            if (pkt_num + pkt_num_inv != 0xFF || pkt_num != packet_number) {
                USART_SendByte(NAK);  // 包序号错误,发送NAK
                continue;
            }

            // 计算CRC校验
            uint16_t crc_received = (packet[XMODEM_PACKET_FULL - 2] << 8) | packet[XMODEM_PACKET_FULL - 1];
            uint16_t crc_calculated = crc16(&packet[XMODEM_HEADER_SIZE], XMODEM_PACKET_SIZE);
            if (crc_received != crc_calculated) {
                USART_SendByte(NAK);  // CRC校验失败,发送NAK
                continue;
            }

            // 数据包有效,拷贝数据到目标缓冲区
            if (total_size + XMODEM_PACKET_SIZE <= dest_size) {
                memcpy(dest + total_size, &packet[XMODEM_HEADER_SIZE], XMODEM_PACKET_SIZE);
                total_size += XMODEM_PACKET_SIZE;
            }

            // 发送ACK确认
            USART_SendByte(ACK);
            packet_number++;  // 更新包序号
        } else {
            // 无效起始字节,发送NAK
            USART_SendByte(NAK);
        }

    next_packet:
        continue;  // 跳转到下一个数据包
    }
}

5.主函数

#define MAX_FILE_SIZE 1024 * 100  // 假设最大接收100KB数据

int main(void) {
    uint8_t file_buffer[MAX_FILE_SIZE];  // 接收文件缓冲区
    uint32_t file_size = 0;

    // 初始化系统时钟、串口等
    SystemInit();
    USART_Init();

    // 接收文件
    file_size = xmodem_receive(file_buffer, MAX_FILE_SIZE);

    // 处理接收到的文件数据
    if (file_size > 0) {
        // 文件接收成功,处理数据
    } else {
        // 文件接收失败
    }

    while (1) {
        // 主循环
    }
}

注意事项

  • 确保串口的波特率、数据位、停止位等参数与发送端一致。

  • 如果数据包较大,目标缓冲区dest需要足够大以存储所有数据。

  • 在实际应用中,可能需要根据硬件平台调整超时时间和定时器实现。

使用SecureCRT给单片机发送固件

1. 准备工作

硬件连接
  • 确保单片机(HHD32F107)与 PC 通过串口连接(如 USB 转串口模块)。

  • 确认串口的波特率、数据位、停止位和校验位等参数与 SecureCRT 配置一致。

软件环境
  • SecureCRT:用于发送文件和与单片机通信。

  • 单片机程序:已实现 Xmodem 协议接收功能(基于前面提供的代码)。


2. SecureCRT 配置

2.1 打开 SecureCRT 并创建串口会话
  1. 打开 SecureCRT。

  2. 点击 File -> Connect,在弹出的窗口中选择 Serial 协议。

  3. 配置串口参数:

    • Port:选择正确的串口号(如 COM3)。

    • Baud rate:设置为与单片机一致的波特率(如 115200)。

    • Data bits:8。

    • Parity:None。

    • Stop bits:1。

    • Flow control:None。

  4. 点击 Connect,连接到串口。

2.2 配置 Xmodem 文件传输
  1. 在 SecureCRT 中,点击 Transfer -> Send Xmodem

  2. 选择要发送的文件(如 firmware.bin)。

  3. SecureCRT 会通过 Xmodem 协议将文件发送到单片机。


3. 单片机程序流程

3.1 单片机初始化
  • 单片机启动后,初始化串口(波特率、中断等)。

  • 初始化环形缓冲区(用于存储接收到的数据)。

  • 等待 SecureCRT 发送数据。

3.2 Xmodem 接收流程
  1. 发送 NAK 启动传输

    • 单片机上电后,主动发送一个 NAK0x15)信号,表示准备好接收数据。

    • SecureCRT 收到 NAK 后,开始发送文件。

  2. 接收数据包

    • SecureCRT 发送一个 Xmodem 数据包(1029 字节,包括 STX、包序号、数据、CRC 等)。

    • 单片机通过串口中断接收数据,并将其存储到环形缓冲区中。

    • 单片机从环形缓冲区中读取数据,并解析数据包。

  3. 校验数据包

    • 检查包序号和包序号反码是否正确。

    • 计算 CRC 校验值,并与接收到的 CRC 值进行比较。

    • 如果校验通过,发送 ACK0x06)确认;否则发送 NAK 请求重传。

  4. 接收完成

    • 当 SecureCRT 发送完所有数据后,会发送一个 EOT0x04)信号,表示传输结束。

    • 单片机收到 EOT 后,发送 ACK 确认,并结束接收流程。

  5. 存储数据

    • 单片机将接收到的有效数据存储到 Flash 或 RAM 中。

    评论 2
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值