小猫爪:i.MX RT1050学习笔记15-FlexSPI-FLASH使用3-KEIL FLASH算法中的使用
1 前言
在前面介绍了RT1050启动时会读取镜像的FCB数据对FlexSPI进行初始化,在下载代码进FLASH时,我们需要使用FLASH算法,那我们怎样在KEIL FLASH算法中对FlexSPI进行初始化呢,或者说怎样针对自己的FLASH设计自己的算法呢?
2 FLASH算法解析
首先去了解一下官方的源码做了什么,打开NXP RT1050的IAR FLASH算法源码(路径:C:\Keil_v5\ARM\Flash\MiMXRT105x_ATXP032)。
2.1 初始化Init
首先我们看看初始化函数,FLASH算法的FlashInit初始化函数源码如下:
int Init (unsigned long adr, unsigned long clk, unsigned long fnc) {
// Initialize pins
BOARD_InitPins();
// Initialize clock
BOARD_BootClockRUN();
// Initialize FlexSPI controller for EcoXiP
EcoXiP_init();
//Unprotect all sectors in anticipation of erase/program operations
EcoXiP_unprotect_all();
//Switch to Octal-DDR mode
EcoXiP_enter_octal_mode(true);
return 0;
}
一览无余,首先对相关引脚和时钟的初始化,这些都跟FlexSPI无关,关于FlexSPI相关的初始化全部都在EcoXiP_init中,废话不多说上源码:
// Initialize FlexSPI controller for EcoXiP
void EcoXiP_init(void)
{
uint32_t i;
// Set flexspi source clock
const clock_usb_pll_config_t g_ccmConfigUsbPll = {.loopDivider = 0U};
// Reset flash
#if(FLASH_RESET_METHOD == METHOD_PIN_RESET)
EcoXiP_reset();
#endif
#if(FLASH_RESET_METHOD == METHOD_JEDEC_RESET)
EcoXiP_jreset();
#endif
// Give it some time to settle down (using EcoXiP reset recovery time)
for(i = 0; i < ECOXIP_RESET_RECOVEY_US; i++)
delay();
// Assign pins to FlexSPI and configure them
EcoXiP_config_flexspi_pins();
// Point to FlexSPI base
exip_flexspi = FLEXSPI;
// Set up source clock
CLOCK_InitUsb1Pll(&g_ccmConfigUsbPll);
CLOCK_InitUsb1Pfd(kCLOCK_Pfd0, 480000*18/(FLEXSPI_SOURCE_CLOCK/1000)); /* Set PLL3 PFD0 clock */
CLOCK_SetMux(kCLOCK_FlexspiMux, 0x3); /* Choose PLL3 PFD0 clock as FlexSPI source clock. */
// Set up SCLK
flexspi_root_clock = SCLK_FREQ;
deviceconfig.flexspiRootClk = flexspi_root_clock;
CLOCK_SetDiv(kCLOCK_FlexspiDiv, (FLEXSPI_SOURCE_CLOCK/flexspi_root_clock) - 1); /* Divide source clock the source/root ratio. */
/* Get FLEXSPI default settings and configure the FlexSPI. */
FLEXSPI_GetDefaultConfig(&flexspi_config);
// Enable AHB prefetching
flexspi_config.ahbConfig.enableAHBPrefetch = true;
// To achieve high speeds - always use DQS
flexspi_config.rxSampleClock = kFLEXSPI_ReadSampleClkExternalInputFromDqsPad;
// Need to set the combination-enable option. This options combines 8 data lines
// from FlexSPI channel A with 4 data lines from FlexSPI channel B to form an
// 8-line bus for octal. On this SoC this is th eonly way to enable octal.
flexspi_config.enableCombination = true;
/* Init FlexSPI. */
FLEXSPI_Init(exip_flexspi, &flexspi_config);
/* Configure flash settings according to serial flash feature. */
FLEXSPI_SetFlashConfig_Adesto(exip_flexspi, &deviceconfig, kFLEXSPI_PortA1);
/* Update LUT table. */
FLEXSPI_UpdateLUT_Adesto(exip_flexspi, 0, spi_lut, LUT_SIZE);
/* Do software reset. */
FLEXSPI_SoftwareReset(exip_flexspi);
// Set current operation mode to SPI
op_mode = SPI_MODE;
// Initialize fields in the structure we will send to the FlexSPI driver.
// These fields will always have the same values in our case.
flashxfer.port = kFLEXSPI_PortA1;
flashxfer.SeqNumber = 1;
}
可以看到其初始化过程非常的露骨,还是那么一览无余,具体过程如下:
①初始化时钟
②初始化引脚
③初始化FlexSPI外设相关的寄存器
④初始化FLASH特性相关的寄存器
⑤初始化LUT表寄存器
⑤复位FlexSPI
非常简单,再看看初始化FlexSPI外设相关的寄存器的结构体:
typedef struct _flexspi_config
{
flexspi_read_sample_clock_t rxSampleClock; /*!< Sample Clock source selection for Flash Reading. */
bool enableSckFreeRunning; /*!< Enable/disable SCK output free-running. */
bool enableCombination; /*!< Enable/disable combining PORT A and B Data Pins
(SIOA[3:0] and SIOB[3:0]) to support Flash Octal mode. */
bool enableDoze; /*!< Enable/disable doze mode support. */
bool enableHalfSpeedAccess; /*!< Enable/disable divide by 2 of the clock for half
speed commands. */
bool enableSckBDiffOpt; /*!< Enable/disable SCKB pad use as SCKA differential clock
output, when enable, Port B flash access is not available. */
bool enableSameConfigForAll; /*!< Enable/disable same configuration for all connected devices
when enabled, same configuration in FLASHA1CRx is applied to all. */
uint16_t seqTimeoutCycle; /*!< Timeout wait cycle for command sequence execution,
timeout after ahbGrantTimeoutCyle*1024 serial root clock cycles. */
uint8_t ipGrantTimeoutCycle; /*!< Timeout wait cycle for IP command grant, timeout after
ipGrantTimeoutCycle*1024 AHB clock cycles. */
uint8_t txWatermark; /*!< FLEXSPI IP transmit watermark value. */
uint8_t rxWatermark; /*!< FLEXSPI receive watermark value. */
struct
{
bool enableAHBWriteIpTxFifo; /*!< Enable AHB bus write access to IP TX FIFO. */
bool enableAHBWriteIpRxFifo; /*!< Enable AHB bus write access to IP RX FIFO. */
uint8_t ahbGrantTimeoutCycle; /*!< Timeout wait cycle for AHB command grant,
timeout after ahbGrantTimeoutCyle*1024 AHB clock cycles. */
uint16_t ahbBusTimeoutCycle; /*!< Timeout wait cycle for AHB read/write access,
timeout after ahbBusTimeoutCycle*1024 AHB clock cycles. */
uint8_t resumeWaitCycle; /*!< Wait cycle for idle state before suspended command sequence
resume, timeout after ahbBusTimeoutCycle AHB clock cycles. */
flexspi_ahbBuffer_config_t buffer[FSL_FEATURE_FLEXSPI_AHB_BUFFER_COUNT]; /*!< AHB buffer size. */
bool enableClearAHBBufferOpt; /*!< Enable/disable automatically clean AHB RX Buffer and TX Buffer
when FLEXSPI returns STOP mode ACK. */
bool enableAHBPrefetch; /*!< Enable/disable AHB read prefetch feature, when enabled, FLEXSPI
will fetch more data than current AHB burst. */
bool enableAHBBufferable; /*!< Enable/disable AHB bufferable write access support, when enabled,
FLEXSPI return before waiting for command excution finished. */
bool enableAHBCachable; /*!< Enable AHB bus cachable read access support. */
} ahbConfig;
} flexspi_config_t;
再看看初始化FLASH特性相关的寄存器的结构体:
typedef struct _flexspi_device_config
{
uint32_t flexspiRootClk; /*!< FLEXSPI serial root clock. */
bool isSck2Enabled; /*!< FLEXSPI use SCK2. */
uint32_t flashSize; /*!< Flash size in KByte. */
flexspi_cs_interval_cycle_unit_t CSIntervalUnit; /*!< CS interval unit, 1 or 256 cycle. */
uint16_t CSInterval; /*!< CS line assert interval, mutiply CS interval unit to
get the CS line assert interval cycles. */
uint8_t CSHoldTime; /*!< CS line hold time. */
uint8_t CSSetupTime; /*!< CS line setup time. */
uint8_t dataValidTime; /*!< Data valid time for external device. */
uint8_t columnspace; /*!< Column space size. */
bool enableWordAddress; /*!< If enable word address.*/
uint8_t AWRSeqIndex; /*!< Sequence ID for AHB write command. */
uint8_t AWRSeqNumber; /*!< Sequence number for AHB write command. */
uint8_t ARDSeqIndex; /*!< Sequence ID for AHB read command. */
uint8_t ARDSeqNumber; /*!< Sequence number for AHB read command. */
flexspi_ahb_write_wait_unit_t AHBWriteWaitUnit; /*!< AHB write wait unit. */
uint16_t AHBWriteWaitInterval; /*!< AHB write wait interval, mutiply AHB write interval
unit to get the AHB write wait cycles. */
bool enableWriteMask; /*!< Enable/Disable FLEXSPI drive DQS pin as write mask
when writing to external device. */
} flexspi_device_config_t;
细心的小伙伴们可能发现了,这个结构体就是官网SDK包库的,还有这个结构体与FCB信息的结构体不一样,更加细心的小伙伴又发现了这个结构体与那个结构体大多数信息都是一样的,因为FCB是给bootROM用的,所以除了包括FlexSPI初始化的信息之外还包括了其他信息,感兴趣的小伙伴可以对比一下,找找不同。在这里我就不对结构体进行详细解释了(野火教程里对这两个结构体作了一个非常详细的解释)。
2.2 写操作
同样的,我们再来分析一下在KEIL中怎样去实现FLASH的写操作。直接找到写操作:
int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf) {
int stat;
stat = EcoXiP_page_program(adr - FLASH_BASE_ADDR, sz, buf);
return stat;;
}
在找到EcoXiP_page_program:
int32_t EcoXiP_page_program(uint32_t address, uint32_t size, uint8_t * data)
{
uint8_t is_busy;
int32_t stat;
status_t flexspi_status;
stat = EcoXiP_write_enable();
if(stat != 0)
return 1;
flashxfer.cmdType = kFLEXSPI_Write;
flashxfer.seqIndex = LUT_SEQ_INDEX_PAGE_PROG;
flashxfer.deviceAddress = address;
flashxfer.data = (uint32_t *) data;
flashxfer.dataSize = size;
flexspi_status = FLEXSPI_TransferBlocking(exip_flexspi, &flashxfer);
if (flexspi_status != kStatus_Success)
return 1;
do
{
stat = EcoXiP_read_busy_status(&is_busy);
if(stat != 0)
return 1;
}
while(is_busy);
return 0;
}
一览无余啊,三个步骤,一个写操作总共调用了三个函数,先发送写使能指令(EcoXiP_write_enable),再开始写数据(FLEXSPI_TransferBlocking),最后发送读指令读取状态确认擦写完成(EcoXiP_read_busy_status),在这里我们就单独拿出读状态EcoXiP_read_busy_status来做个介绍。直接上代码:
// Read EcoXiP status register 1
uint32_t EcoXiP_read_busy_status(uint8_t *busy_status)
{
uint32_t status_reg_1;
status_t flexspi_status;
flashxfer.cmdType = kFLEXSPI_Read;
flashxfer.seqIndex = LUT_SEQ_INDEX_READ_STATUS_REG_BYTE1;
flashxfer.deviceAddress = 0;
flashxfer.data = &status_reg_1;
flashxfer.dataSize = 1;
flexspi_status = FLEXSPI_TransferBlocking(exip_flexspi, &flashxfer);
if (flexspi_status != kStatus_Success)
return 1;
*busy_status = (status_reg_1 & BUSY_BIT);
return 0;
}
非常的简单,使用了SDK库里的发送数据结构体发起了一次IP command流程操作(具体流程可以参考我之前的文章《小猫爪:i.MX RT1050学习笔记12-FlexSPI简介》)。可以看出它调用了LUT表中序列LUT_SEQ_INDEX_READ_STATUS_REG_BYTE1的指令,找到这个序列:
// Read Status (byte 1)
[4] = (QINST_CMD << 10) | (PAD_1 << 8) | (EXIP_CMD_READ_STATUS_REG_BYTE1) |
(QINST_READ << 26) | (PAD_1 << 24) | (1 << 16),
整理一下就是:
指令序号 | 指令名称 | PAD数量 | OPCODE | 描述 |
---|---|---|---|---|
1 | QINST_CMD | PAD_1 | 0x05 | SDR模式1线模式向FLASH发送0x05(查芯片手册0x05为读状态寄存器第一位) |
2 | QINST_READ | PAD_1 | 1 | SDR模式1线模式读取1个字节 |
说到这里,细心的小伙伴又问了,不是不满8个指令的序列需要在后面加STOP指令吗?因为STOP指令对应的LUT表值就是0,而数组初始值为0,所以每条指令后面默认都会有STOP指令,不加STOP也是可以的。
说到这里,大家可能对LUT表的使用已经非常熟悉了吧(注意:对LUT表其他指令的解析在另外两篇文章中也有)。
对于KEIL的FLASH算法,我们不要忘记了还要对FlashDevice的初始化,直接上源码:
struct FlashDevice const FlashDevice = {
FLASH_DRV_VERS, // Driver Version, do not modify!
"MIMXRT105x EcoXiP Flash", // Device Name
EXTSPI, // Device Type
FLASH_BASE_ADDR, // Device Start Address
FLASH_SIZE, // Device Size in Bytes
FLASH_PAGE_SIZE, // Programming Page Size
0, // Reserved, must be 0
0xFF, // Initial Content of Erased Memory
100, // Program Page Timeout 100 mSec
3000, // Erase Sector Timeout 3000 mSec
// Specify Size and Address of Sectors
FLASH_SECTORE_SIZE, 0x00000000, // Sector size and start offset of this sector group
SECTOR_END
};
FlashDevice的初始化对于KEIL来说是非常重要的,需要根据实际FLASH情况对其进行修改。
根据以上的操作,我们就能针对FLASH编写自己的KEIL FLASH算法啦。FLASH算法编写完了之后,还有两处的设置不要随意篡改:
第一个就是FLASH空间大小,第二个则是FLASH算法下载的RAM位置及大小,如下图: