NXP i.MX RT1020 - BootLoader 中燒寫 APP 到 Flash 指定位置的方法

1.   前言

       本篇博文將介紹如何實現在 BootLoader 中燒寫代碼數據到 Flash 的指定位置,並跳轉到代碼位置運行,正常實現升級的方式是通過各種通信接口如:UART、CAN、USB、Ethernet...,通信接口把需要升級的代碼傳送給 BootLoader,BootLoader 寫代碼數據到 Flash 並跳轉到代碼位置執行。

       博文將不使用通信接口獲取數據,而是把需要燒錄到 Flash 的數據放到 Flash 當中,在 BootLoader 中把 Flash 數據讀取到 RAM 當中,通過 FlexSPI 把 RAM 中的數據寫到 Flash 的指定位置。


2.   BootLoader與 APP 的配置

2.1 存儲方案

  1. Flash 起始地址為 0x60000000 大小為 0x00010000 即 64KB,其中放置用於實現 SecondBootLoader 的代碼和數據。
  2. APP1 放在 SecondBootLoader 後面,大小為 128KB。
  3. APP2 放在 APP1 後面,大小為 128KB。
  4. Database 放在 APP2 後面,大小 192KB,存放用戶數據。

2.1 程序地址的分散加載修改

  1. 在 SecondBootLoader 的 scf 文件中修改:
#define m_flash_config_start           0x60000000
#define m_flash_config_size 0x00001000
#define m_ivt_start 0x60001000
#define m_ivt_size 0x00001000
#define m_interrupts_start 0x60002000
#define m_interrupts_size 0x00000400
#define m_text_start 0x60002400
#define m_text_size 0x0000DBFF
  1. 在 APP1 的 scf 文件中修改:
#define m_flash_config_start           0x60010000
#define m_flash_config_size 0x00001000
#define m_ivt_start 0x60011000
#define m_ivt_size 0x00001000
#define m_interrupts_start 0x60012000
#define m_interrupts_size 0x00000400
#define m_text_start 0x60012400
#define m_text_size 0x0001DBFF
  1. 在 APP2 的 scf 文件中修改:
#define m_flash_config_start           0x60030000
#define m_flash_config_size 0x00001000
#define m_ivt_start 0x60031000
#define m_ivt_size 0x00001000
#define m_interrupts_start 0x60032000
#define m_interrupts_size 0x00000400
#define m_text_start 0x60032400
#define m_text_size 0x0001DBFF


3.   把數組數據放到指定的 Flash 自定義 section 中

       數組定義與聲明:

  1. 對把數組需要放到的 section 進行 __attribute__ 修飾;
#define LOCATION_EXTFLASH_ATTRIBUTE __attribute__ ((section ("Myapp"))) __attribute__ ((aligned(4)))

const unsigned char _aclwip_ping_bm[0x12880] LOCATION_EXTFLASH_ATTRIBUTE = {
0, 0, 0,
};
  1. 這裡把數據放到了 APP2 的位置,實際的升級不會把升級數據預先放到 Flash 中,這裡只是對程序燒寫做測試,修改 scf 文件:
  • 自定義 block,定義 block 的起始和結束地址。
  • 加載 block 段,在其中聲明數據放到的 section 段 *.o(Myapp)。
;Define new block m_text_data, place it at the beginning of the scf file
#define m_text_data_start 0x60030000
#define m_text_data_size 0x00013000


;Place at the end of the scf file, as a new block of m_text_data
LR_m_data_text m_text_data_start m_text_data_size{
ER_m_data_text m_text_data_start FIXED m_text_data_size { ; load address = execution address
*.o(Myapp)
}
}


4.   加入 FlexSPI 操作函數

        添加 flexspi 操作函數文件到工程中。

 

圖 1. 加入 FlexSPI 驅動文件

        在主函數所在文件中添加:

        FlexSPI 操作用到的頭文件:

#include "fsl_flexspi.h"
#include "app.h"
#include "fsl_cache.h"

        FlexSPI 操作用到的函數和數據:

        對於 W25Q64JVSIQ 這個 8M 的 Flash,要根據晶片的數據手冊修改參數

  1. 修改 flexspi_nor_enable_quad_mode 函數

        通過修改 writeValue 為 0x200,可以使能把寄存器中的QE(Quad Enable)位置為1,由於 0x200 為 2Byte數據,還需要設置 flashXfer.dataSize 為 2。

圖 2. 修改 QE 位

        需要修改的內容如下:

  1. uint32_t writeValue = 0x0200; //FLASH_QUAD_ENABLE;
  2. flashXfer.dataSize = 2; //1

        flexspi_nor_enable_quad_mode 修改後如下:

status_t flexspi_nor_enable_quad_mode(FLEXSPI_Type *base)
{
flexspi_transfer_t flashXfer;
status_t status;
uint32_t writeValue = 0x0200; //FLASH_QUAD_ENABLE;

#if defined(CACHE_MAINTAIN) && CACHE_MAINTAIN
flexspi_cache_status_t cacheStatus;
flexspi_nor_disable_cache(&cacheStatus);
#endif

/* Write enable */
status = flexspi_nor_write_enable(base, 0);

if (status != kStatus_Success)
{
return status;
}

/* Enable quad mode. */
flashXfer.deviceAddress = 0;
flashXfer.port = FLASH_PORT;
flashXfer.cmdType = kFLEXSPI_Write;
flashXfer.SeqNumber = 1;
flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG;
flashXfer.data = &writeValue;
flashXfer.dataSize = 2; //1

status = FLEXSPI_TransferBlocking(base, &flashXfer);

if (status != kStatus_Success)
{
return status;
}

status = flexspi_nor_wait_bus_busy(base);

/* Do software reset. */
FLEXSPI_SoftwareReset(base);

#if defined(CACHE_MAINTAIN) && CACHE_MAINTAIN
flexspi_nor_enable_cache(cacheStatus);
#endif

return status;
}
  1. 對於擦除操作,最小單位為一個 Sector (4KB),修改 LUT 0xD7 為 0x20(4KB),或 0x52 (32KB) 或 0xD8(64KB),參考 https://community.nxp.com/t5/i-MX-RT/RT1050-QSPI-flash-change-to-Winbond-W25Q32JV-3-3V/m-p/904534

        數據手冊的命令表如下:



圖 3. Sector Erase 命令

        數據手冊中完整的表格如下:

圖 4. QSPI Flash 命令表

    在主函數所在文件中添加如下代碼:

/************************************************FlexSPI********************************************************************/
//add FlexSPI Functions
/* Program data buffer should be 4-bytes alignment, which can avoid busfault due to this memory region is configured as

Device Memory by MPU. */

SDK_ALIGN(static uint8_t s_nor_program_buffer[256], 4);
static uint8_t s_nor_read_buffer[256];

extern status_t flexspi_nor_flash_erase_sector(FLEXSPI_Type *base, uint32_t address);
extern status_t flexspi_nor_flash_page_program(FLEXSPI_Type *base, uint32_t dstAddr, const uint32_t *src);
extern status_t flexspi_nor_get_vendor_id(FLEXSPI_Type *base, uint8_t *vendorId);
extern status_t flexspi_nor_enable_quad_mode(FLEXSPI_Type *base);
extern status_t flexspi_nor_erase_chip(FLEXSPI_Type *base);
extern void flexspi_nor_flash_init(FLEXSPI_Type *base);

/*******************************************************************************

* Code

******************************************************************************/
flexspi_device_config_t deviceconfig = {
.flexspiRootClk = 133000000,
.flashSize = FLASH_SIZE,
.CSIntervalUnit = kFLEXSPI_CsIntervalUnit1SckCycle,
.CSInterval = 2,
.CSHoldTime = 3,
.CSSetupTime = 3,
.dataValidTime = 0,
.columnspace = 0,
.enableWordAddress = 0,
.AWRSeqIndex = 0,
.AWRSeqNumber = 0,
.ARDSeqIndex = NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD,
.ARDSeqNumber = 1,
.AHBWriteWaitUnit = kFLEXSPI_AhbWriteWaitUnit2AhbCycle,
.AHBWriteWaitInterval = 0,
};

const uint32_t customLUT[CUSTOM_LUT_LENGTH] = {
/* Normal read mode -SDR */
/* Normal read mode -SDR */
[4 * NOR_CMD_LUT_SEQ_IDX_READ_NORMAL] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x03, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18),
[4 * NOR_CMD_LUT_SEQ_IDX_READ_NORMAL + 1] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),

/* Fast read mode - SDR */
[4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x0B, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18),
[4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST + 1] = FLEXSPI_LUT_SEQ(
kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_1PAD, 0x08, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04),

/* Fast read quad mode - SDR */
[4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0xEB, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_4PAD, 0x18),
[4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD + 1] = FLEXSPI_LUT_SEQ(
kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_4PAD, 0x06, kFLEXSPI_Command_READ_SDR, kFLEXSPI_4PAD, 0x04),

/* Read extend parameters */
[4 * NOR_CMD_LUT_SEQ_IDX_READSTATUS] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x81, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04),


/* Write Enable */
[4 * NOR_CMD_LUT_SEQ_IDX_WRITEENABLE] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x06, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),

/* Erase Sector */
[4 * NOR_CMD_LUT_SEQ_IDX_ERASESECTOR] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x20, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18),

/* Page Program - single mode */
[4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_SINGLE] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x02, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18),
[4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_SINGLE + 1] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_1PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),

/* Page Program - quad mode */

[4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x32, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18),
[4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD + 1] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_4PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),

/* Read ID */
[4 * NOR_CMD_LUT_SEQ_IDX_READID] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x9F, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04),

/* Enable Quad mode */
[4 * NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x01, kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_1PAD, 0x04),

/* Enter QPI mode */
[4 * NOR_CMD_LUT_SEQ_IDX_ENTERQPI] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x35, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),

/* Exit QPI mode */
[4 * NOR_CMD_LUT_SEQ_IDX_EXITQPI] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_4PAD, 0xF5, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),

/* Read status register */
[4 * NOR_CMD_LUT_SEQ_IDX_READSTATUSREG] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x05, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04),

/* Erase whole chip */
[4 * NOR_CMD_LUT_SEQ_IDX_ERASECHIP] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0xC7, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),
};

/************************************************FlexSPI********************************************************************/
uint8_t flespi_test()
{
/************************************************FlexSPI********************************************************************/
uint32_t i = 0;
status_t status;
uint8_t vendorID = 0;

flexspi_nor_flash_init(EXAMPLE_FLEXSPI);

PRINTF("\r\nFLEXSPI example1 started!\r\n");

/* Get vendor ID. */
status = flexspi_nor_get_vendor_id(EXAMPLE_FLEXSPI, &vendorID);
if (status != kStatus_Success)
{
return status;
}

PRINTF("Vendor ID: 0x%x\r\n", vendorID);

PRINTF("Enter quad mode 1\n");

/* Enter quad mode. */
status = flexspi_nor_enable_quad_mode(EXAMPLE_FLEXSPI);

if (status != kStatus_Success)
{
return status;
}

PRINTF("Enter quad mode 2\n");

for(int i = 0; i<= sizeof(_aclwip_ping_bm)/SECTOR_SIZE; i++)
{
/* Erase sectors. */
PRINTF("Erasing Serial NOR over FlexSPI...\r\n");
status = flexspi_nor_flash_erase_sector(EXAMPLE_FLEXSPI, APP_BASE_ADDRESS + SECTOR_SIZE * i);
if (status != kStatus_Success)
{
PRINTF("Erase sector failure !\r\n");
return -1;

}
}


for(int j = 0; j<= sizeof(_aclwip_ping_bm)/FLASH_PAGE_SIZE; j++)
{
if(j>sizeof(_aclwip_ping_bm)/256-1)
{
memset(s_nor_program_buffer, 0xFFU, sizeof(s_nor_program_buffer));
DCACHE_InvalidateByRange(EXAMPLE_FLEXSPI_AMBA_BASE + APP_BASE_ADDRESS + FLASH_PAGE_SIZE * j, FLASH_PAGE_SIZE);
memcpy(s_nor_program_buffer, &_aclwip_ping_bm[(j)*256], sizeof(_aclwip_ping_bm)%256);
}
else
{
DCACHE_InvalidateByRange(EXAMPLE_FLEXSPI_AMBA_BASE + APP_BASE_ADDRESS + FLASH_PAGE_SIZE * j, FLASH_PAGE_SIZE);
memcpy(s_nor_program_buffer, &_aclwip_ping_bm[(j)*256], sizeof(s_nor_program_buffer));
}

status = flexspi_nor_flash_page_program(EXAMPLE_FLEXSPI, APP_BASE_ADDRESS + FLASH_PAGE_SIZE*(j), (void *)s_nor_program_buffer);

if (status != kStatus_Success)
{
PRINTF("Page program failure !\r\n");
return -1;
}
}
return 0;
}

/************************************************FlexSPI********************************************************************/

 

       對於 flespi_test 函數,其流程如下:

  1. flexspi_nor_flash_init 初始化 flexspi 外設;
  2. flexspi_nor_get_vendor_id 獲取 flash 的 vedor id;
  3. flexspi_nor_enable_quad_mode 使能 qspi 的 quad mode;
  4. 使用 for 循環擦除 sizeof(_aclwip_ping_bm)/SECTOR_SIZE 個 sector size 的 flash 內容,即擦除數組要燒入到的地址範圍。
for(int i = 0; i<= sizeof(_aclwip_ping_bm)/SECTOR_SIZE; i++)
{
/* Erase sectors. */
PRINTF("Erasing Serial NOR over FlexSPI...\r\n");
status = flexspi_nor_flash_erase_sector(EXAMPLE_FLEXSPI, APP_BASE_ADDRESS + SECTOR_SIZE * i);
if (status != kStatus_Success)
{
PRINTF("Erase sector failure !\r\n");
return -1;
}
}
  1. 使用 for 循環寫入sizeof(_aclwip_ping_bm)/FLASH_PAGE_SIZE 個 page size 的 flash 內容,一個 page size 的大小為 FLASH_PAGE_SIZE(256Bytes) 即寫入數組要燒入到的地址範圍。

      這裡會有一個問題:如果代碼的大小不是 256Bytes,需要如何處理?

      這裡通過對數組的最後 256Bytes 做處理:

  • 對最後的 256Bytes 的數據先把要寫入的在 RAM 的 256 大小的數組 s_nor_program_buffer 通過 memset 清除為全 0xFF;
  • 得到數組的最後一個 256Bytes 空間中的數據,通過 memcpy 將數據寫入 s_nor_program_buffer 數組中。
for(int j = 0; j<= sizeof(_aclwip_ping_bm)/FLASH_PAGE_SIZE; j++)
{
if(j>sizeof(_aclwip_ping_bm)/256-1)
{
memset(s_nor_program_buffer, 0xFFU, sizeof(s_nor_program_buffer));
DCACHE_InvalidateByRange(EXAMPLE_FLEXSPI_AMBA_BASE + APP_BASE_ADDRESS + FLASH_PAGE_SIZE * j, FLASH_PAGE_SIZE);
memcpy(s_nor_program_buffer, &_aclwip_ping_bm[(j)*256], sizeof(_aclwip_ping_bm)%256);
}
else
{
DCACHE_InvalidateByRange(EXAMPLE_FLEXSPI_AMBA_BASE + APP_BASE_ADDRESS + FLASH_PAGE_SIZE * j, FLASH_PAGE_SIZE);
memcpy(s_nor_program_buffer, &_aclwip_ping_bm[(j)*256], sizeof(s_nor_program_buffer));
}
status =
flexspi_nor_flash_page_program(EXAMPLE_FLEXSPI, APP_BASE_ADDRESS + FLASH_PAGE_SIZE*(j), (void *)s_nor_program_buffer);
if (status != kStatus_Success)
{
PRINTF("Page program failure !\r\n");
return -1;
}
}

 

5.   編寫 jump_to_application 函數實現程序跳轉

       這裡要區分兩種情況:

       第一種情況:APP 編譯時設置宏定義 XIP_BOOT_HEADER_ENABLE=0,編譯出不包含 FDCB Flash 頭的代碼,此時編譯出的代碼不包含 flash_config 和 ivt 段,如下圖,程序的中斷向量表是放到 0x60002000 的位置的。

       第二種情況:APP 編譯時設置宏定義 XIP_BOOT_HEADER_ENABLE=1,編譯的程序將包含 flash_config 和 ivt 段。

       jump_to_application 函數的實現思路是:

  1. 對 SCB->VTOR 重新賦值為跳轉的 APP 代碼的起始地址,即 APP 的中斷向量表的起始地址。
  2. AppAddr = *(uint32_t*)app_start_address; 取得中斷向量表起始地址的前 4 字節數據賦值給 AppAddr,即棧頂的地址。
  3. __set_MSP(AppAddr); 設置 MSP 指向棧頂。
  4. AppAddr = *(uint32_t*)(app_start_address + 4); 使得 AppAddr 賦值為第二個 4 字節的數據,即 ResetHandler 的地址。
  5. JumpToApp = (pFunc)AppAddr; 將 AppAddr 強制轉化為函數指針類型 pFunc,並賦值給函數指針 JumpToApp。
  6. JumpToApp(); 通過調用函數指針,將使得 PC 指針指向 AppAddr,實現程序跳轉到 APP 的 ResetHandler 執行。

      主函數如下:

/*!
* @brief Main function
*/
int main(void)
{
char ch;
/* Init board hardware. */
BOARD_ConfigMPU();
BOARD_InitBootPins();
BOARD_InitBootClocks();
BOARD_InitDebugConsole();

/* Just enable the trace clock, leave coresight initialization to IDE debugger */
SystemCoreClockUpdate();
CLOCK_EnableClock(kCLOCK_Trace);

PRINTF("hello world.\r\n");
PRINTF("0x = %x\r\n",_aclwip_ping_bm[0]);
flespi_test();
jump_to_application(APP_ADDR);

while (1)
{
}
}


6.   附錄

  1. IMXRT1020RM.pdf,鏈接:

https://cache.nxp.com.cn/secured/assets/documents/en/reference-manual/IMXRT1020RM.pdf?__gda__=1701850166_6fc1a74da8ebd2df63a522fe91280413&fileExt=.pdf

  1. C83140_NOR+FLASH_W25Q64JVSSIQ_規格書_WINBOND(華邦)NOR+FLASH規格書,鏈接:

https://atta.szlcsc.com/upload/public/pdf/source/20161110/1478742546019.pdf?Expires=4070880000&OSSAccessKeyId=LTAIJDIkh7KmGS1H&Signature=P9Za%2BLJZtmLFPAAPdipnILx5bdE%3D&response-content-disposition=attachment%3Bfilename%3DC83140_NOR%2BFLASH_W25Q64JVSSIQ_%25E8%25A7%2584%25E6%25A0%25BC%25E4%25B9%25A6_WINBOND%2528%25E5%258D%258E%25E9%2582%25A6%2529NOR%2BFLASH%25E8%25A7%2584%25E6%25A0%25BC%25E4%25B9%25A6.PDF

★博文內容均由個人提供,與平台無關,如有違法或侵權,請與網站管理員聯繫。

★文明上網,請理性發言。內容一周內被舉報5次,發文人進小黑屋喔~

評論