[AT32F405] 【AT-START-F405测评】 SPIFlash下载算法制作

[复制链接]
 楼主| 夜声 发表于 2024-5-19 21:45 | 显示全部楼层 |阅读模式
1.前言
Flash 编程算法是一种用于擦除应用程序或将应用程序下载到 Flash 的程序代码。 MDK 本身支持的各种器件都自带下载算法,存放在 MDK 各种器件的软件包里面,但由于一些芯片容量较小,如STM32H750VB芯片或者一些其他需求需要将代码存储在flash里面时,就需要修改原有的下载算法下载至外部Flash。
2.硬件设计
开发板上使用的spiflash型号为EN25QH128A
c3cd8b914bb573f987091dab577cb334
对应电路原理图如下所示:
7041146019e6fddad92408b37c66e4e8
3.制作过程
在Keil\ARM\Flash下存在很多的下载算法,默认会存在很多的下载算法,如下所示,其中官方也提供了一个模板,我们可以根据这个模块进行更改实现我们自己的需求。
e554537935dd09b2735965e574f281b9
复制这个模板工程,打开如下:
7aed4a7bce0757009912ce57afffc73e
点击魔术棒,选择device,device中选择当前的开发板型号,AT32F405RCT7-7
e78da7f7640da50457c30ce551aa0277
在output下修改名称,即生成算法的名字
c9a69273628ce52fccafdc0691eadc0f
原有工程下的两个文件FlashPrg.c和FlashDev.c,FlashPrg.c主要是接口文件,需要自己实现,FlashDev.c则是Flash配置文件。
68cf77a2d69bb208c339dd6c1b3eaf23
接下来添加芯片的相关文件,一些配置信息,时钟,外设等内容:
dae4cbc72ad7c31cbd9a2f530783e80e
主要添加时钟,GPIO,QSPI几个文件
103767aad146b336137be3be631dd2da
接下来对接FlashPrg文件中的几个函数:
在初始化中,需要配置我们时钟,qspi使用到的引脚等相关配置:
  1. int Init (unsigned long adr, unsigned long clk, unsigned long fnc) {

  2.   /* Add your Code */
  3.   system_clock_config();
  4.    
  5.   qspi_gpio_config();
  6.    
  7.   /* switch to cmd port */
  8.   qspi_xip_enable(QSPI1, FALSE);

  9.   /* set sclk */
  10.   qspi_clk_division_set(QSPI1, QSPI_CLK_DIV_4);

  11.   /* set sck idle mode 0 */
  12.   qspi_sck_mode_set(QSPI1, QSPI_SCK_MODE_0);

  13.   /* set wip in bit 0 */
  14.   qspi_busy_config(QSPI1, QSPI_BUSY_OFFSET_0);

  15.   /* enable auto ispc */
  16.   qspi_auto_ispc_enable(QSPI1);

  17.   /*configure xip mode*/
  18.   en25qh128a_qspi_xip_init();
  19.    
  20.   return (0);                                  // Finished without Errors
  21. }
时钟配置调用的是at32f402_405_clock.c文件中的时钟配置,gpio的初始化:
  1. void qspi_gpio_config(void)
  2. {
  3.   gpio_init_type gpio_init_struct;

  4.   /* enable the qspi clock */
  5.   crm_periph_clock_enable(CRM_QSPI1_PERIPH_CLOCK, TRUE);

  6.   /* enable the pin clock */
  7.   crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE);
  8.   crm_periph_clock_enable(CRM_GPIOC_PERIPH_CLOCK, TRUE);

  9.   /* set default parameter */
  10.   gpio_default_para_init(&gpio_init_struct);

  11.   /* configure the io0 gpio */
  12.   gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
  13.   gpio_init_struct.gpio_out_type  = GPIO_OUTPUT_PUSH_PULL;
  14.   gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
  15.   gpio_init_struct.gpio_pins = GPIO_PINS_9;
  16.   gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
  17.   gpio_init(GPIOC, &gpio_init_struct);
  18.   gpio_pin_mux_config(GPIOC, GPIO_PINS_SOURCE9, GPIO_MUX_11);

  19.   /* configure the io1 gpio */
  20.   gpio_init_struct.gpio_pins = GPIO_PINS_7;
  21.   gpio_init(GPIOB, &gpio_init_struct);
  22.   gpio_pin_mux_config(GPIOB, GPIO_PINS_SOURCE7, GPIO_MUX_11);

  23.   /* configure the io2 gpio */
  24.   gpio_init_struct.gpio_pins = GPIO_PINS_8;
  25.   gpio_init(GPIOC, &gpio_init_struct);
  26.   gpio_pin_mux_config(GPIOC, GPIO_PINS_SOURCE8, GPIO_MUX_11);

  27.   /* configure the io3 gpio */
  28.   gpio_init_struct.gpio_pins = GPIO_PINS_5;
  29.   gpio_init(GPIOC, &gpio_init_struct);
  30.   gpio_pin_mux_config(GPIOC, GPIO_PINS_SOURCE5, GPIO_MUX_11);

  31.   /* configure the sck gpio */
  32.   gpio_init_struct.gpio_pins = GPIO_PINS_2;
  33.   gpio_init(GPIOB, &gpio_init_struct);
  34.   gpio_pin_mux_config(GPIOB, GPIO_PINS_SOURCE2, GPIO_MUX_11);

  35.   /* configure the cs gpio */
  36.   gpio_init_struct.gpio_pins = GPIO_PINS_11;
  37.   gpio_init(GPIOC, &gpio_init_struct);
  38.   gpio_pin_mux_config(GPIOC, GPIO_PINS_SOURCE11, GPIO_MUX_11);
  39. }
qspi_xip_enable_config的实现:
  1. /**
  2.   * [url=home.php?mod=space&uid=247401]@brief[/url]  配置 QSPI XIP 模式使能
  3.   * @param  无
  4.   * @retval 无
  5.   */
  6. void qspi_xip_enable_config(void)
  7. {
  8.   uint32_t xc0_val = 0, xc1_val = 0, xc2_val = 0; // 用于存储寄存器值的临时变量
  9.   qspi_xip_type *custom_xip_config; // 定义指向 qspi_xip_type 类型的指针 custom_xip_config

  10.   // 配置自定义 XIP 配置结构体的各个字段
  11.   custom_xip_config->read_instruction_code = 0xEB; // 设置读取指令代码为 0xEB
  12.   custom_xip_config->read_address_length = QSPI_XIP_ADDRLEN_3_BYTE; // 设置读取地址长度为 3 字节
  13.   custom_xip_config->read_operation_mode = QSPI_OPERATE_MODE_144; // 设置读取操作模式为 1-4-4 模式
  14.   custom_xip_config->read_second_dummy_cycle_num = 6; // 设置读取的第二个虚拟周期数为 6
  15.   custom_xip_config->read_select_mode = QSPI_XIPR_SEL_MODED; // 设置读取选择模式为 QSPI_XIPR_SEL_MODED
  16.   custom_xip_config->read_time_counter = 0xF; // 设置读取时间计数器为 0xF
  17.   custom_xip_config->read_data_counter = 32; // 设置读取数据计数器为 32

  18.   // 配置 xip_cmd_w0 寄存器的值
  19.   xc0_val = (uint32_t)custom_xip_config->read_second_dummy_cycle_num; // 将第二个虚拟周期数写入 xc0_val
  20.   xc0_val |= (uint32_t)(custom_xip_config->read_operation_mode << 8); // 将操作模式左移 8 位后写入 xc0_val
  21.   xc0_val |= (uint32_t)(custom_xip_config->read_address_length << 11); // 将地址长度左移 11 位后写入 xc0_val
  22.   xc0_val |= (uint32_t)(custom_xip_config->read_instruction_code << 12); // 将指令代码左移 12 位后写入 xc0_val
  23.   QSPI1->xip_cmd_w0 = xc0_val; // 将计算得到的 xc0_val 写入 QSPI1->xip_cmd_w0 寄存器

  24.   // 配置 xip_cmd_w1 寄存器的值
  25.   xc1_val = (uint32_t)custom_xip_config->write_second_dummy_cycle_num; // 将第二个虚拟周期数写入 xc1_val
  26.   xc1_val |= (uint32_t)(custom_xip_config->write_operation_mode << 8); // 将操作模式左移 8 位后写入 xc1_val
  27.   xc1_val |= (uint32_t)(custom_xip_config->write_address_length << 11); // 将地址长度左移 11 位后写入 xc1_val
  28.   xc1_val |= (uint32_t)(custom_xip_config->write_instruction_code << 12); // 将指令代码左移 12 位后写入 xc1_val
  29.   QSPI1->xip_cmd_w1 = xc1_val; // 将计算得到的 xc1_val 写入 QSPI1->xip_cmd_w1 寄存器

  30.   // 配置 xip_cmd_w2 寄存器的值
  31.   xc2_val = (uint32_t)custom_xip_config->read_data_counter; // 将读取数据计数器写入 xc2_val
  32.   xc2_val |= (uint32_t)(custom_xip_config->read_time_counter << 8); // 将读取时间计数器左移 8 位后写入 xc2_val
  33.   xc2_val |= (uint32_t)(custom_xip_config->read_select_mode << 15); // 将读取选择模式左移 15 位后写入 xc2_val
  34.   xc2_val |= (uint32_t)(custom_xip_config->write_data_counter << 16); // 将写入数据计数器左移 16 位后写入 xc2_val
  35.   xc2_val |= (uint32_t)(custom_xip_config->write_time_counter << 24); // 将写入时间计数器左移 24 位后写入 xc2_val
  36.   xc2_val |= (uint32_t)(custom_xip_config->write_select_mode << 31); // 将写入选择模式左移 31 位后写入 xc2_val
  37.   QSPI1->xip_cmd_w2 = xc2_val; // 将计算得到的 xc2_val 写入 QSPI1->xip_cmd_w2 寄存器

  38.   qspi_xip_enable(QSPI1, TRUE); // 启用 XIP 模式
  39. }


接下来UnInit的对接:
  1. int UnInit (unsigned long fnc) {

  2.   /* Add your Code */
  3.   system_clock_disable();
  4.   qspi_xip_enable_config();
  5.   return (0);                                  // Finished without Errors
  6. }
EraseChip函数对接:
  1. int EraseChip (void) {

  2.   /* Add your Code */
  3.     /* switch to cmd port */
  4.   qspi_xip_enable(QSPI1, FALSE);
  5.   /* qspi write enable */
  6.   qspi_write_enable();

  7.   /* qspi chip erase */
  8.   qspi_chip_erase();

  9.   /* qspi check busy */
  10.   qspi_busy_check();

  11.   qspi_xip_enable_config();
  12.    
  13.    
  14.   return (0);                                  // Finished without Errors
  15. }
qspi_chip_erase函数实现:
  1. /**
  2.   * @brief  esmt32m 芯片擦除命令
  3.   * @param  无
  4.   * @retval 无
  5.   */
  6. void qspi_chip_erase(void)
  7. {
  8.   QSPI1->cmd_w0 = 0;                           // 设置命令寄存器W0为0,无地址需要发送
  9.   QSPI1->cmd_w1 = 0x01000000;                  // 设置命令寄存器W1,指定一个字节的数据长度和单字节命令
  10.   QSPI1->cmd_w2 = 0;                           // 设置命令寄存器W2为0,无额外的数据
  11.   QSPI1->cmd_w3 = 0x60000002;                  // 设置命令寄存器W3,发送0x60命令(芯片擦除),并使用指令周期2
  12.   while((flag_status)(QSPI1->cmdsts_bit.cmdsts) == RESET); // 等待命令完成,直到命令状态位变为SET
  13.   QSPI1->cmdsts_bit.cmdsts = TRUE;             // 清除命令状态位,准备接受下一个命令
  14. }
ProgramPage的对接:

  1. /**
  2.   * @brief  编程页面。
  3.   * @param  adr: 页面起始地址
  4.             sz:  页面大小
  5.             buf: 页面数据
  6.   * @retval 0 表示成功,1 表示失败
  7.   */
  8. int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf)
  9. {
  10.   uint32_t i;
  11.   adr -= QSPI1_MEM_BASE; // 将地址转换为相对于 QSPI 基地址的偏移
  12.   qspi_xip_enable(QSPI1, FALSE); // 禁用 XIP 模式以进行编程操作
  13.   qspi_write_enable(); // 使能 QSPI 写操作

  14.   // 设置 QSPI 编程命令
  15.   QSPI1->cmd_w0 = adr; // 设置命令寄存器W0为要编程的起始地址
  16.   QSPI1->cmd_w1 = 0x01000003; // 设置命令寄存器W1,指定地址和命令数据长度
  17.   QSPI1->cmd_w2 = sz; // 设置命令寄存器W2为要编程的数据大小
  18.   QSPI1->cmd_w3 = 0x32000042; // 设置命令寄存器W3,发送 0x32 命令(页编程),并使用指令周期 42
  19.   
  20.   // 写入数据到 QSPI 数据寄存器
  21.   for(i = 0; i < sz; ++i)
  22.   {
  23.     while((flag_status)(QSPI1->fifosts_bit.txfifordy) == RESET); // 等待发送 FIFO 准备好
  24.     QSPI1->dt_u8 = *buf++; // 将数据写入 QSPI 数据寄存器
  25.   }
  26.   while((flag_status)(QSPI1->cmdsts_bit.cmdsts) == RESET); // 等待命令完成
  27.   QSPI1->cmdsts_bit.cmdsts = TRUE; // 清除命令状态位,准备接受下一个命令

  28.   qspi_busy_check(); // 检查 QSPI 是否忙碌

  29.   qspi_xip_enable_config(); // 重新配置并启用 XIP 模式
  30.   return 0; // 返回 0 表示成功
  31. }
FlashDev.C函数的配置:
  1. struct FlashDevice const FlashDevice  =  {
  2.    FLASH_DRV_VERS,             // Driver Version, do not modify!
  3.    "AT32F405_EN25Q128",        // Device Name
  4.    EXTSPI,                     // Device Type
  5.    0x90000000,                 // Device Start Address
  6.    16*1024*1024,               // Device Size in Bytes (256kB)
  7.    256,                       // Programming Page Size
  8.    0,                          // Reserved, must be 0
  9.    0xFF,                       // Initial Content of Erased Memory
  10.    3000,                        // Program Page Timeout 100 mSec
  11.    3000,                       // Erase Sector Timeout 3000 mSec

  12. // Specify Size and Address of Sectors
  13.    4096, 0x000000,         // Sector Size  8kB (8 Sectors)
  14.    SECTOR_END
  15. };
现在函数已经对接完了,编译生产算法文件,配置一下地方,模板工程中是已经配置好的
1.jpg
接下来编译工程,生产下载算法文件:
2.jpg
接下来将编译好的下载算法文件复制到Keil\ARM\Flash下,如下所示:
3.jpg

打开一个LED灯工程,在下载算法界面选择刚制作的外部flash算法:
5.jpg
点击确定后,点击下载即可。


trucyw 发表于 2025-5-5 16:53 | 显示全部楼层
不错,改天可以测试下
zhengshuai888 发表于 2025-5-14 21:21 来自手机 | 显示全部楼层
MCU可以从外部flash启动?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

27

主题

89

帖子

2

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