GD32 串口升级 IAP升级程序 芯片:GD32F3 移植性:主要使用的是串口部分, GD32系列其他芯片移植比较容易。 stm32系列有较大改动,主要因为是函 数库不同。 通信协议:提供通信协议文档 上位机功能: 升级,重启,导出芯片内的程序。 使用C# winform编写 可得: bootloader源码, 上位机 测试用主程序, 协议word文档, 操作说明文档。 电子资料

直接断电再上电试试——这大概是嵌入式开发中最常听到的骚操作了。但搞过IAP升级的老铁都知道,断电重启有时候真的不够优雅。今天咱们来盘一盘GD32F3的串口IAP实现,手把手教你让设备自己完成空中升级。

先看Bootloader的核心跳转逻辑。这个骚操作的关键在于中断向量表重定位,来看这段硬核代码:

void jump_to_app(uint32_t app_addr)
{
    typedef void (*pFunction)(void);
    pFunction Jump_To_Application;
    
    if(((*(__IO uint32_t*)app_addr) & 0x2FFE0000 ) == 0x20000000) //验证栈顶地址
    {
        __disable_irq();
        
        /* 重设中断向量表偏移 */
        SCB->VTOR = app_addr & 0x1FFFFF; 
        
        /* 获取复位地址 */
        uint32_t jump_address = *(__IO uint32_t*)(app_addr + 4);
        Jump_To_Application = (pFunction)jump_address;
        
        /* 初始化主程序堆栈指针 */
        __set_MSP(*(__IO uint32_t*)app_addr);
        
        Jump_To_Application();
    }
}

这段代码里有个骚操作验证栈顶地址。为什么要校验0x2FFE0000?因为GD32F3的SRAM地址范围是0x20000000开头,这个掩码操作能快速判断应用程序是否有效。不过要注意,不同型号的掩码值可能需要调整,别直接抄作业。

GD32 串口升级 IAP升级程序 芯片:GD32F3 移植性:主要使用的是串口部分, GD32系列其他芯片移植比较容易。 stm32系列有较大改动,主要因为是函 数库不同。 通信协议:提供通信协议文档 上位机功能: 升级,重启,导出芯片内的程序。 使用C# winform编写 可得: bootloader源码, 上位机 测试用主程序, 协议word文档, 操作说明文档。 电子资料

通信协议方面,咱们设计的帧结构简单粗暴:

| 起始头(0xAA55) | 指令类型 | 数据长度 | 数据区 | 校验和 |

对应的解析代码里有个小技巧——状态机解析。举个栗子:

typedef enum {
    FRAME_HEAD1,
    FRAME_HEAD2,
    CMD_TYPE,
    DATA_LEN,
    DATA_RECV,
    CHECK_SUM
} FrameState;

void parse_uart_data(uint8_t ch)
{
    static FrameState state = FRAME_HEAD1;
    static uint8_t data_len_counter = 0;
    static uint8_t checksum = 0;
    
    switch(state){
        case FRAME_HEAD1:
            if(ch == 0xAA){
                state = FRAME_HEAD2;
                checksum = ch;
            }
            break;
        case FRAME_HEAD2:
            if(ch == 0x55){
                state = CMD_TYPE;
                checksum += ch;
            } else {
                state = FRAME_HEAD1;
            }
            break;
        //...其他状态处理
    }
}

状态机的设计能有效应对数据粘包问题,比直接判断帧头可靠得多。不过要注意静态变量的使用,在多任务环境下可能得改成结构体封装。

上位机用C#搞了个带进度条的烧录界面。关键代码在文件读取和分包发送:

byte[] fileBytes = File.ReadAllBytes(filePath);
int packetSize = 256; //根据波特率调整
for(int i=0; i<fileBytes.Length; i+=packetSize){
    int remain = fileBytes.Length - i;
    int sendLen = remain > packetSize ? packetSize : remain;
    byte[] packet = new byte[sendLen + 5];
    packet[0] = 0xAA;
    packet[1] = 0x55;
    packet[2] = (byte)CommandType.DATA;
    packet[3] = (byte)sendLen;
    Array.Copy(fileBytes, i, packet, 4, sendLen);
    packet[sendLen+4] = CalcChecksum(packet, sendLen+4);
    serialPort.Write(packet, 0, sendLen+5);
    //更新进度条
    UpdateProgressBar(i*100/fileBytes.Length);
}

这里有个坑点:分包大小要根据波特率动态调整。比如115200波特率下,256字节一包大约要22ms发送时间,得确保设备端接收缓冲区足够大。

移植到STM32时,最大的坑在于外设库函数。比如GD32的GPIO初始化:

gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);

而STM32标准库是:

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

这种函数接口差异会导致移植时要重写大量硬件驱动层代码。建议用宏定义做平台抽象,比如:

#ifdef GD32_PLATFORM
    #define UART_SEND(data,len) usart_data_transmit(USART0, data, len)
#elif defined(STM32_PLATFORM)
    #define UART_SEND(data,len) HAL_UART_Transmit(&huart1, data, len, 1000)
#endif

最后提醒下,IAP工程里务必要配置好分散加载文件。比如在Keil里要把APP区域的起始地址设置为0x8004000(假设Bootloader占16KB),同时中断向量表偏移量要和SCB->VTOR设置一致。搞错了这个,跳转后直接hardfault教你做人。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐