大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是i.MXRT上使用16MB以上NOR Flash软复位无法正常启动问题的分析解决经验。
痞子衡这几天在支持一个i.MXRT1050客户项目,客户遇到了软复位无法从32MB NOR Flash重新启动的问题。这个客户是做医疗设备的,已经基于i.MXRT做出一款成功的产品了,所以客户其实有丰富的i.MXRT使用经验。目前调试的项目是客户的第二款产品,这个软复位无法启动问题已经困扰他们很久,但问题毕竟不是特别紧急,不影响他们开发进度,所以耽搁至今。这次客户趁着出差苏州参加劳特巴赫TRACE32调试器培训机会,让痞子衡现场帮他们定位问题,经过一番调试和分析,痞子衡终于成功地解决了问题,特此将问题解决的全过程记录下来,供大家参考。
一、问题描述
在描述问题前,首先给大家介绍下客户的项目设计,底下是客户硬件简图。客户选用的i.MXRT1052作为主控,挂载了两个QSPI Flash,FlexSPI接口连接的32MB Flash用于启动和存放静态图片资源(只需要读即可),LPSPI接口连接的1MB Flash用于存放运行时状态数据(需要读写),此外板子连接了一个显示屏,所以还挂载一片SDRAM用于显示缓存,其实SDRAM除了显示缓存功能之外,还用于执行App(QSPI Flash里的App会自加载到SDRAM执行)。
有必要重点介绍下QSPI Flash启动设计细节,客户选用的Flash型号是ISSI的IS25WP256D,这是一款容量256Mb的四线串行Flash。客户启动流程设计的挺复杂,芯片上电之后,BootROM负责从Flash中XIP启动L2 loader程序,L2 loader运行后从Flash中选出最新的一份Boot程序(A/B是双备份),将其加载到SDRAM中执行。Boot程序运行后做一些系统初始化工作,然后直接跳转到App中执行(XIP),App才是最终的客户应用程序,这个应用程序会完成往SDRAM的自拷贝以及跳转执行。
客户的App实际大小接近5MB,对于嵌入式程序来说,这个体量相当大了,这也是为什么客户需要借助专业的劳特巴赫TRACE32调试器来分析定位程序逻辑设计问题。从下图还可以看到从0x60800000开始,Flash中还存放了一些静态图片资源,客户项目有显示屏,Flash里放一些固定图片数据方便UI切换。
介绍完客户的项目设计,现在描述客户的软复位无法重新启动问题。其实这个问题现象很简单,就是每次重新上电启动,程序都是可以正常运行的,但是一旦使用按键软复位(ONOFF Reset),系统就会有一定概率起不来(概率很大,很容易复现),调试器连上去会发现PC停留在BootROM里,这意味着此时BootROM没能正常启动L2 loader。
二、问题分析
让我们来分析一下问题,这个问题要从两方面来考虑:一、板子上芯片的POR和软复位的区别;二、软复位无法启动是概率性的,因此痞子衡想到了如下四处疑点:
- 两种复位下主芯片内部非易失寄存器状态的区别是否对BootROM运行产生了影响?
- 两种复位下主芯片内FlexSPI这个模块状态是否有区别?
- 两种复位下外挂Flash芯片状态是否有区别?
- 客户App代码里是否有某种操作导致了概率性问题的发生?
因为每次都是软复位重新启动出问题,所以客户板级供电设计不在疑点范围内。虽然问题都表现在BootROM没法加载L2 loader执行,但BootROM本身缺陷也不是我们主要考虑的方向,毕竟BootROM是固化在芯片内部的,可靠性有一定保证。我们首先要把疑点放在概率性以及两种复位的差异上,那么我们从哪里开始着手测试?
痞子衡想的是先从第4个疑点开始下手,原因是前3个疑点本质上都由第4个疑点引起的,客户代码的执行可能会改主芯片内部非易失性寄存器,也同时会操作FlexSPI模块去访问外部Flash,它是问题的引爆点。
三、开始测试
3.1 对比非易失性寄存器
i.MXRT内部有一些非易失性寄存器(比如IOMUXC_GPR寄存器组,SRC寄存器等),这些寄存器仅在POR时才会被复位,而普通软复位是不会改变其状态的。客户App代码近5MB,如果是去肉眼排查是否操作了非易失性寄存器,难免有疏漏。最简单的方法就是在正常启动和非正常启动时分别用调试器将这些寄存器的值全部保存下来,然后使用文本工具去对比。经测试,两种情况下,这些非易失性寄存器并无区别,因此这个疑点被排除。
3.2 逐步精简App代码
现在我们开始逐步精简App代码,由于客户代码涉及机密,所以精简的工作由客户来做,当然客户也最清楚如何去精简他们自己的代码。一番测试下来,我们发现App代码里只要不去读存在Flash里的静态图片数据,就不会存在软复位无法重新启动问题,看起来我们已经找到线索了。
四、原因分析
问题出在App代码里读存在Flash里的静态图片数据,这意味着App里可能用了特殊的读Flash方法改变了Flash状态,并且这个Flash状态是非易失性的。谜团接近解开了,痞子衡让客户公布了他们的L2 loader里的FDCB配置头以及App里的读Flash图片的代码实现:
4.1 L2 loader的FDCB
先来看客户的FDCB启动头,客户仅让BootROM配置Flash工作于50MHz,并且是1bit SDR Fast Read(命令是0x0B),这是标准3字节地址读,因此配置成功后通过AHB总线最大可访问16MB以内的Flash空间。因为客户的L2 loader很小,且存储在Flash的起始地址,所以这样的配置对于启动而言没有问题。
4.2 App读Flash实现
再来看客户实现的读Flash函数BigCapRead(),根据前面的介绍,静态图片数据是从0x60800000处开始存储的,因此0x60800000 - 0x60FFFFFF范围内的8MB数据是可以直接AHB读,但是0x61000000地址之后的数据在上述BootROM的配置下无法直接访问,这也是为什么客户写了BigCapRead()函数,这个函数会根据传入的地址范围来判断数据是在低16MB空间还是高16MB空间,然后对地址空间做了一个切换。
#define FLASH_BIG_CAP_SIZE (0x1000000)
static status_t flexspi_nor_select_segment(uint32_t base, uint8_t seg)
{
qspi_transfer_t flashXfer;
status_t status = Success;
uint32_t writeValue = 0x00;
uint32_t readValue = 0x00;
flexspi_nor_write_enable(base, 0, true);
flexspi_nor_read_volatilebankaddr_reg(base, &readValue);
if ((readValue & 0x01) == (seg & 0x1))
{
return Success;
}
writeValue = seg & 0x1;
flexspi_nor_write_volatilebankaddr_reg(base, writeValue);
flexspi_nor_read_volatilebankaddr_reg(base, &readValue);
if (readValue != writeValue)
{
return Failure;
}
flexspi_nor_wait_bus_busy(base);
return Success;
}
static UINT32 BigCapRead(struct flash_dev* dev, UINT32 start_addr, UCHAR *buffer, UINT32 size)
{
UINT32 tempLen = 0;
UINT32 result = 0;
if (start_addr >= FLASH_BIG_CAP_SIZE)
{
flexspi_nor_select_segment(dev->base, 1);
start_addr = start_addr - FLASH_BIG_CAP_SIZE;
result = Read(dev, start_addr, buffer, size);
}
else
{
if (start_addr + size < FLASH_BIG_CAP_SIZE)
{
flexspi_nor_select_segment(dev->base, 0);
result = Read(dev, start_addr, buffer, size);
}
else
{
tempLen = FLASH_BIG_CAP_SIZE - start_addr;
flexspi_nor_select_segment(dev->base, 0);
result = Read(dev, start_addr, buffer, tempLen);
flexspi_nor_select_segment(dev->base, 1);
result = Read(dev, 0, buffer + tempLen, size - tempLen);
}
}
return result;
}
4.3 关于Flash的3/4字节地址
对于16MB以上空间的Flash,总会面临3/4字节地址访问的问题,JESD216规定了3/4字节地址访问标准命令,对于3字节地址Fast Read,其命令是FRD(0x0B);而对于四字节地址Fast Read,其命令是4FRD(0x0C)。对于一个32MB的Flash,如果仅需要访问低16MB空间,可以使用FRD;如果需要访问高16MB空间,则需要使用4FRD。
4FRD相比FRD多传输了一字节地址,对于地址连续的大块数据访问,这个1字节地址影响不太,但是如果是执行代码或者非连续数据访问,4FRD相比FRD还是有效率上的降低的,对于这个问题,不同的厂家提供了不同的解决方案。
客户选用的这款Flash来自ISSI,ISSI的解决方案是在Flash内部增加一个Bank Address Register,其bit0用于实时切换低Bank和高Bank(并且这个位是非易失性的,仅POR才会复位,引脚reset无法复位!),当bit0值为0时,FRD命令访问的是低16MB空间,而bit0置1后,FRD命令此时实际访问的是高16MB空间(从AHB地址上看不出这个变化)。
4.4 解决方案
看到这,这个软复位无法重启问题真相大白了,是由于这颗Flash里的内部非易失寄存器BAR[0]的操作导致的,如果软复位时间点恰好在App读了高16MB空间(Bank1)里的数据之后,此时Bank发生了切换,软复位后BootROM去启动时无法读到存在低16MB空间(Bank0)的有效的L2 loader。如果软复位时间点发生在App正在读低16MB空间的数据,那下次还是可以正常启动,这就是概率性启动失败的原因。
原因调查清楚了,问题解决方法就很简单了,将L2 loader里的FDCB头用4FRD代替FRD,这样BootROM配置完成之后,可以直接AHB读全部的32MB空间,不需要切换Bank,因此App里的BigCapRead()函数里的Bank切换操作可以删掉。
这个经验也告诉了我们,当使用16MB以上Flash作为启动设备时,一定要小心处理好3/4字节地址访问问题,不然就可能出现启动问题。
至此,i.MXRT上使用16MB以上NOR Flash软复位无法正常启动问题的分析解决经验痞子衡便介绍完毕了,掌声在哪里~~~
欢迎订阅
文章会同时发布到我的 博客园主页、CSDN主页、知乎主页、微信公众号 平台上。
微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。