痞子衡嵌入式:導(dǎo)致串行NOR Flash在i.MXRT下無(wú)法正常下載/啟動(dòng)的常見(jiàn)因素之Write Protection
大家好,我是痞子衡,是正經(jīng)搞技術(shù)的痞子。今天痞子衡給大家介紹的是導(dǎo)致串行NOR Flash在i.MXRT下無(wú)法正常下載/啟動(dòng)的常見(jiàn)因素之Write Protection。
i.MXRT系列MCU發(fā)布已兩年多了,基于i.MXRT的客戶產(chǎn)品也越來(lái)越多,可以說(shuō)是全面開(kāi)花了。痞子衡作為i.MXRT產(chǎn)品線的系統(tǒng)應(yīng)用工程師,早期的時(shí)候還可以盡情做參考設(shè)計(jì),現(xiàn)在基本大量時(shí)間都被客戶支持占據(jù)了。
因?yàn)閕.MXRT系列都沒(méi)有內(nèi)置Flash(RT1064, RT1024等SIP型號(hào)除外),因此為其搭配一塊串行NOR Flash去啟動(dòng)是客戶項(xiàng)目的頭等大事,而串行NOR Flash廠商非常多,客戶選擇余地很大,因此我們不得不與客戶一起同茫茫Flash型號(hào)打交道,痞子衡也常常調(diào)侃自己已淪為Flash測(cè)試工程師。
痞子衡在支持客戶解決串行NOR Flash下載啟動(dòng)問(wèn)題過(guò)程中主要遇到幾個(gè)常見(jiàn)因素,這幾個(gè)因素可能會(huì)影響Flash在i.MXRT下無(wú)法正常使用,上兩篇痞子衡分別講了 《SFDP因素》 和 《QE bit因素》, 今天痞子衡重點(diǎn)跟大家聊聊Write Protection這個(gè)因素。
一、引入客戶板子可以啟動(dòng)、無(wú)法再次下載問(wèn)題
痞子衡最近遇到一個(gè)智能電表廠商客戶,他們項(xiàng)目板卡選用的是主控i.MXRT1051 + 華邦W25Q64JVSSIQ,應(yīng)用程序是MBED bootloader + User App二級(jí)加載設(shè)計(jì),其中MBED bootloader是由Arm Pelion物聯(lián)網(wǎng)小組主導(dǎo)設(shè)計(jì)的,User App是這個(gè)電表廠商自己的功能代碼。
客戶的問(wèn)題是燒寫(xiě)了一個(gè)特定版本的MBED bootloader運(yùn)行之后,板卡Flash無(wú)法再次做燒寫(xiě)了,但是板子是能夠正常從Flash啟動(dòng)的。客戶之后嘗試使用了各種下載工具都不管用(J-Flash/IDE/NXP Tool等),其中下載工具包括痞子衡設(shè)計(jì)的一站式下載工具 MCUBootUtility ,于是問(wèn)題就轉(zhuǎn)到了痞子衡這里(好像有點(diǎn)躺槍的感覺(jué))。工具后臺(tái)報(bào)的錯(cuò)是擦除或者寫(xiě)入時(shí)會(huì)返回 kStatus_FlexSPINOR_CommandFailure,導(dǎo)致無(wú)法下載。
既然問(wèn)題和特定版本的MBED bootloader有關(guān),那看起來(lái)就是這個(gè)bootloader引入的問(wèn)題,但是痞子衡拿不到客戶MBED bootloader源碼,無(wú)法做白盒分析。鑒于痞子衡這么多年和Flash打交道的經(jīng)驗(yàn),痞子衡盲猜是bootloader使能了Flash的軟件寫(xiě)保護(hù)功能(Software Write Protection)導(dǎo)致的問(wèn)題,于是痞子衡讓客戶寄來(lái)了一塊板卡,實(shí)測(cè)來(lái)證實(shí)痞子衡的猜想。
二、修改SDK FlexSPI例程來(lái)讀取Status Register
查看W25Q64JVSSIQ數(shù)據(jù)手冊(cè)得知,軟件寫(xiě)保護(hù)功能的配置集成在Flash器件內(nèi)部非易失性Status Register中,這款Flash一共有3個(gè)8bit的Status Register(狀態(tài)寄存器均支持易失性寫(xiě)入(即斷電恢復(fù))和非易失性寫(xiě)入(即斷電保持),由前導(dǎo)的Write Enable命令類(lèi)型0x06/0x50決定),并且每個(gè)Status Register都有不同的讀寫(xiě)命令:
現(xiàn)在我們簡(jiǎn)單修改了下SDK里的如下flexspi nor例程(選擇ram build),增加上述三個(gè)Status Register讀取功能的支持,從而實(shí)測(cè)讀取Status Register來(lái)做驗(yàn)證。
\SDK_2.9.1_EVKB-IMXRT1050\boards\evkbimxrt1050\driver_examples\flexspi\nor\polling_transfer
首先是在 app.h 和 flexspi_nor_polling_transfer.c 中將Status Register的讀取命令加入到LUT表中。原來(lái)工程里已經(jīng)有Status Register 1的讀取支持,所以我們僅需增加Status Register 2/3的支持即可。因?yàn)檫@新增的兩條命令,需要將CUSTOM_LUT_LENGTH由60改到64(i.MXRT1051上最大64,即支持16條LUT Sequence)。
const uint32_t customLUT[CUSTOM_LUT_LENGTH] = {
// ...
/* 原有 Read status register 1 */
[4 * NOR_CMD_LUT_SEQ_IDX_READSTATUSREG] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x05, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04),
/* 新增 Read status register 2 */
[4 * NOR_CMD_LUT_SEQ_IDX_READSTATUSREG2] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x35, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04),
/* 新增 Read status register 3 */
[4 * NOR_CMD_LUT_SEQ_IDX_READSTATUSREG3] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x15, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04),
// ...
};
然后在 flexspi_nor_flash_ops.c 中新增如下 flexspi_nor_get_status_register() 函數(shù),這個(gè)函數(shù)直接仿照 flexspi_nor_get_vendor_id() 函數(shù)流程寫(xiě)即可,F(xiàn)lash端時(shí)序是一樣的。
status_t flexspi_nor_get_status_register(FLEXSPI_Type *base, uint8_t seqIndex, uint8_t *regValue)
{
uint32_t readValue;
flexspi_transfer_t flashXfer;
flashXfer.deviceAddress = 0;
flashXfer.port = kFLEXSPI_PortA1;
flashXfer.cmdType = kFLEXSPI_Read;
flashXfer.SeqNumber = 1;
flashXfer.seqIndex = seqIndex;
flashXfer.data = &readValue;
flashXfer.dataSize = 1;
status_t status = FLEXSPI_TransferBlocking(base, &flashXfer);
*regValue = (uint8_t)readValue;
/* Do software reset. */
FLEXSPI_SoftwareReset(base);
return status;
}
最后就是在 flexspi_nor_polling_transfer.c 中的 main() 函數(shù)里增加 flexspi_nor_get_status_register() 函數(shù)調(diào)用語(yǔ)句,將Status Register 1/2/3的值全部讀取出來(lái)。
static uint8_t s_regValue1 = 0;
static uint8_t s_regValue2 = 0;
static uint8_t s_regValue3 = 0;
int main(void)
{
status_t status;
BOARD_ConfigMPU();
BOARD_InitPins();
BOARD_BootClockRUN();
BOARD_InitDebugConsole();
flexspi_nor_flash_init(EXAMPLE_FLEXSPI);
/* Get status register 1-3. */
status = flexspi_nor_get_status_register(EXAMPLE_FLEXSPI, NOR_CMD_LUT_SEQ_IDX_READSTATUSREG, &s_regValue1);
status = flexspi_nor_get_status_register(EXAMPLE_FLEXSPI, NOR_CMD_LUT_SEQ_IDX_READSTATUSREG2, &s_regValue2);
status = flexspi_nor_get_status_register(EXAMPLE_FLEXSPI, NOR_CMD_LUT_SEQ_IDX_READSTATUSREG3, &s_regValue3);
// ...
}
三、W25Q64JVSSIQ的Write Protection特性
將上一節(jié)修改后的 flexspi nor 例程下載進(jìn)RAM調(diào)試運(yùn)行,可以讀出Status Register 1/2/3的值分別為0x40、0x42、0x60,看起來(lái)Status Register確實(shí)被MBED bootloader修改過(guò)。痞子衡標(biāo)出了W25Q64JVSSIQ內(nèi)部狀態(tài)寄存器中所有與寫(xiě)保護(hù)相關(guān)的bit位如下,我們需要對(duì)照Flash數(shù)據(jù)手冊(cè)具體查看讀出來(lái)的值對(duì)應(yīng)了什么樣的寫(xiě)保護(hù)設(shè)置:
首先Status Register1[SRP],Status Register2[SRL]以及外部WP#引腳共同決定Status Register設(shè)置條件,本例中SRL和SRP均為0,則WP#引腳控制不生效,Status Register可以直接被設(shè)置。
Status Register中其他寫(xiě)保護(hù)相關(guān)的bit位解釋如下,其中WPS是核心設(shè)置,它決定了寫(xiě)保護(hù)是用單獨(dú)的Block lock命令來(lái)控制(見(jiàn)Flash命令集)還是直接由Status Register1/2中的CMP,TB,SEC,BPx位來(lái)決定。
Status Register3[WPS]:決定寫(xiě)保護(hù)策略是獨(dú)立的Block鎖定命令控制(WPS=1,默認(rèn)設(shè)置)還是由Status Register1/2控制(WPS=0)
Status Register1[BPx]:指定Flash Memory保護(hù)區(qū)域塊范圍
Status Register1[SEC]:指定Flash Memory保護(hù)區(qū)域塊單位是4KB Sector(SEC=1)還是64KB Block(SEC=0)
Status Register1[TB]:指定Flash Memory保護(hù)區(qū)域是從Top(TB=0)/Bottom(TB=1)開(kāi)始
Status Register2[CMP]:決定由BPx,SEC,TB決定的Flash Memory保護(hù)區(qū)域是否生效(否的話,則區(qū)域外是受保護(hù)的)
綜合上面分析,最后我們發(fā)現(xiàn)MBED bootloader將全部的8MB Flash空間都保護(hù)起來(lái)了,所以各種下載工具都無(wú)法正常燒寫(xiě)這款Flash了。
四、修改SDK FlexSPI例程來(lái)寫(xiě)入Status Register
要想重新使能Flash燒寫(xiě),需要一個(gè)單獨(dú)的嵌入式小工程將Status Register1/2/3值再改回到默認(rèn)狀態(tài)(WPS=0, CMP=0),可按照第2節(jié)里讀SR功能修改步驟,代碼如下。代碼工程修改完成后借助調(diào)試器下載到RAM運(yùn)行一次即可,需要注意應(yīng)在芯片SDP模式下運(yùn)行,運(yùn)行結(jié)束后,立刻借助其他下載工具將Flash里舊固件更新掉,保證這個(gè)過(guò)程中不存在軟復(fù)位而導(dǎo)致舊固件被再次運(yùn)行的可能。(此處是將Flash里的MBED bootloader換掉,因?yàn)槭撬谑鼓蹻lash的寫(xiě)保護(hù)功能)
// flexspi_nor_flash_ops.c 文件中新增 flexspi_nor_set_status_register() 函數(shù)
status_t flexspi_nor_set_status_register(FLEXSPI_Type *base, uint32_t seqIdx, uint8_t regValue)
{
flexspi_transfer_t flashXfer;
status_t status;
uint32_t writeValue = (uint32_t)regValue;
/* Write enable */
status = flexspi_nor_write_enable(base, 0);
if (status != kStatus_Success)
{
return status;
}
flashXfer.deviceAddress = 0;
flashXfer.port = kFLEXSPI_PortA1;
flashXfer.cmdType = kFLEXSPI_Write;
flashXfer.SeqNumber = 1;
flashXfer.seqIndex = seqIdx;
flashXfer.data = &writeValue;
flashXfer.dataSize = 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);
return status;
}
// app.h 文件中
#define NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG 9
#define NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG2 10
#define NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG3 11
//#define NOR_CMD_LUT_SEQ_IDX_ENTERQPI 10
//#define NOR_CMD_LUT_SEQ_IDX_EXITQPI 11
// flexspi_nor_polling_transfer.c 文件中將Status Register的寫(xiě)入命令加入到LUT表中
const uint32_t customLUT[CUSTOM_LUT_LENGTH] = {
// ...
/* 原有 Write status register 1 */
[4 * NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x01, kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_1PAD, 0x04),
/* 新增 Write status register 2 */
[4 * NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG2] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x31, kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_1PAD, 0x04),
/* 新增 Write status register 3 */
[4 * NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG3] =
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x11, 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),
*/
};
int main(void)
{
status_t status;
BOARD_ConfigMPU();
BOARD_InitPins();
BOARD_BootClockRUN();
BOARD_InitDebugConsole();
flexspi_nor_flash_init(EXAMPLE_FLEXSPI);
/* Set status register 1-3. */
status = flexspi_nor_set_status_register(EXAMPLE_FLEXSPI, NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG, 0x00);
status = flexspi_nor_set_status_register(EXAMPLE_FLEXSPI, NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG2, 0x02);
status = flexspi_nor_set_status_register(EXAMPLE_FLEXSPI, NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG3, 0x60);
// ...
}
五、寫(xiě)在最后
這里僅以華邦Flash為例介紹Write Protection,其他廠商Flash對(duì)于這個(gè)Write Protection特性設(shè)計(jì)也許有所不同,需要查看數(shù)據(jù)手冊(cè)具體分析。此外鑒于Flash這種種因素會(huì)導(dǎo)致在i.MXRT無(wú)法下載或正常啟動(dòng),痞子衡之前計(jì)劃做的全新上位機(jī)工具M(jìn)CUTestSuite,會(huì)考慮將串行NOR Flash狀態(tài)信息查詢功能(SFDP/QE bit/Write Protection等)也放進(jìn)去,敬請(qǐng)關(guān)注這個(gè)新項(xiàng)目:
- MCUTestSuite工具項(xiàng)目:https://github.com/JayHeng/NXP-MCUTestSuite
至此,導(dǎo)致串行NOR Flash在i.MXRT下無(wú)法正常下載/啟動(dòng)的常見(jiàn)因素之Write Protection痞子衡便介紹完畢了,掌聲在哪里~~~
歡迎訂閱
文章會(huì)同時(shí)發(fā)布到我的 博客園主頁(yè)、CSDN主頁(yè)、知乎主頁(yè)、微信公眾號(hào) 平臺(tái)上。
微信搜索"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機(jī)上第一時(shí)間看了哦。

最后歡迎關(guān)注痞子衡個(gè)人微信公眾號(hào)【痞子衡嵌入式】,一個(gè)專(zhuān)注嵌入式技術(shù)的公眾號(hào),跟著痞子衡一起玩轉(zhuǎn)嵌入式。
衡杰(痞子衡),目前就職于恩智浦(NXP)半導(dǎo)體MCU系統(tǒng)應(yīng)用部門(mén),擔(dān)任高級(jí)嵌入式系統(tǒng)應(yīng)用工程師。
專(zhuān)欄內(nèi)所有文章的轉(zhuǎn)載請(qǐng)注明出處:http://www.rzrgm.cn/henjay724/
與痞子衡進(jìn)一步交流或咨詢業(yè)務(wù)合作請(qǐng)發(fā)郵件至 hengjie1989@foxmail.com
可以關(guān)注痞子衡的Github主頁(yè) https://github.com/JayHeng,有很多好玩的嵌入式項(xiàng)目。
關(guān)于專(zhuān)欄文章有任何疑問(wèn)請(qǐng)直接在博客下面留言,痞子衡會(huì)及時(shí)回復(fù)免費(fèi)(劃重點(diǎn))答疑。
痞子衡郵箱已被私信擠爆,技術(shù)問(wèn)題不推薦私信,堅(jiān)持私信請(qǐng)先掃碼付款(5元起步)再發(fā)。
浙公網(wǎng)安備 33010602011771號(hào)