• 痞子衡嵌入式:导致串行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-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等)也放进去,敬请关注这个新项目:

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

    欢迎订阅

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

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

      最后欢迎关注痞子衡个人微信公众号【痞子衡嵌入式】,一个专注嵌入式技术的公众号,跟着痞子衡一起玩转嵌入式。

    痞子衡嵌入式-微信二维码 痞子衡嵌入式-微信收款二维码 痞子衡嵌入式-支付宝收款二维码

      衡杰(痞子衡),目前就职于恩智浦MCU系统部门,担任嵌入式系统应用工程师。

      专栏内所有文章的转载请注明出处:http://www.cnblogs.com/henjay724/

      与痞子衡进一步交流或咨询业务合作请发邮件至 hengjie1989@foxmail.com

      可以关注痞子衡的Github主页 https://github.com/JayHeng,有很多好玩的嵌入式项目。

      关于专栏文章有任何疑问请直接在博客下面留言,痞子衡会及时回复免费(划重点)答疑。

      痞子衡邮箱已被私信挤爆,技术问题不推荐私信,坚持私信请先扫码付款(5元起步)再发。


  • 相关阅读:
    ASP.NET 表单验证 Part.2(实现表单验证)
    长工的买房故事
    软件界面交互和易用性改进总结[zz]
    访问hotmail邮箱的途径
    Google内部收集员工创意的方法[转载]
    Web2.0地图展望
    C++开源跨平台类库集
    庆祝lucky荣登早教网首页宝宝
    在那些打磨汉芯的日子里[转贴]
    在中国搞技术只能混碗饭吃,没有太大希望
  • 原文地址:https://www.cnblogs.com/henjay724/p/14779064.html
Copyright © 2020-2023  润新知