大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是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 软复位无法正常启动问题的分析解决经验痞子衡便介绍完毕了,掌声在哪里~~~