大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是i.MXRT1xxx里SystemReset不复位的GPR寄存器的小妙用。
我们知道稍大规模的项目代码设计一般都是多人协作完成的,在项目开始阶段的总体设计时,项目组长通常会将代码按功能进行划分,每个功能块代码之间尽量做到耦合度低、互不依赖、互不影响,这样各功能可以独立进行单元测试,项目得以并行开发,后期通过事先定义好的接口/协议进行功能块集成即可。
但上述方法在嵌入式软件项目里有时候会遇到功能块集成后互相干扰的问题,因为嵌入式项目很多时候并不是纯软件设计,也会跟片内外设资源打交道,而片内外设属于硬件范畴,硬件模块的工作是有前后状态依赖的(这点在片内时钟的配置上体现得尤其明显),出了问题传统方法是具体分析具体解决,来一个就解决一个,但任何代码的改动或者后期新特性的增加都可能会带来新的潜在干扰问题。
那么对于上述困境,有没有一个一劳永逸的解决方法?其实是有的!那就是每个功能块在设计时都不要依赖芯片初始状态,按照进入时先清理系统环境,然后做功能设计,退出时做一下系统恢复。这种方法虽然保险,但是会引入集成后项目整体运行低效的问题。今天痞子衡要在具体项目实战中介绍一种利用i.MXRT芯片内System Reset后不复位的GPR寄存器来解决属性上互斥的功能代码块集成互相干扰问题的方法。
一、SBL项目中的痛点
恩智浦MCU SE团队近期一直在加班加点赶一个大项目,这个项目是为客户产品OTA需求而生的。我们知道在线升级是每个智能产品都不可绕开的话题,恩智浦SE团队为了方便客户在基于i.MXRT/LPC的产品上做在线升级,特别推出OTA参考设计,下面是功能架构简图:项目分为SBL + SFW两部分,SBL负责ISP本地更新(UART/USB)以及App切换管理;SFW是一个示例App,其除了客户项目业务功能外,也集成了远程更新功能(WiFi、以太、U盘、SD卡四种升级方式)。
![](https://i-blog.csdnimg.cn/blog_migrate/a6f817f74d2098e43e333144ab5aca63.png)
在SBL代码设计里,主要有两大子功能模块:一个是ISP本地更新 (isp_boot_main),另一个是App切换管理(sbl_boot_main),其中ISP本地更新属于可选项,而App切换管理是必选。
SBL的主流程是上电启动运行后,先执行ISP本地更新功能块,在超时时间内,如果有收到来自Host的ISP命令,则进入ISP命令处理,此后除非执行ISP复位命令或者有板级复位,否则不会退出ISP;
如果在超时时间内没有收到ISP命令,则转到App切换管理功能块。在验证App时,如果发现Flash里有合法App,则跳转过去执行;如果没有发现合法的App,会重新返回ISP本地更新(此时是无限超时)。大概主逻辑代码如下:
#define COMPONENT_MCU_ISP
#define ISP_TIMEOUT (5) // in seconds
int main(void)
{
#if (defined(COMPONENT_MCU_ISP))
// 先尝试isp本地更新(有5s超时)
bool isInfiniteIsp = false;
isp_boot_main(isInfiniteIsp);
#endif
// 无本地升级则进入app跳转处理
sbl_boot_main();
}
#if (defined(COMPONENT_MCU_ISP))
void isp_boot_main(bool isInfiniteIsp)
{
if (isInfiniteIsp || ISP_TIMEOUT)
{
// isp相关功能外设初始化
isp_boot_init();
// 在5s超时时间内/无限超时去等待isp命令
isp_boot_run(isInfiniteIsp);
}
}
#endif
void sbl_boot_main(void)
{
// 判断Flash里是否存在合法的app
if (sbl_boot_go() != 0)
{
#if (defined(COMPONENT_MCU_ISP))
// 无合法app,重入isp本地更新(无限超时)
bool isInfiniteIsp = true;
isp_boot_main(isInfiniteIsp);
#endif
NVIC_SystemReset();
}
else
{
// 有合法app,跳转到app执行
sbl_do_boot();
}
while (1);
}
上述SBL设计里,你会发现ISP本地更新和App切换管理两个功能块在执行上是互斥的,是Flash里的App处理需求将它们联系在了一起。从软件集成角度来说,这两个功能本不该互相影响,但实际上它们之间产生了互相影响,因为各自在设计时没有遵循进入时清理系统、退出时恢复现场的准则,所以 isp_boot_main() 在超时结束后跳到 sbl_boot_main() 对其部分验签功能产生了影响,而 sbl_boot_main() 执行后没找到合法App跳回 isp_boot_main() 时又出现ISP功能不正常的情况,我们需要解决这个问题。
二、寻找i.MXRT中理想的GPR寄存器
第一小节里描述的问题,可以通过功能块退出时恢复现场来解决,但是每个模块代码量都比较大,使用代码去逐一恢复现场不太容易。痞子衡想到的一个好办法就是调用 NVIC_SystemReset() 函数来简单粗暴地将芯片系统复位,我们所需要做的就是寻找一个区域,能够临时存放标志位,并且这个区域内容不受系统软复位的影响,芯片复位回来之后在SBL里增加对标志位的判断处理,处理结束后再将标志位清掉。
在寻找这个不受系统软复位影响的区域前,我们先对i.MXRT里面的电源管理架构作个基本了解。下图是i.MXRT1060的电源架构,除了USB和ADC模块特殊供电需求外,芯片一共有四种电源输入。在板级供电设计时,通常VDD_SNVS_IN需要单独一路外部输入(设计上应由电池供电),其他三路电源VDD_HIGH_IN / DCDC_IN / VDD_SOC_IN可共用一路外部输入(芯片POR_B引脚往往连在这个外部输入控制上)。
VDD_HIGH_IN:给芯片内部LDO供电
DCDC_IN:给芯片内部DCDC模块供电
VDD_SOC_IN:给芯片主系统(Core,SoC,Memory)供电
VDD_SNVS_IN:给芯片内部SNVS域相关模块供电
![](https://i-blog.csdnimg.cn/blog_migrate/3d8a959e7fafedb791683f8eee7a3a7a.png)
从芯片系统复位等级上来分,一共有三类复位:第一类是借助Cortex-M7内核SCB模块的AIRCR寄存器中集成的SYSRESETREQ复位的支持、第二类是DCDC重新上电(POR_B复位)、第三类是整体重新上电。依据这三种不同程度的复位,痞子衡整理了i.MXRT上所有可存放标志位的区域受不同复位类型影响情况如下:
模块 \ 复位类型 | NVIC_SystemReset() | POR_B和DCDC重新上电 | 整体重新上电 |
---|---|---|---|
TCM OCRAM IOMUXC_GPR SRC_GPR | 保持 | 复位 | 复位 |
IOMUXC_SNVS_GPR SNVS_LPGPR | 保持 | 保持 | 复位 |
Flash, eFuse | 保持 | 保持 | 保持 |
根据上表,我们先排除掉NVM属性的Flash和eFuse,它们不符合临时存放、轻松读写的需求。TCM / OCRAM可用,但需要在SBL工程里做特殊处理,分配一块.noinit区,并且要确定BootROM没有使用这个区域,用起来还是有点麻烦。IOMUXC_GPR / SRC_GPR用起来简单,但它们已被SoC / BootROM占用了,不能随便使用,对芯片产生的影响未知。IOMUXC_SNVS_GPR / SNVS_LPGPR这两个都不错,但后者在使能加密时有时会被用来存放用户密钥。所以 IOMUXC_SNVS_GPR 寄存器才是最佳选择,这也是真正意义上开放给用户自由使用的GPR寄存器。
三、在SBL项目中使用GPR寄存器
现在我们找到了理想的 IOMUXC_SNVS_GPR 寄存器,那么可在SBL代码中增加两个函数 isp_cleanup_enter()、isp_cleanup_exit(),前者用于触发软复位前标记状态,后者用于软复位后读取标记的状态做相应处理。最终修改后的SBL主逻辑代码如下:
#define CLEANUP_ISP_TO_SBL (0x5A)
#define CLEANUP_SBL_TO_ISP (0xA5)
bool isp_cleanup_exit(bool *isInfiniteIsp)
{
uint32_t flag = IOMUXC_SNVS_GPR->GPR0;
switch (flag)
{
// SBL_TO_ISP软复位情况下,进入无限超时isp
case CLEANUP_SBL_TO_ISP:
*isInfiniteIsp = true;
flag = 0x0;
break;
// 第一次上电或ISP_TO_SBL软复位情况下,直接退出isp
case CLEANUP_ISP_TO_SBL:
default:
break;
}
IOMUXC_SNVS_GPR->GPR0 = 0x0;
return flag;
}
void isp_cleanup_enter(uint32_t flag)
{
IOMUXC_SNVS_GPR->GPR0 = flag;
NVIC_SystemReset();
}
#if (defined(COMPONENT_MCU_ISP))
void isp_boot_main(bool isInfiniteIsp)
{
// 加一级对于Reset不复位flag的判断处理
if (!isp_cleanup_exit(&isInfiniteIsp))
{
if (isInfiniteIsp || ISP_TIMEOUT)
{
isp_boot_init();
isp_boot_run(isInfiniteIsp);
// 标记flag为ISP_TO_SBL,并触发软复位
isp_cleanup_enter(CLEANUP_ISP_TO_SBL);
}
}
}
#endif
void sbl_boot_main(void)
{
if (sbl_boot_go() != 0)
{
#if (defined(COMPONENT_MCU_ISP))
// 标记flag为SBL_TO_ISP,并触发软复位
isp_cleanup_enter(CLEANUP_SBL_TO_ISP);
#endif
NVIC_SystemReset();
}
else
{
sbl_do_boot();
}
while (1);
}
至此,i.MXRT1xxx里SystemReset不复位的GPR寄存器的小妙用痞子衡便介绍完毕了,掌声在哪里~~~
欢迎订阅
文章会同时发布到我的 博客园主页、CSDN主页、知乎主页、微信公众号 平台上。
微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。
![](https://i-blog.csdnimg.cn/blog_migrate/a99d94865a00020c85359afac0d50620.jpeg)