在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
|
|