你们好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给你们分享的是深刻i.MXRT1050系列ROM中串行NOR Flash启动初始化流程。html
从外部串行NOR Flash启动问题是i.MXRT系列开发最高频的话题,不管是开发调试XIP应用程序阶段仍是最终产品量产阶段都绕不开NOR Flash选型以及为它设计一个匹配的FDCB配置块。若是不了解FDCB是什么,先去看痞子衡以前的文章 《Bootable image格式与加载》。微信
实际开发过程当中,影响串行NOR Flash正常下载/启动的因素有不少,痞子衡已经写过三篇:《16MB以上使用不当因素》、《SFDP因素》、《QE bit因素》,列举了三个不一样因素,固然这都是出了问题,具体调试分析才定位出来的,显然还有不少未知因素等待陆续被发掘。性能
若是老是被动去解决问题,那问题是解不完的。不如咱们主动出击,摸清i.MXRT启动串行NOR Flash设备究竟是怎样的初始化流程,搞清这个流程,未来定位启动问题才能游刃有余,话很少说,开始今天的主题。flex
- 备注:本文主角是i.MXRT1050,但内容也基本适用i.MXRT10十、i.MXRT1020,仅细节微小差异。
咱们知道外部串行NOR Flash是接到i.MXRT的FlexSPI外设引脚上,有时串行NOR Flash启动也叫FlexSPI NOR启动。关于FlexSPI NOR启动流程,i.MXRT1050参考手册System Boot章节有以下流图,蓝框以外的流程属于常规i.MXRT启动XIP App流程,是个通用流程。蓝框以内才是具体FlexSPI初始化步骤,这个步骤归纳得比较精炼。ui
为了让你们对FlexSPI NOR设备启动初始化流程有个更具体的概念,痞子衡从新画了一张更详细的流程图,图中灰底框里描述得是FlexSPI初始化流程,痞子衡将其分解成了六步,咱们有必要深刻这六步初始化流程。.net
第一步是尝试复位Flash芯片,这步是可选的,在fuse_0x6e0[7]里配置,默认是不使能的。复位Flash目的是为了让Flash处于一个肯定的初始状态,方便i.MXRT BootROM去配置访问。为何要强调Flash的初始状态,由于不少时候i.MXRT未必是冷启动(上电启动),也有多是软复位启动(好比调用NVIC_SystemReset),这时候外部Flash已经被软复位前执行过的BootROM甚至用户App配置过,所以Flash的状态可能不是上电初始状态(通常来讲板级设计里Flash的RESET#引脚要么悬空,要么链接i.MXRT的POR#引脚),这可能会影响软复位后BootROM去再次配置启动这块不定态的Flash。翻译
fuse 0x6e0[7] - FLEXSPI_RESET_PIN_EN
正常的Flash都提供了RESET#引脚来实现跟上电复位同样的功能,对于普通8-pin的QSPI Flash,这个RESET#引脚每每是跟信号线IO3复用的(仅在QE bit没使能状况下有效),而对于16-pin的QSPI Flash或者HyperFlash,其RESET#引脚都是独立的。设计
BootROM就是借助了Flash的RESET#引脚来实现的复位操做,实现代码比较简单,i.MXRT1050 BootROM直接指定了GPIO1[9]当作复位信号线,板级设计里须要你将GPIO1[9]连到Flash的RESET#引脚,而后BootROM就是简单地拉低GPIO1[9]便可。RESET#信号都是低电平有效,BootROM直接拉低这个信号持续250us,这个低电平持续时间对于复位来讲是够够的,不少Flash数据手册里其实仅要求几us便可。3d
- 备注:对于BootROM的Flash复位功能来讲,主要适用有独立RESET#引脚的Flash。
#define RESET_PAD_IDX kIOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B0_09 #define RESET_PIN_MUX IOMUXC_SW_MUX_CTL_PAD_MUX_MODE(5) #define RESET_PIN_GPIO GPIO1 #define RESET_PIN_INDEX 9 if ((OCOTP->MISC_CONF1 & 0x80) >> 7) { // Set pinmux as GPIO IOMUXC->SW_MUX_CTL_PAD[RESET_PAD_IDX] = RESET_PIN_MUX; // Set GPIO to output mode RESET_PIN_GPIO->GDIR |= (1U<<RESET_PIN_INDEX); // High RESET_PIN_GPIO->DR_SET = (1U<<RESET_PIN_INDEX); sw_delay_us(250); // Low RESET_PIN_GPIO->DR_CLR = (1U<<RESET_PIN_INDEX); sw_delay_us(250); // High RESET_PIN_GPIO->DR_SET = (1U<<RESET_PIN_INDEX); sw_delay_us(500); }
第二步是准备一个初始的FDCB配置块(即flexspi_nor_config_t,大小为512字节),这个初始FDCB配置块将被用来作FlexSPI外设的第一次初始化,目的是为了可以保证FlexSPI初始化以后CPU可以使用AHB方式正常读取Flash(访问性能不要求最高,但求稳定访问)。这个初始FDCB并非一个彻底定死的配置块,部分值也是根据fuse来配置的,一共有三处fuse位置,其中最重要的是FLASH_TYPE:调试
fuse 0x440[20] - QSPI_2ND_BOOTPIN_ENABLE,决定是否启动第二组FlexSPI pinmux fuse 0x450[10:8] - FLASH_TYPE,决定当前链接的Flash类型 fuse 0x470[30:24] - DELAY_CELL_NUM,设置Flash读访问时序数据线有效时间
初始FDCB配置块中仅给memConfig设了值,这个memConfig才是用于配置FlexSPI外设自己。以下部分赋值是固定的FDCB设置,不受fuse影响,从这个固定配置你能够看到,BootROM假定了全部外接Flash都是128MB,且访问时钟(SCK)速度能支持30MHz,不要对这个假定感到焦虑,它只是用于FlexSPI第一次初始化,目的只求能正常访问Flash前4KB便可:
flexspi_nor_config_t config; memset(config, 0, sizeof(config)); // 公共的FDCB配置 config.memConfig.tag = FLEXSPI_CFG_BLK_TAG; config.memConfig.version = FLEXSPI_CFG_BLK_VERSION; config.memConfig.deviceType = kFlexSpiDeviceType_SerialNOR; config.memConfig.sflashA1Size = 128UL*1024*1024; config.memConfig.serialClkFreq = kFlexSpiSerialClk_30MHz; config.memConfig.dataHoldTime = 3; config.memConfig.dataSetupTime = 3; config.memConfig.timeoutInMs = 1000;
而后即是从fuse里获取flashType,根据具体flashType来对初始FDCB配置块作进一步动态赋值,这进一步赋值才用于区分不一样Flash种类(Pad数量、DQS信号属性、最重要的lookupTable等)。
// 从fuse里获取flash类型 uint32_t flashType; if ((OCOTP->CFG3 & 0x100000) >> 20) { flashType = 7; } else { flashType = (OCOTP->CFG4 & 0x700) >> 8; }
上图中最重要的FDCB赋值是config.memConfig.lookupTable,它是FlexSPI外设须要的核心配置,有了这个配置,CPU即可以直接从AHB总线读取Flash的内容,由于FlexSPI会自动解析AHB总线读请求而后翻译成具体FlexSPI读时序,底层读时序须要的命令、地址字节数、DUMMY周期都在lookupTable里。BootROM预存了以下6大类Flash的lookupTable:
// Dedicated 3Byte Address Read(0x03), 24bit address static const uint32_t s_dedicated3bRead[4] = { FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x03, RADDR_SDR, FLEXSPI_1PAD, 0x18), FLEXSPI_LUT_SEQ(READ_SDR, FLEXSPI_1PAD, 0x04, STOP, FLEXSPI_1PAD, 0), 0, 0 }; // Dedicated 4Byte Address Read(0x13), 32 bit address static const uint32_t s_dedicated4bRead[4] = { FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x13, RADDR_SDR, FLEXSPI_1PAD, 0x20), FLEXSPI_LUT_SEQ(READ_SDR, FLEXSPI_1PAD, 0x04, STOP, FLEXSPI_1PAD, 0), 0, 0 }; // HyperFlash Read static const uint32_t s_hyperflashRead[4] = { FLEXSPI_LUT_SEQ(CMD_DDR, FLEXSPI_8PAD, 0xA0, RADDR_DDR, FLEXSPI_8PAD, 0x18), FLEXSPI_LUT_SEQ(CADDR_DDR, FLEXSPI_8PAD, 0x10, DUMMY_RWDS_DDR, FLEXSPI_8PAD, 0x0c), FLEXSPI_LUT_SEQ(READ_DDR, FLEXSPI_8PAD, 0x04, STOP, FLEXSPI_8PAD, 0), 0 }; // MXIC Octal DDR read static const uint32_t s_mxicOctDdrRead[4] = { FLEXSPI_LUT_SEQ(CMD_DDR, FLEXSPI_8PAD, 0xEE, CMD_DDR, FLEXSPI_8PAD, 0x11), FLEXSPI_LUT_SEQ(RADDR_DDR, FLEXSPI_8PAD, 0x20, DUMMY_DDR, FLEXSPI_8PAD, 0xc), FLEXSPI_LUT_SEQ(READ_DDR, FLEXSPI_8PAD, 0x04, STOP, FLEXSPI_8PAD, 0), 0 }; // Micron Octal DDR read static const uint32_t s_micronOctDdrRead[4] = { FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_8PAD, 0xFD, RADDR_DDR, FLEXSPI_8PAD, 0x20), FLEXSPI_LUT_SEQ(DUMMY_DDR, FLEXSPI_8PAD, 0x8, READ_DDR, FLEXSPI_8PAD, 0x04), 0, 0 }; // Adesto Octal DDR read static const uint32_t s_adestoOctDdrRead[4] = { FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_8PAD, 0x0B, RADDR_DDR, FLEXSPI_8PAD, 0x20), FLEXSPI_LUT_SEQ(DUMMY_DDR, FLEXSPI_8PAD, 0x8, READ_DDR, FLEXSPI_8PAD, 0x04), 0, 0 };
第三步就是利用上述配置完成的初始FDCB块对FlexSPI外设进行第一次初始化,就是下面代码,这个流程跟官方SDK里的flexspi_nor_flash_init()大同小异,这里不予具体展开。若是在这里初始化就返回失败(这里通常不会失败,由于仅仅是FlexSPI外设自身初始化,并不涉及操做外部Flash芯片的动做),BootROM则直接退出FlexSPI NOR设备启动,转入SDP下载。
#define FLEXSPI_INSTANCE 0 uint32_t instance = FLEXSPI_INSTANCE; status_t status = flexspi_init(instance, (flexspi_mem_config_t *)(&config)); if (status != kStatus_Success) { return status; } flexspi_update_lut(instance, 0, &config.memConfig.lookupTable, 1);
上述第一次FlexSPI初始化通常都会成功的,但这并不表明fuse里的flashType等配置跟板子上Flash型号是匹配的,也就是说初始FDCB配置块此时尚未被充分验证其是否适用板载Flash型号。
FlexSPI第一次初始化结束后,为了保证后续能正常AHB访问,BootROM里作了一些善后工做,主要是两件事:
- 作一些访问前的延时:根绝fuse 0x450[3:2] - HOLD TIME来调用microseconds_delay()作延时,以使FlexSPI外设彻底准备好。
- 作一次无效AHB访问:相似这样的代码 volatile uint32_t dummy = *(uint32_t *)0x60000000;,无效AHB读可使Flash退出continuous read模式
善后工做结束以后,此时CPU应该能够经过AHB正常访问Flash了,这个阶段咱们只须要从Flash的偏移0地址处读取用户FDCB,验证用户FDCB是否存在,这里才是对前面初始FDCB配置块以及第一次FlexSPI外设初始化的真正考验。
验证用户FDCB是否存在就是简单读取FDCB的前四个字节(tag),验证这个tag是否合法。若是第一次验证tag不成功(有多是FlexSPI配置不正确,也有多是用户FDCB不存在),会尝试作一次三字节地址切换到四字节地址的LUT更新(仅适用QSPI Flash),而后作第二次tag读取验证,若是此时仍是验证失败(大几率是不存在用户FDCB了),BootROM则直接退出FlexSPI NOR设备启动,转入SDP下载。
#define FlexSPI_AMBA_BASE (0x60000000U) #define FLASH_BASE FlexSPI_AMBA_BASE // 使用三字节地址的LUT对Flash进行初次AHB访问 flexspi_clear_cache(FLEXSPI_INSTANCE); flexspi_nor_config_t *pConfig = (flexspi_nor_config_t *)FLASH_BASE; if (pConfig->memConfig.tag != FLEXSPI_CFG_BLK_TAG) { // 由于拿不到用户FDCB的tag,尝试切换使用四字节地址的LUT if (flashType == 0) { flexspi_update_lut(FLEXSPI_INSTANCE, 0, s_basic4bRead, 1); } flexspi_clear_cache(FLEXSPI_INSTANCE); pConfig = (flexspi_nor_config_t *)FLASH_BASE; } // 对Flash进行第二次AHB访问,再次确认可否拿到用户FDCB的tag if (pConfig->memConfig.tag != FLEXSPI_CFG_BLK_TAG) { return kStatus_Fail; }
上面代码里有flexspi_clear_cache()操做,这个其实就是利用FLEXSPI0->MCR0[SWRESET]作一个外设级别的软复位,另外代码里还涉及到一个四字节地址QSPI Flash的LUT表,即以下所示:
// Basic read with 32bit address static const uint32_t s_basic4bRead[4] = { FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x03, RADDR_SDR, FLEXSPI_1PAD, 0x20), FLEXSPI_LUT_SEQ(READ_SDR, FLEXSPI_1PAD, 0x04, STOP, FLEXSPI_1PAD, 0), 0, 0 };
到了这里,基本表明第一次FlexSPI初始化是正确且可用的,而且可以拿到有效的用户FDCB配置块。这时候就是利用用户FDCB配置块对FlexSPI外设作第二次初始化,初始化代码流程跟第一次初始化是如出一辙的。
这个第二次初始化是很是有必要的,由于它反映了用户的真实需求,用户FDCB配置块里会准确描述板载Flash的全面特性(访问速度,真实存储空间大小,特殊定制LUT等等),这些信息必须由用户来提供。
须要注意的是,第二次FlexSPI初始化返回成功并不表明用户FDCB配置块必定就是正确的,仍是那句话,这仅仅是对FlexSPI外设自身的初始化。后续常规App解析流程里才是对这个用户FDCB配置块的真正考验。
至此,深刻i.MXRT1050系列ROM中串行NOR Flash启动初始化流程痞子衡便介绍完毕了,掌声在哪里~~~
文章会同时发布到个人 博客园主页、CSDN主页、知乎主页、微信公众号 平台上。
微信搜索"痞子衡嵌入式"或者扫描下面二维码,就能够在手机上第一时间看了哦。