[研电赛技术支持] GD32F4 通过USB主机模式的U盘实现IAP程序升级

[复制链接]
Puchou 发表于 2025-8-11 21:09 | 显示全部楼层 |阅读模式
一、涉及到的模块代码

1、USB主机模式驱动程序,参照官方例程GD32F4xx_Firmware_Library_V3.2.0中的usb_host_msc_udisk

2、GD32内部FLASH读写,参照官方例程GD32F4xx_Firmware_Library_V3.2.0中的Program_erase

二、实现步骤

1、bootloader和application程序的分区设置,需要在Keil中配置

bootloader

on-chip地址和大小,从0地址开始,128KB




Keil Falsh烧录设置,用于开发阶段




application

on-chip地址和大小,从boot空间之后的地址开始,剩余896KB




Keil Falsh烧录设置,用于开发阶段手动烧录程序




2、boot程序检测U盘并挂载文件

设置U盘自动处理状态,参照文章顶部的官方例程的玩法

usbh_user_status usbh_user_userinput(void)
{
        usbh_user_status usbh_usr_status;
    usbh_usr_status = USR_IN_RESP_OK;
        return usbh_usr_status;
}


然后状态机处理函数usbh_msc_handle会通过回调函数来处理U盘文件的读取   

    case MSC_IDLE:

        uhost->usr_cb->dev_user_app();

        status = USBH_OK;

        break;


读写文件,复位硬件然后跳转到App

int usbh_usr_msc_application(void)
{
        printf("USB MSC application started.\n");
        fatfs_readandwrite_config(&fatfs);
        nvic_irq_disable(USBFS_IRQn);
        usbh_deinit(&usb_host_msc);
        system_hardware_deinit();
        JumpToApp();
        return 0;
}



  读写文件到内部FLASH中

uint8_t fatfs_readandwrite_config(FATFS *pFs)
{
    FRESULT fs_res;           // 文件操作的结果状态。
    FIL fs_file;              // 存储文件的信息,如文件名、文件大小、当前读写位置等
    fmc_state_enum fmc_state; // 存储Flash存储器操作(如擦除、编程)的结果状态

    char fs_buf[FLASH_TMP_BUF_SIZE]; // 定义一个缓冲区,用于临时存储读取的固件数据
    uint32_t fs_Len = 0;             // 存储实际读取的数据长度
    uint32_t index;                  // 循环索引变量
    uint32_t Addr;                   // Flash地址
    uint32_t flashValue = 0;         // 存储要写入Flash的一个字(4字节)

    // 注册文件系统
    fs_res = f_mount(pFs, "0:/", 1); // 尝试立即挂载文件系统
    if (fs_res != FR_OK)             // 如果挂载失败
    {
        printf("Mount error!\n");
        return 0xFF; // 返回错误代码,表示没有MSC(大容量存储)设备
    }

    // 打开固件文件
    strcpy(cFileName, Path); // 将固件文件的路径拷贝到cFileName
    strcat(cFileName, Name); // 将固件文件的名称追加到cFileName
    uart_send_string(cFileName);
    fs_res = f_open(&fs_file, (const TCHAR *)cFileName, FA_READ); // 尝试以只读方式打开固件文件
    if (fs_res != FR_OK)                                          // 如果打开文件失败
    {
        f_unmount("0:/"); // 注销文件系统
        printf("File open error!\n");
        return 0xFF; // 返回错误代码,表示没有找到文件
    }

    erase_app_sectors(); // 擦除应用程序Flash扇区
    fmc_unlock();        // 解锁Flash

    // 按页写入固件数据
    Addr = FLASH_APP_ADDRESS; // 设置Flash写入起始地址
    do
    {
        fs_res = f_read(&fs_file, fs_buf, FLASH_TMP_BUF_SIZE, &fs_Len); // 从文件中读取数据到缓冲区
        if (fs_res != FR_OK)                                            // 如果读取失败
        {
            printf("File read error\n");
            return 0xFF; // 返回错误代码,表示文件读取错误
        }
        // 将缓冲区中的数据写入Flash
        for (index = 0; index < fs_Len; index = index + 4)
        {
            flashValue = fs_buf[index + 3] << 24;
            flashValue += fs_buf[index + 2] << 16;
            flashValue += fs_buf[index + 1] << 8;
            flashValue += fs_buf[index];
            fmc_state = fmc_word_program(Addr, flashValue); // 写入一个字到Flash
            if (FMC_READY != fmc_state)                     // 如果写入失败
            {
                return 0xFF; // 返回错误代码,表示内存错误
            }
            if (*(__IO uint32_t *)Addr != flashValue) // 如果写入的数据与预期不一致
            {
                return 0xFF; // 返回错误代码,表示内存不匹配
            }
            Addr += 4; // 更新地址,准备写入下一个字
        }
    } while (fs_Len == FLASH_TMP_BUF_SIZE); // 如果读取的数据长度等于缓冲区大小,则继续写入

    // 上锁Flash
    fmc_lock();

    f_close(&fs_file); // 关闭文件,重命名文件,注销文件系统
    f_unmount("0:/");  // 注销文件系统
    printf("File write complete!\n");
    return 0; // 返回成功代码
}



跳转应用程序

/**
* @brief  跳转到应用程序
* @param  None
* @retval None
*/
void JumpToApp(void)
{
    // 定义函数指针类型
    typedef void (*pFunction)(void);
    // 声明函数指针变量,用于跳转到应用程序
    pFunction JumpToAppPrg;
    // 存储应用程序复位处理程序地址
    uint32_t AppPrgAddr;

    // 延时100ms,确保系统稳定
    delay_1ms(100);

    // 检查应用程序栈顶地址是否合法
    // 用户代码区的第一个字用于存放栈顶地址,合法的SRAM地址应该在0x20000000范围内
    if (((*(__IO uint32_t *)FLASH_APP_ADDRESS) & 0x2FFE0000) == 0x20000000)
    {
        // 禁用全局中断
        hw_irq_disable();

        // 关闭USART0中断
        usart_interrupt_disable(USART0, USART_INT_RBNE);  // 禁用接收中断
        usart_interrupt_disable(USART0, USART_INT_TC);    // 禁用发送完成中断
        usart_interrupt_flag_clear(USART0, USART_INT_FLAG_RBNE); // 清除接收中断标志
        usart_interrupt_flag_clear(USART0, USART_INT_FLAG_TC);   // 清除发送完成中断标志
        usart_deinit(USART0);  // 复位USART0
        nvic_irq_disable(USART0_IRQn); // 禁用USART0 NVIC中断

        // 关闭TIMER2中断
        timer_interrupt_disable(TIMER2, TIMER_INT_BRK);   // 禁用TIMER2中断
        timer_interrupt_flag_clear(TIMER0, TIMER_INT_BRK); // 清除TIMER0中断标志
        timer_deinit(TIMER2);  // 复位TIMER2
        nvic_irq_disable(TIMER2_IRQn); // 禁用TIMER2 NVIC中断

        // 禁用所有可屏蔽中断
        __set_PRIMASK(1);

        // 禁用外设时钟
        rcu_periph_clock_disable(RCU_USBFS);    // 禁用USBFS时钟
        rcu_periph_clock_disable(RCU_TIMER2);   // 禁用TIMER2时钟
        rcu_periph_clock_disable(RCU_USART0);   // 禁用USART0时钟
        rcu_periph_clock_disable(RCU_USART0);   // 重复禁用USART0时钟(可能是笔误)

        // 获取应用程序复位处理程序地址(用户代码区第二个字为程序开始地址)
        AppPrgAddr = *(volatile uint32_t *)(FLASH_APP_ADDRESS + 4);
        JumpToAppPrg = (pFunction)AppPrgAddr;

        // 初始化应用程序堆栈指针
        // 将用户定义的flash首地址存储的栈顶地址告诉ARM内核
        __set_MSP(*(volatile uint32_t *)FLASH_APP_ADDRESS);

        // 跳转到应用程序
        JumpToAppPrg();
    }
    else
    {
        // 如果栈顶地址不合法,则进入死循环
        while (1)
        {
        }
    }
}



注意boot程序此处通过        hw_irq_disable(); // 禁用全局中断;那么在跳转到App之后需要通过hw_irq_enable();使能全局中断。还需要设置App的向量表地址

        nvic_vector_table_set(NVIC_VECTTAB_FLASH, 0x20000);//设置向量表地址
        hw_irq_enable();//使能全局中断


至此,GD32通过USB接口的U盘读取功能,实现了程序升级;涉及到GD32的USB主机和FMC读写功能代码。

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

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

本版积分规则

64

主题

207

帖子

0

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