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算法:
点击确定后,点击下载即可。
|