小猫爪:i.MX RT1050学习笔记13-FlexSPI-FLASH使用1-启动时的初始化
1 前言
前面对FlexSPI做了一个简单的介绍,接下来就是FlexSPI具体得使用了。因为对于RT1050来说没有内嵌的FLASH,而且只有一个FlexSPI外设(它的大哥们都会有两个FlexSPI外设),所以在一般的应用中都会使用FlexSPI接口外接支持XIP的外部FLASH存储代码(虽然RT1050支持多种启动设备,但是一般用的最多的还是FlexSPI nor),所以接下来只会对FlexSPI和nor Flash这对最佳组合做介绍。
2 硬件连接
对于RTFlexSPI与Flash的链接方式大家可以参考大神文章《痞子衡嵌入式:恩智浦i.MX RT1xxx系列MCU启动那些事(11.B)- FlexSPI NOR连接方式大全(RT1015/1020/1050)》,这篇把一些硬件相关的问题解释的非常详细,基本上可以回答所有的疑问。
3 启动时FlexSPI的初始化
对于FlexSPI启动来说,bootROM会先读取镜像的FCB数据对FlexSPI外设进行初始化,而且如果使用FlexSPI接口进行XIP执行代码的话,是不能在用户代码中二次初始化FlexSPI的,因为这会导致CPU取指错误,导致硬件错误,所以对于FlexSPI的初始化都会放在RT镜像文件中的FCB中(对于FCB的具体细节大家可以参考我前面讲镜像分解的那一章。现在我们就可以把那个结构体拿出来说一说了,如下:
typedef struct _flexspi_nor_config
{
flexspi_mem_config_t memConfig; //!< Common memory configuration info via FlexSPI
uint32_t pageSize; //!< Page size of Serial NOR
uint32_t sectorSize; //!< Sector size of Serial NOR
uint8_t ipcmdSerialClkFreq; //!< Clock frequency for IP command
uint8_t isUniformBlockSize; //!< Sector/Block size is the same
uint8_t reserved0[2]; //!< Reserved for future use
uint8_t serialNorType; //!< Serial NOR Flash type: 0/1/2/3
uint8_t needExitNoCmdMode; //!< Need to exit NoCmd mode before other IP command
uint8_t halfClkForNonReadCmd; //!< Half the Serial Clock for non-read command: true/false
uint8_t needRestoreNoCmdMode; //!< Need to Restore NoCmd mode after IP commmand execution
uint32_t blockSize; //!< Block size
uint32_t reserve2[11]; //!< Reserved for future use
} flexspi_nor_config_t;
对于这个结构体主要分为两部分,第一部分是针对FlexSPI外设的初始化,第二部分则是针对目标FLASH的特性的FLASH初始化部分。
在上面的结构体中,其中第一项 flexspi_mem_config_t memConfig则就是描述了FlexSPI初始化配置。该结构体信息如下:
typedef struct _FlexSPIConfig
{
uint32_t tag; //!< [0x000-0x003] Tag, fixed value 0x42464346UL
uint32_t version; //!< [0x004-0x007] Version,[31:24] -'V', [23:16] - Major, [15:8] - Minor, [7:0] - bugfix
uint32_t reserved0; //!< [0x008-0x00b] Reserved for future use
uint8_t readSampleClkSrc; //!< [0x00c-0x00c] Read Sample Clock Source, valid value: 0/1/3
uint8_t csHoldTime; //!< [0x00d-0x00d] CS hold time, default value: 3
uint8_t csSetupTime; //!< [0x00e-0x00e] CS setup time, default value: 3
uint8_t columnAddressWidth; //!< [0x00f-0x00f] Column Address with, for HyperBus protocol, it is fixed to 3, For
//! Serial NAND, need to refer to datasheet
uint8_t deviceModeCfgEnable; //!< [0x010-0x010] Device Mode Configure enable flag, 1 - Enable, 0 - Disable
uint8_t deviceModeType; //!< [0x011-0x011] Specify the configuration command type:Quad Enable, DPI/QPI/OPI switch,
//! Generic configuration, etc.
uint16_t waitTimeCfgCommands; //!< [0x012-0x013] Wait time for all configuration commands, unit: 100us, Used for
//! DPI/QPI/OPI switch or reset command
flexspi_lut_seq_t deviceModeSeq; //!< [0x014-0x017] Device mode sequence info, [7:0] - LUT sequence id, [15:8] - LUt
//! sequence number, [31:16] Reserved
uint32_t deviceModeArg; //!< [0x018-0x01b] Argument/Parameter for device configuration
uint8_t configCmdEnable; //!< [0x01c-0x01c] Configure command Enable Flag, 1 - Enable, 0 - Disable
uint8_t configModeType[3]; //!< [0x01d-0x01f] Configure Mode Type, similar as deviceModeTpe
flexspi_lut_seq_t
configCmdSeqs[3]; //!< [0x020-0x02b] Sequence info for Device Configuration command, similar as deviceModeSeq
uint32_t reserved1; //!< [0x02c-0x02f] Reserved for future use
uint32_t configCmdArgs[3]; //!< [0x030-0x03b] Arguments/Parameters for device Configuration commands
uint32_t reserved2; //!< [0x03c-0x03f] Reserved for future use
uint32_t controllerMiscOption; //!< [0x040-0x043] Controller Misc Options, see Misc feature bit definitions for more
//! details
uint8_t deviceType; //!< [0x044-0x044] Device Type: See Flash Type Definition for more details
uint8_t sflashPadType; //!< [0x045-0x045] Serial Flash Pad Type: 1 - Single, 2 - Dual, 4 - Quad, 8 - Octal
uint8_t serialClkFreq; //!< [0x046-0x046] Serial Flash Frequencey, device specific definitions, See System Boot
//! Chapter for more details
uint8_t lutCustomSeqEnable; //!< [0x047-0x047] LUT customization Enable, it is required if the program/erase cannot
//! be done using 1 LUT sequence, currently, only applicable to HyperFLASH
uint32_t reserved3[2]; //!< [0x048-0x04f] Reserved for future use
uint32_t sflashA1Size; //!< [0x050-0x053] Size of Flash connected to A1
uint32_t sflashA2Size; //!< [0x054-0x057] Size of Flash connected to A2
uint32_t sflashB1Size; //!< [0x058-0x05b] Size of Flash connected to B1
uint32_t sflashB2Size; //!< [0x05c-0x05f] Size of Flash connected to B2
uint32_t csPadSettingOverride; //!< [0x060-0x063] CS pad setting override value
uint32_t sclkPadSettingOverride; //!< [0x064-0x067] SCK pad setting override value
uint32_t dataPadSettingOverride; //!< [0x068-0x06b] data pad setting override value
uint32_t dqsPadSettingOverride; //!< [0x06c-0x06f] DQS pad setting override value
uint32_t timeoutInMs; //!< [0x070-0x073] Timeout threshold for read status command
uint32_t commandInterval; //!< [0x074-0x077] CS deselect interval between two commands
uint16_t dataValidTime[2]; //!< [0x078-0x07b] CLK edge to data valid time for PORT A and PORT B, in terms of 0.1ns
uint16_t busyOffset; //!< [0x07c-0x07d] Busy offset, valid value: 0-31
uint16_t busyBitPolarity; //!< [0x07e-0x07f] Busy flag polarity, 0 - busy flag is 1 when flash device is busy, 1 -
//! busy flag is 0 when flash device is busy
uint32_t lookupTable[64]; //!< [0x080-0x17f] Lookup table holds Flash command sequences
flexspi_lut_seq_t lutCustomSeq[12]; //!< [0x180-0x1af] Customizable LUT Sequences
uint32_t reserved4[4]; //!< [0x1b0-0x1bf] Reserved for future use
} flexspi_mem_config_t;
在设计该结构体时,需要注意下面几个地方:
①RT1050的bootROM启动会默认从flashA1启动,也就是A_SS0片选控制的Flash。
②readSampleClkSrc这个参数决定了RX时钟源,它很大程度得决定了读取数据的速度,比如在HyperFlash应用中,会使用DQS信号来当做RX时钟源。
③对于LUT表初始化来说,对于bootROM来说最重要的就是读指令,所以我们需要针对FLASH的芯片手册初始化一个读数据的指令序列,并且将其放在第一条序列所在的位置,也就是上述结构体中的lookupTable的前四个word,对于寄存器就是LUT0~LUT3(因为FLSHA1CR2默认为0,该寄存器决定了AHB读写时使用LUT表索引都是0,且bootRAM并没有改变。
④因为很多产商的FLASH都有点不同,所以大产商后来针对串行FLASH制定一套标准,叫做JESD216 SFDP,用来规范存放串行Flash相关信息的功能参数表。其实现方式是在串行Flash中内置一个可供查询的统一规范的功能参数表,不占用串行Flash正常的容量大小。当然对于一些老的串行FLASH型号也是没有这个标准的,对于有SFDP标准的串行FLASH来说,只需要初始化好简单的几个参数就能完成配置(具体需要初始化哪些参数可参考NXP的官方例程),因为bootROM会读取SFDP去自动完善结构体数据,但是对于没有SFDP标准的FLASH来说,就要完整的将整个FlexSPI配置结构体完善配置好。(相关具体介绍可参考文章:为什么SFDP是影响串行Flash正常下载的重要因素?)
⑤对于FLASH的Fast Read Quad I/O模式使能位的位置和默认配置,不同型号的FLASH也是不尽相同的,所以我们在设计LUT时,需要重点考虑以下怎样使能Fast Read Quad I/O模式。(相关具体介绍可参考文章:为什么QE bit是影响串行Flash正常启动的重要因素?)
⑥csHoldTime、csSetupTime和dataValidTime这三个参数分别表示CS 信号线的保持时间、建立时间以及数据有效时间,这部分内容需要根据具体FLASH 的时序参数表来配置。(对于具有SFDC的FLASH来说,这些参数就可有可无了。)
⑦注意flexspi_lut_seq_t这个结构体,有两个这个结构体性质的成员deviceModeSeq和configCmdSeqs,查看一下该结构体的定义:
typedef struct _lut_sequence
{
uint8_t seqNum; //!< Sequence Number, valid number: 1-16
uint8_t seqId; //!< Sequence Index, valid number: 0-15
uint16_t reserved;
} flexspi_lut_seq_t;
可以看到其有序列索引和序列数量两个信息,BootROM则可以根据这个结构体信息调用LUT表指令将相关的数据写入FLASH(盲猜configCmdSeqs指令对应的数据保持在configCmdArgs中,deviceModeSeq指令对应的数据保存在deviceModeArg中),通过这个则可以对FLASH的相关工作模式以及相关配置在APP执行前进行初始化,比如使能写,使能四线模式等等,如果需要使用configCmdSeqs,则需要修改参数configCmdEnable对该功能进行使能。
(注意:在自定义自己的LUT序列时,需要知道0~5序列已经被BootROM的API函数占用了,所以只能使用其他的序列号,等到启动完成之后,程序正常运行后,在应用程序中,除了读序列(也就是0号序列)不要修改外其他则可以修改,但是如果你在应用程序中调用了ROM APIs则也是不能去修改的,所以建议不到万不得已时,在应用程序中,不要动LUT表0 ~ 5几个序列。)
接下来让我们看看其指令核心中的LUT表:
不妨拿出NXP官方RT1050的demo板初始化结构体中的LUT:
const flexspi_nor_config_t hyperflash_config = {
..........
..........
.lookupTable =
{
// Read LUTs
FLEXSPI_LUT_SEQ(CMD_DDR, FLEXSPI_8PAD, 0xA0, RADDR_DDR, FLEXSPI_8PAD, 0x18),
FLEXSPI_LUT_SEQ(CADDR_DDR, FLEXSPI_8PAD, 0x10, DUMMY_DDR, FLEXSPI_8PAD, 0x06),
FLEXSPI_LUT_SEQ(READ_DDR, FLEXSPI_8PAD, 0x04, STOP, FLEXSPI_1PAD, 0x0),
},
............
............
};
可以看到它使用了FLEXSPI_LUT_SEQ函数来初始指令,该函数的作用也就是将两条指令的三个部分按照寄存器定义的格式重组,最后生成一个word,最后填入LUT寄存器。接下来看看它的指令有啥:
序号 | 指令名称 | PAD数量 | OPERAND |
---|---|---|---|
1 | CMD_DDR | FLEXSPI_8PAD | 0xA0 |
2 | RADDR_DDR | FLEXSPI_8PAD | 0x18 |
3 | CADDR_DDR | FLEXSPI_8PAD | 0x10 |
4 | DUMMY_DDR | FLEXSPI_8PAD | 0x06 |
5 | READ_DDR | FLEXSPI_8PAD | 0x04 |
6 | STOP | FLEXSPI_1PAD | 0x0 |
再结合LUT指令表就可以得出每条指令在干什么?具体描述如下:
指令序号 | 描述 |
---|---|
1 | DDR模式,8线模式发送FLASH指令0xA0至FLASH(查看芯片手册A0为读指令) |
2 | DDR模式,8线模式发送Row地址至FLASH,其中为地址为24位 |
3 | DDR模式,8线模式发送Col地址至FLASH,其中为地址为16位 |
4 | DDR模式,FlexSPI释放数据总线,要求总线等待6个SCK时钟的时间(该参数应满足FLASH读写时序要求) |
5 | DDR模式,准备读取FLASH返回数据,共4个字节 |
6 | 停止,释放CS信号 |
只有针对目标FLASH的特性完善好上面的结构体,RT才能正常得从FlexSPI nor启动,并且进行XIP执行。所以上述结构体的初始化非常重要。
(注意:①对于PAD数量,对于大部分FLASH来说,发送指令时只能单线模式发送,只有读写数据时才能使用其他多线模式,而且对于有些FLASH,使用多线传输模式前还需要对其发送相关命令开启此功能。因为NXP板子上默认采用的是8线的HyperFLASH,它是默认支持8线接收指令的。
②对于大部分FLASH来说,是不存在Col地址的,所以在读写数据时只需要发送Row地址即可。
③对于24位地址来说,最大支持16MB,所以想访问超过16MB以外的地址,则需要发送FLASH对应的32位寻址指令。
④如果用户在XIP执行下,在代码中又使用IP操作FLASH,因为代码执行过程中,CPU取指是AHB操作,两种手段会冲突导致CPU取指失败发生错误。所以要想实现在代码中进行FLASH操作,可以通过以下四个方法去实现:
- 方法1:在MPU里设置Code Flash对应的映射地址区域,使能Cache,并且保证应用程序代码里调用FlexSPI驱动去擦写 Flash的关键部分(触发IP CMD执行)始终缓存在Cache里。
- 方法2:将应用程序代码里调用FlexSPI驱动去擦写Data Flash的关键部分搬运到RAM空间去运行。
- 方法3:在写Data Flash的时候借助DMA来搬运数据,不让CPU干预,具体可以参考官方《AN12564 Implement RWW on i.MX RT Series》。
- 方法4:应用程序代码里的FlexSPI驱动直接使用BootROM API(代码是在ROM空间运行的)。
【后续会对这几种方法作详细的介绍】
)
以上是在RT启动过程中初始化FlexSPI,还有一个大家可能忽略了,就是下载程序时候,则会使用到Flashloder,在IDE中叫做Flash算法,后面待续。。。