痞子衡嵌入式:导致串行NOR Flash在i.MXRT下无法正常下载/启动的常见因素之Write Protection


  大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是导致串行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-IMXRT1050\boards\evkbimxrt1050\driver_examples\flexspi\nor\polling_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等)也放进去,敬请关注这个新项目:

  至此,导致串行NOR Flash在i.MXRT下无法正常下载/启动的常见因素之Write Protection痞子衡便介绍完毕了,掌声在哪里~~~

欢迎订阅

文章会同时发布到我的 博客园主页CSDN主页知乎主页微信公众号 平台上。

微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。

posted @ 2021-05-17 23:05  痞子衡  阅读(784)  评论(0编辑  收藏  举报