[研电赛技术支持] GD32F330 Bootloader OTA固件更新

[复制链接]
 楼主| guanjiaer 发表于 2025-7-8 14:44 | 显示全部楼层 |阅读模式
1.Flash分区

30222686c9006a0614.png

2.实现逻辑

97787686c900d3fb42.png

3.固件传输协议
固件由主板通过串口发送给MCU,通讯协议如下:

61213686c901e14557.png

共103字节

4. Bootloader
main.c

#include "gd32f3x0.h"
#include "systick.h"

#define OTA_FLAG_ADDR    0x0800FFF8
#define OTA_FLAG_VALUE   0xA5A5A5A5
#define OTA_CACHE_ADDR   0x08009000
#define APP_ADDR         0x08002000
#define APP_MAX_SIZE     (27 * 1024)  // 不为28K,避免flash末尾的ota_flag也被写入,影响app
#define APP_SIZE         (27 * 1024)

typedef void (*app_func)(void);

void jump_to_app(void) {
       
                uint32_t sp = *(uint32_t *)APP_ADDR;
    __set_MSP(sp);
                uint32_t pc = *(uint32_t *)(APP_ADDR + 4);
    ((app_func)pc)();
}

//void jump_to_app(void) {
//       
//                uint32_t sp = *(uint32_t *)OTA_CACHE_ADDR;
//    __set_MSP(sp);
//                uint32_t pc = *(uint32_t *)(OTA_CACHE_ADDR + 4);
//    ((app_func)pc)();
//}

//void copy_ota_to_app(void) {
//    fmc_unlock();
//    for (uint32_t i = 0; i < APP_MAX_SIZE; i += 4) {
//        uint32_t data = *(uint32_t *)(OTA_CACHE_ADDR + i);
//        fmc_word_program(APP_ADDR + i, data);       
//    }
//    fmc_page_erase(OTA_FLAG_ADDR); // 清除升级标志
//    fmc_word_program(OTA_FLAG_ADDR, 0xFFFFFFFF);
//    fmc_lock();
//}

void copy_ota_to_app(void) {
    fmc_unlock();

    // 擦除 APP 区域 每页1KB
    for (uint32_t addr = APP_ADDR; addr < (APP_ADDR + APP_SIZE); addr += 1024) {
        fmc_page_erase(addr);
        while (fmc_flag_get(FMC_FLAG_END) == RESET);
    }

    // 写入新数据
    for (uint32_t i = 0; i <= APP_MAX_SIZE; i += 4) {
        uint32_t data = *(uint32_t *)(OTA_CACHE_ADDR + i);
        fmc_word_program(APP_ADDR + i, data);
        while (fmc_flag_get(FMC_FLAG_END) == RESET);
    }

    // 清除 OTA_FLAG
//    fmc_page_erase(OTA_FLAG_ADDR);
        fmc_page_erase(OTA_FLAG_ADDR & 0xFFFFFC00);  // 页对齐(0x400)
    while (fmc_flag_get(FMC_FLAG_END) == RESET);
    fmc_word_program(OTA_FLAG_ADDR, 0xFFFFFFFF);
    while (fmc_flag_get(FMC_FLAG_END) == RESET);

    fmc_lock();
}


int main(void) {

        rcu_periph_clock_enable(RCU_GPIOA);
    gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_0);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);
    gpio_bit_set(GPIOA, GPIO_PIN_0);
       
        if (*(uint32_t *)OTA_FLAG_ADDR == OTA_FLAG_VALUE) {   
                copy_ota_to_app();       
    }
               
        SCB->VTOR = APP_ADDR;
               
    jump_to_app(); // 跳转APP
    while (1); // 不应运行到此
}



烧录位置配置:

70470686c902cd8fe4.png

5.APP
main.c

#include "gd32f3x0.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "gd32f350r_eval.h"
                                                                                               
uint8_t uart_rx_buf[PACKET_TOTAL_LEN];
uint8_t uart_rx_index = 0;
uint8_t in_packet = 0;       
volatile uint8_t packet_ready = 0;         
uint8_t packet_buf[PACKET_TOTAL_LEN];   

//bootloader 8k  app 28k  ota_cache 28k-8bit  ota_flag 8bit                                                                                                                                
#define APP_ADDR         0x08002000
#define OTA_CACHE_ADDR   0x08009000
#define OTA_FLAG_ADDR    0x0800FFF8
#define OTA_FLAG_VALUE   0xA5A5A5A5    //有固件写入

uint32_t ota_offset = 0;

void flash_write_word(uint32_t address, uint32_t data) {
    fmc_word_program(address, data);
}

void flash_erase_pages(uint32_t start_addr, uint32_t length) {
    for (uint32_t addr = start_addr; addr < start_addr + length; addr += 1024) {
        fmc_page_erase(addr);
    }
}

void ota_start(void) {
    ota_offset = 0;       
        usart_interrupt_disable(USART0, USART_INT_RBNE); // 关闭串口中断 中断干扰flash擦写       
    fmc_unlock();
    flash_erase_pages(OTA_CACHE_ADDR, 27 * 1024); //ota_cache 27k 避免擦除ota_flag
    fmc_lock();
    usart_interrupt_enable(USART0, USART_INT_RBNE); // 重新开启串口中断
}


void ota_write_bin_chunk(uint8_t *data, uint32_t len) {
    fmc_unlock();
    //4字节写入
        for (uint32_t i = 0; i < len; i += 4) {
                uint32_t word = *(uint32_t *)(data + i);
                flash_write_word(OTA_CACHE_ADDR + ota_offset + i, word);
                while (fmc_flag_get(FMC_FLAG_END) == RESET);
        }
    fmc_lock();
    ota_offset += len;
}

void ota_finish(void) {
    fmc_unlock();
    flash_erase_pages(OTA_FLAG_ADDR, 1024);  
        flash_write_word(OTA_FLAG_ADDR, OTA_FLAG_VALUE);            
    fmc_lock();
        delay_1ms(1000);
    NVIC_SystemReset();                                           //软复位MCU reset后bootloader检查ota_flag
}

void handle_uart_ota_packet(uint8_t *buf, uint32_t len) {
    uint8_t cmd = buf[0];
    switch(cmd) {
        case 0x01: ota_start();  break;                           //擦除ota_cache
        case 0x02: ota_write_bin_chunk(&buf[1], len - 1); break;  //写入ota_cache
        case 0x03: ota_finish();  break;                           //ota_flag赋值,reset MCU
    }
}                                                                                                                               
                                                                       
static void _soft_delay_(uint32_t time)
{
    __IO uint32_t i;
    for(i=0; i<time*10; i++){
    }
}

void USART0_IRQHandler(void)
{
                if (usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE)) {
        uint8_t ch = usart_data_receive(USART0);
                       
//                                gpio_bit_reset(GPIOA, GPIO_PIN_0);

        if (!in_packet) {
            if (uart_rx_index == 0 && ch == 0x4D) {
                uart_rx_buf[uart_rx_index++] = ch;
            } else if (uart_rx_index == 1 && ch == 0x4D) {
                uart_rx_buf[uart_rx_index++] = ch;
                in_packet = 1;
            } else {
                uart_rx_index = 0;
            }
        } else {
            uart_rx_buf[uart_rx_index++] = ch;

            if (uart_rx_index == 4) {
                uint16_t length = uart_rx_buf[2] | (uart_rx_buf[3] << 8);
                if (length != 0x62) {
                    uart_rx_index = 0;
                    in_packet = 0;
                }
            }

            if (uart_rx_index == PACKET_TOTAL_LEN) {
                for (int i = 0; i < PACKET_TOTAL_LEN; ++i){
                                                                                packet_buf = uart_rx_buf;
                                                                }
                packet_ready = 1;

                uart_rx_index = 0;
                in_packet = 0;
            }
        }
    }
}

int main(void)
{
        SCB->VTOR = APP_ADDR; //中断函数向量表重定向
       
    systick_config();      
    gd_eval_com_init(EVAL_COM);  
       
        Fixed_Color_Display(0x000000);         
               
        usart_interrupt_enable(USART0, USART_INT_RBNE);
    nvic_irq_enable(USART0_IRQn, 2, 0);
       
        while (1) {       

                       
                //串口接收数据后处理
                if (packet_ready) {
                        packet_ready = 0;               
                        uint8_t cmd = packet_buf[4];
                        uint8_t checksum = packet_buf[PACKET_TOTAL_LEN - 1];
                        uint8_t sum = cmd;

                        for (int i = 0; i < 97; ++i) sum += packet_buf[5 + i];
                        sum &= 0xFF;  //checknum对比

                        if (sum == checksum && cmd == 0x30) {
                                uint8_t row = packet_buf[5];
                                const uint8_t* grb_data = &packet_buf[6];
                                Set_Row_Color_From_Payload(row, grb_data);
                        } else if(cmd == 0x40){
                                //OTA                       
                                handle_uart_ota_packet(&packet_buf[5], 97);
                        }
                }
    }
}




烧录位置及擦除配置:

67182686c903828845.png

99638686c903f018e1.png

选择Erase Sectors,避免第一次烧录APP时将Bootloader擦除。

由于新固件以.bin格式传输与接收,默认情况下,编译生成的项目文件为.hex格式,需要添加用户指令:

36393686c904537310.png

fromelf.exe --bin --output .\output\Project.bin .\output\Project.axf


如此编译后,output文件夹下会生成bin文件,用于后续的固件发送与更新:

47686686c904da53e4.png

6.主板发送新固件
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>

#define PACKET_SIZE 103
#define HEADER_BYTE 0x4D
#define CMD_OTA     0x40

int open_serial(const char *device) {
    int fd = open(device, O_RDWR | O_NOCTTY | O_SYNC);
    if (fd < 0) {
        perror("open serial");
        return -1;
    }

    struct termios tty;
    if (tcgetattr(fd, &tty) != 0) {
        perror("tcgetattr");
        close(fd);
        return -1;
    }

    cfsetospeed(&tty, B115200);
    cfsetispeed(&tty, B115200);

    tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;
    tty.c_iflag = IGNBRK;
    tty.c_lflag = 0;                // no signaling chars, no echo
    tty.c_oflag = 0;                // no remapping, no delays
    tty.c_cc[VMIN] = 1;             // read doesn't block
    tty.c_cc[VTIME] = 1;            // 0.1 seconds read timeout

    tty.c_iflag &= ~(IXON | IXOFF | IXANY);  // shut off xon/xoff ctrl
    tty.c_cflag |= (CLOCAL | CREAD);         // ignore modem controls
    tty.c_cflag &= ~(PARENB | PARODD);       // no parity
    tty.c_cflag &= ~CSTOPB;
    tty.c_cflag &= ~CRTSCTS;

    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
        perror("tcsetattr");
        close(fd);
        return -1;
    }

    return fd;
}

void build_packet(uint8_t *packet, uint8_t ota_cmd, const uint8_t *payload, size_t payload_len) {
    memset(packet, 0, PACKET_SIZE);
    packet[0] = HEADER_BYTE;
    packet[1] = HEADER_BYTE;
    packet[2] = 0x62;  // Length = 0x62 (98 bytes: Command + Payload[96] + Checksum)
    packet[3] = 0x00;
    packet[4] = CMD_OTA;
    packet[5] = ota_cmd;

    if (payload && payload_len > 0) {
        memcpy(&packet[6], payload, payload_len);
    }

    uint8_t sum = CMD_OTA + ota_cmd;
    for (int i = 0; i < 96; ++i)
        sum += packet[6 + i];

    packet[102] = sum & 0xFF;
}

int send_packet(int fd, uint8_t ota_cmd, const uint8_t *payload, size_t payload_len) {
    uint8_t packet[PACKET_SIZE];
    build_packet(packet, ota_cmd, payload, payload_len);
    return write(fd, packet, PACKET_SIZE);
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: %s firmware.bin\n", argv[0]);
        return 1;
    }

    const char *bin_path = argv[1];
    FILE *fp = fopen(bin_path, "rb");
    if (!fp) {
        perror("fopen bin");
        return 1;
    }

    int fd = open_serial("/dev/ttyS4");
    if (fd < 0) {
        fclose(fp);
        return 1;
    }

    // Step 1: Send ota_start (0x01)
    printf("Sending OTA_START...\n");
    send_packet(fd, 0x01, NULL, 0);
    usleep(1000 * 1000);

    // Step 2: Send ota_write_bin_chunk (0x02)
    uint8_t buf[96];
    size_t chunk = 0;
    while (!feof(fp)) {
        size_t read_bytes = fread(buf, 1, 96, fp);
        if (read_bytes < 96) {
            memset(buf + read_bytes, 0xFF, 96 - read_bytes); // pad with 0xFF
        }
        printf("Sending chunk %zu...\n", chunk++);
        send_packet(fd, 0x02, buf, 96);
        usleep(100 * 1000);
    }

    // Step 3: Send ota_finish (0x03)
    usleep(1000*1000);
    printf("Sending OTA_FINISH...\n");
    send_packet(fd, 0x03, NULL, 0);
//    send_packet(fd, 0x03, NULL, 0);
//    send_packet(fd, 0x03, NULL, 0);
    usleep(1000 * 1000);

    fclose(fp);
    close(fd);
    printf("OTA completed.\n");
    return 0;
}



————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/m0_60013390/article/details/148873319

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

本版积分规则

99

主题

4338

帖子

2

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