打印
[AT32F405]

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

[复制链接]
550|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
夜声|  楼主 | 2024-5-19 21:45 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
1.前言
Flash 编程算法是一种用于擦除应用程序或将应用程序下载到 Flash 的程序代码。 MDK 本身支持的各种器件都自带下载算法,存放在 MDK 各种器件的软件包里面,但由于一些芯片容量较小,如STM32H750VB芯片或者一些其他需求需要将代码存储在flash里面时,就需要修改原有的下载算法下载至外部Flash。
2.硬件设计
开发板上使用的spiflash型号为EN25QH128A

对应电路原理图如下所示:

3.制作过程
在Keil\ARM\Flash下存在很多的下载算法,默认会存在很多的下载算法,如下所示,其中官方也提供了一个模板,我们可以根据这个模块进行更改实现我们自己的需求。

复制这个模板工程,打开如下:

点击魔术棒,选择device,device中选择当前的开发板型号,AT32F405RCT7-7

在output下修改名称,即生成算法的名字

原有工程下的两个文件FlashPrg.c和FlashDev.c,FlashPrg.c主要是接口文件,需要自己实现,FlashDev.c则是Flash配置文件。

接下来添加芯片的相关文件,一些配置信息,时钟,外设等内容:

主要添加时钟,GPIO,QSPI几个文件

接下来对接FlashPrg文件中的几个函数:
在初始化中,需要配置我们时钟,qspi使用到的引脚等相关配置:
int Init (unsigned long adr, unsigned long clk, unsigned long fnc) {

  /* Add your Code */
  system_clock_config();
   
  qspi_gpio_config();
   
  /* switch to cmd port */
  qspi_xip_enable(QSPI1, FALSE);

  /* set sclk */
  qspi_clk_division_set(QSPI1, QSPI_CLK_DIV_4);

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

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

  /* enable auto ispc */
  qspi_auto_ispc_enable(QSPI1);

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

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

  /* enable the pin clock */
  crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE);
  crm_periph_clock_enable(CRM_GPIOC_PERIPH_CLOCK, TRUE);

  /* set default parameter */
  gpio_default_para_init(&gpio_init_struct);

  /* configure the io0 gpio */
  gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
  gpio_init_struct.gpio_out_type  = GPIO_OUTPUT_PUSH_PULL;
  gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
  gpio_init_struct.gpio_pins = GPIO_PINS_9;
  gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
  gpio_init(GPIOC, &gpio_init_struct);
  gpio_pin_mux_config(GPIOC, GPIO_PINS_SOURCE9, GPIO_MUX_11);

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

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

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

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

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

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

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

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

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

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


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

  /* Add your Code */
  system_clock_disable();
  qspi_xip_enable_config();
  return (0);                                  // Finished without Errors
}
EraseChip函数对接:
int EraseChip (void) {

  /* Add your Code */
    /* switch to cmd port */
  qspi_xip_enable(QSPI1, FALSE);
  /* qspi write enable */
  qspi_write_enable();

  /* qspi chip erase */
  qspi_chip_erase();

  /* qspi check busy */
  qspi_busy_check();

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

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

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

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

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

// Specify Size and Address of Sectors
   4096, 0x000000,         // Sector Size  8kB (8 Sectors)
   SECTOR_END
};
现在函数已经对接完了,编译生产算法文件,配置一下地方,模板工程中是已经配置好的

接下来编译工程,生产下载算法文件:

接下来将编译好的下载算法文件复制到Keil\ARM\Flash下,如下所示:


打开一个LED灯工程,在下载算法界面选择刚制作的外部flash算法:

点击确定后,点击下载即可。


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

26

主题

84

帖子

2

粉丝