[研电赛技术支持] GD32的Bootloader中实现Ymodem协议进行固件升级

[复制链接]
406|0
wowu 发表于 2025-11-7 17:15 | 显示全部楼层 |阅读模式
在GD32的Bootloader中实现Ymodem协议进行固件升级,主要涉及协议解析、数据包处理、CRC校验等关键环节。以下是具体实现步骤和代码示例:

Ymodem协议核心流程
Ymodem协议基于Xmodem-1K扩展,支持1024字节/包传输,使用CRC16校验,传输流程如下:

启动传输:接收方发送'C'(ASCII码0x43)请求开始传输。
文件头包:发送方发送第一个包(序号0),包含文件名、文件大小等信息。
数据包传输:发送方发送序号1~N的数据包,每包1024字节(不足补0xFF)。
结束确认:发送方发送EOT(0x04),接收方回应ACK(0x06)完成传输。
代码实现(GD32 USART + Ymodem)
以下是一个简化的Ymodem接收实现,基于GD32标准库(以GD32F30x为例):

1. 数据包结构定义
#define YMODEM_PACKET_SIZE  1024
#define YMODEM_HEADER_SIZE  3      // 包头: [SOH][序号][补码序号]
#define YMODEM_FOOTER_SIZE  2      // 包尾: CRC16 (2字节)

typedef struct {
    uint8_t header;                // SOH (0x01) 或 STX (0x02)
    uint8_t packet_num;            // 包序号 (1-based)
    uint8_t packet_num_complement; // 包序号的补码 (255 - packet_num)
    uint8_t data[YMODEM_PACKET_SIZE];
    uint16_t crc;                  // CRC16校验值
} YmodemPacket;



2. CRC16校验函数
// 计算CRC16 (Ymodem使用CRC-16-CCITT,多项式0x1021)
uint16_t ymodem_calc_crc(const uint8_t *data, uint32_t length) {
    uint16_t crc = 0;
    while (length--) {
        crc ^= (*data++) << 8;
        for (uint8_t i = 0; i < 8; i++) {
            crc = (crc & 0x8000) ? (crc << 1) ^ 0x1021 : (crc << 1);
        }
    }
    return crc;
}



3. 接收单包数据
// 从串口接收一个Ymodem数据包 (超时返回-1,校验失败返回0,成功返回1)
int8_t ymodem_receive_packet(YmodemPacket *packet, uint32_t timeout_ms) {
    uint8_t *p = (uint8_t *)packet;
    uint32_t start_time = get_tick_ms(); // 获取当前时间戳(需自行实现)

    // 接收包头 (SOH + 序号 + 补码序号)
    for (uint32_t i = 0; i < YMODEM_HEADER_SIZE; i++) {
        while (usart_flag_get(USART0, USART_FLAG_RBNE) == RESET) {
            if (get_tick_ms() - start_time > timeout_ms) return -1; // 超时
        }
        p[i] = usart_data_receive(USART0);
    }

    // 接收数据部分 (固定1024字节)
    for (uint32_t i = 0; i < YMODEM_PACKET_SIZE; i++) {
        while (usart_flag_get(USART0, USART_FLAG_RBNE) == RESET) {
            if (get_tick_ms() - start_time > timeout_ms) return -1;
        }
        p[YMODEM_HEADER_SIZE + i] = usart_data_receive(USART0);
    }

    // 接收CRC16 (2字节)
    for (uint32_t i = 0; i < YMODEM_FOOTER_SIZE; i++) {
        while (usart_flag_get(USART0, USART_FLAG_RBNE) == RESET) {
            if (get_tick_ms() - start_time > timeout_ms) return -1;
        }
        p[YMODEM_HEADER_SIZE + YMODEM_PACKET_SIZE + i] = usart_data_receive(USART0);
    }

    // 校验CRC
    uint16_t crc_received = (packet->crc << 8) | (packet->crc >> 8); // 大端转小端
    uint16_t crc_calculated = ymodem_calc_crc(packet->data, YMODEM_PACKET_SIZE);
    return (crc_received == crc_calculated) ? 1 : 0;
}



4. 主流程控制
// Ymodem固件接收主函数
void ymodem_receive_firmware(void) {
    YmodemPacket packet;
    uint32_t flash_addr = APP_START_ADDRESS; // APP区起始地址
    uint8_t retry = 0;
    uint8_t packet_expected = 1; // 期望的包序号 (从1开始)

    // 1. 发送'C'启动Ymodem传输
    usart_data_transmit(USART0, 'C');
    delay_ms(100);

    while (1) {
        // 2. 接收数据包
        int8_t status = ymodem_receive_packet(&packet, 1000);
        if (status == -1) {
            // 超时处理
            if (++retry > 3) break;
            usart_data_transmit(USART0, 'C'); // 重发'C'
            continue;
        } else if (status == 0) {
            // CRC校验失败
            usart_data_transmit(USART0, NAK); // 发送NAK (0x15) 请求重传
            continue;
        }

        // 3. 处理包序号
        if (packet.packet_num != packet_expected) {
            usart_data_transmit(USART0, NAK);
            continue;
        }

        // 4. 处理文件头包 (序号0)
        if (packet_expected == 0) {
            // 解析文件名和文件大小 (示例: 文件名以NULL结尾)
            char *filename = (char *)packet.data;
            uint32_t filesize = strtoul(filename + strlen(filename) + 1, NULL, 10);
            // 可以在此验证文件合法性
            usart_data_transmit(USART0, ACK);
            packet_expected = 1;
            continue;
        }

        // 5. 处理数据包 (写入Flash)
        flash_unlock(); // 解锁Flash
        flash_sector_erase(flash_addr); // 擦除目标扇区 (需根据Flash页大小调整)
        flash_program(flash_addr, (uint32_t *)packet.data, YMODEM_PACKET_SIZE / 4); // 按字写入
        flash_lock(); // 上锁Flash
        flash_addr += YMODEM_PACKET_SIZE;

        // 6. 回应ACK
        usart_data_transmit(USART0, ACK);
        packet_expected++;

        // 7. 检查结束标志 (EOT)
        if (packet.header == EOT) {
            usart_data_transmit(USART0, ACK);
            break;
        }
    }
}


关键注意事项
Flash操作限制:

GD32的Flash写入需按字(4字节)或多字进行,且必须先擦除后写入。
擦除操作以**扇区(Sector)或页(Page)**为单位,需根据具体型号调整(如GD32F303的页大小为2KB)。
协议优化:

超时重传:建议设置合理的超时时间(如1秒),超时后发送NAK或'C'重试。
内存缓冲:若RAM有限,可分块接收并直接写入Flash,避免全包缓存。
调试工具:

使用SecureCRT、Xshell等终端工具发送Ymodem文件(协议选择Ymodem)。
通过串口打印调试信息(如包序号、CRC值)辅助排查问题。
中断处理:

在接收关键阶段(如Flash写入)关闭全局中断,避免时序被打断。
确保USART中断优先级高于其他中断(如需实时响应)。
示例调用流程
int main(void) {
    // 硬件初始化
    system_init(); // 初始化时钟、GPIO等
    usart_init(115200); // 初始化串口

    // 检查是否需要升级(如检测特定引脚电平或Flash标志位)
    if (need_firmware_update()) {
        ymodem_receive_firmware(); // 启动Ymodem接收
        jump_to_app(); // 升级完成后跳转到APP
    } else {
        jump_to_app(); // 直接跳转
    }
}



————————————————
版权声明:本文为CSDN博主「蜀黍@猿」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_58087444/article/details/151613320

您需要登录后才可以回帖 登录 | 注册

本版积分规则

144

主题

4382

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部