大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是导致串行NOR Flash在i.MXRT下无法正常下载/启动的常见因素之Write Protection。
i.MXRT系列MCU发布已两年多了,基于i.MXRT的客户产品也越来越多,可以说是全面开花了。痞子衡作为i.MXRT产品线的系统应用工程师,早期的时候还可以尽情做参考设计,现在基本大量时间都被客户支持占据了。
因为i.MXRT系列都没有内置Flash(RT1064, RT1024等SIP型号除外),因此为其搭配一块串行NOR Flash去启动是客户项目的头等大事,而串行NOR Flash厂商非常多,客户选择余地很大,因此我们不得不与客户一起同茫茫Flash型号打交道,痞子衡也常常调侃自己已沦为Flash测试工程师。
痞子衡在支持客户解决串行NOR Flash下载启动问题过程中主要遇到几个常见因素,这几个因素可能会影响Flash在i.MXRT下无法正常使用,上两篇痞子衡分别讲了 《SFDP因素》 和 《QE bit因素》, 今天痞子衡重点跟大家聊聊Write Protection这个因素。
一、引入客户板子可以启动、无法再次下载问题
痞子衡最近遇到一个智能电表厂商客户,他们项目板卡选用的是主控i.MXRT1051 + 华邦W25Q64JVSSIQ,应用程序是MBED bootloader + User App二级加载设计,其中MBED bootloader是由Arm Pelion物联网小组主导设计的,User App是这个电表厂商自己的功能代码。
客户的问题是烧写了一个特定版本的MBED bootloader运行之后,板卡Flash无法再次做烧写了,但是板子是能够正常从Flash启动的。客户之后尝试使用了各种下载工具都不管用(J-Flash/IDE/NXP Tool等),其中下载工具包括痞子衡设计的一站式下载工具 MCUBootUtility ,于是问题就转到了痞子衡这里(好像有点躺枪的感觉)。工具后台报的错是擦除或者写入时会返回 kStatus_FlexSPINOR_CommandFailure,导致无法下载。
既然问题和特定版本的MBED bootloader有关,那看起来就是这个bootloader引入的问题,但是痞子衡拿不到客户MBED bootloader源码,无法做白盒分析。鉴于痞子衡这么多年和Flash打交道的经验,痞子衡盲猜是bootloader使能了Flash的软件写保护功能(Software Write Protection)导致的问题,于是痞子衡让客户寄来了一块板卡,实测来证实痞子衡的猜想。
二、修改SDK FlexSPI例程来读取Status Register
查看W25Q64JVSSIQ数据手册得知,软件写保护功能的配置集成在Flash器件内部非易失性Status Register中,这款Flash一共有3个8bit的Status Register(状态寄存器均支持易失性写入(即断电恢复)和非易失性写入(即断电保持),由前导的Write Enable命令类型0x06/0x50决定),并且每个Status Register都有不同的读写命令:
现在我们简单修改了下SDK里的如下flexspi nor例程(选择ram build),增加上述三个Status Register读取功能的支持,从而实测读取Status Register来做验证。
SDK_2.9.1_EVKB-IMXRT1050oardsevkbimxrt1050driver_examplesflexspi orpolling_transfer
首先是在 app.h 和 flexspi_nor_polling_transfer.c 中将Status Register的读取命令加入到LUT表中。原来工程里已经有Status Register 1的读取支持,所以我们仅需增加Status Register 2/3的支持即可。因为这新增的两条命令,需要将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() 函数,这个函数直接仿照 flexspi_nor_get_vendor_id() 函数流程写即可,Flash端时序是一样的。
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() 函数里增加 flexspi_nor_get_status_register() 函数调用语句,将Status Register 1/2/3的值全部读取出来。
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特性
将上一节修改后的 flexspi nor 例程下载进RAM调试运行,可以读出Status Register 1/2/3的值分别为0x40、0x42、0x60,看起来Status Register确实被MBED bootloader修改过。痞子衡标出了W25Q64JVSSIQ内部状态寄存器中所有与写保护相关的bit位如下,我们需要对照Flash数据手册具体查看读出来的值对应了什么样的写保护设置:
首先Status Register1[SRP],Status Register2[SRL]以及外部WP#引脚共同决定Status Register设置条件,本例中SRL和SRP均为0,则WP#引脚控制不生效,Status Register可以直接被设置。
Status Register中其他写保护相关的bit位解释如下,其中WPS是核心设置,它决定了写保护是用单独的Block lock命令来控制(见Flash命令集)还是直接由Status Register1/2中的CMP,TB,SEC,BPx位来决定。
Status Register3[WPS]:决定写保护策略是独立的Block锁定命令控制(WPS=1,默认设置)还是由Status Register1/2控制(WPS=0)
Status Register1[BPx]:指定Flash Memory保护区域块范围
Status Register1[SEC]:指定Flash Memory保护区域块单位是4KB Sector(SEC=1)还是64KB Block(SEC=0)
Status Register1[TB]:指定Flash Memory保护区域是从Top(TB=0)/Bottom(TB=1)开始
Status Register2[CMP]:决定由BPx,SEC,TB决定的Flash Memory保护区域是否生效(否的话,则区域外是受保护的)
综合上面分析,最后我们发现MBED bootloader将全部的8MB Flash空间都保护起来了,所以各种下载工具都无法正常烧写这款Flash了。
四、修改SDK FlexSPI例程来写入Status Register
要想重新使能Flash烧写,需要一个单独的嵌入式小工程将Status Register1/2/3值再改回到默认状态(WPS=0, CMP=0),可按照第2节里读SR功能修改步骤,代码如下。代码工程修改完成后借助调试器下载到RAM运行一次即可,需要注意应在芯片SDP模式下运行,运行结束后,立刻借助其他下载工具将Flash里旧固件更新掉,保证这个过程中不存在软复位而导致旧固件被再次运行的可能。(此处是将Flash里的MBED bootloader换掉,因为是它在使能Flash的写保护功能)
// flexspi_nor_flash_ops.c 文件中新增 flexspi_nor_set_status_register() 函数
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的写入命令加入到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);
// ...
}
五、写在最后
这里仅以华邦Flash为例介绍Write Protection,其他厂商Flash对于这个Write Protection特性设计也许有所不同,需要查看数据手册具体分析。此外鉴于Flash这种种因素会导致在i.MXRT无法下载或正常启动,痞子衡之前计划做的全新上位机工具MCUTestSuite,会考虑将串行NOR Flash状态信息查询功能(SFDP/QE bit/Write Protection等)也放进去,敬请关注这个新项目:
- MCUTestSuite工具项目:https://github.com/JayHeng/NXP-MCUTestSuite
至此,导致串行NOR Flash在i.MXRT下无法正常下载/启动的常见因素之Write Protection痞子衡便介绍完毕了,掌声在哪里~~~
欢迎订阅
文章会同时发布到我的 博客园主页、CSDN主页、知乎主页、微信公众号 平台上。
微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。