一、涉及到的模块代码
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
|