0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

i.MXRT上提升代码执行性能的十八般武艺

GReq_mcu168 来源:痞子衡嵌入式 作者:痞子衡嵌入式 2022-05-23 09:31 次阅读

今天给大家介绍的是在串口波特率识别实例里逐步展示i.MXRT上提升代码执行性能的十八般武艺

恩智浦 MCU SE 团队近期一直在加班加点赶 SBL 项目(解决客户产品 OTA 需求),这个项目里集成了 ISP 本地升级(UART/USB)功能,其中 UART 口下载升级实现里加入了自动波特率识别支持,具体识别方法见 《串口(UART)自动波特率识别程序设计与实现(中断)》 一文,这一套 ISP 代码其实是移植于 i.MXRT Flashloader(更早期的时候叫 KBOOT)。

ISP 代码放在 SBL 工程里会出现高波特率(比如115200)无法识别的问题,但在低波特率的情况下(比如9600,19200),ISP 代码是功能正常的,说明代码本身并不存在逻辑缺陷,但高波特率下就异常了,大概率是遇到了代码执行性能瓶颈。今天痞子衡就尝试在 i.MXRT 上使用各种方法去提升性能来解决这个高波特率无法识别问题:

一、SBL项目里ISP串口高波特率识别问题

SBL 项目是支持全系列 i.MXRT 平台的,为了具体化问题,我们就选取 i.MXRT1062 型号为例,官方配套 MIMXRT1060-EVK 板子上搭配了一颗四线串行 NOR Flash(芯成IS25WP064A)用于存放代码。

SBL 程序主体是 XIP 执行的,仅部分涉及 IAP 操作的代码被分散加载到了 RAM 里。SBL 中 ISP 功能代码主体当然也是 XIP 为主,且在 SBL 程序里是最先执行的(本地升级超时后才进入 SBL 主体),SBL 工程里跟串口波特率识别相关的源文件一共如下三个:

microseconds_pit.c                 -- 存放 PIT 计时函数
autobaud_irq.c                     -- 存放 GPIO 中断回调、波特率识别计算函数
pinmux_utility_imxrt_series.c      -- 存放 GPIO 配置与中断处理函数

MIMXRT1060-EVK 板子上串口是 GPIO1[13:12],其中 RXD - GPIO1[13] 是核心的用于波特率识别的引脚,为了便于直观地感受代码执行性能,我们用另一个 GPIO1[12] 来辅助,将其配置为 GPIO 输出模式,初值为高电平,在 GPIO 中断处理函数里保持低电平来标示执行总时间:

  • Note :下述代码里中断处理函数实际上有点小缺陷,《中断处理函数(IRQHandler)的标准流程》 一文里给出了改进方法,但这里为了观察中断处理代码是否能在下一次中断来临前执行完毕特意舍弃了文中 2.2.2 小节里的改进)
voidGPIO1_Combined_0_15_IRQHandler(void)
{
//****辅助调试:进入中断时拉低 GPIO1[12],标志执行时间起点
GPIO1->DR&=(uint32_t)~(1U<< 12);

uint32_tinterrupt_flag=(1U<< 13);
//仅当GPIO1[13]下降沿中断发生时
if((GPIO_GetPinsInterruptFlags(GPIO1)&interrupt_flag)&&s_pin_irq_func)
{
//执行一次回调函数
s_pin_irq_func();
//清除GPIO1[13]中断标志
GPIO_ClearPinsInterruptFlags(GPIO1,interrupt_flag);
__DSB();
}

//****辅助调试:退出中断时拉高 GPIO1[12],标志执行时间结束
GPIO1->DR|=(1U<< 12);
}

现在我们用示波器同时抓取 GPIO1[13:12] 信号,分别测试 9600 低波特率(下图一)和 115200 高波特率(下图二)下实际波形,根据测量第一次 GPIO 中断处理执行时间大概是 32.8us(7 次中断因代码分支执行不同略有区别),这个时间对于 9600 波特率下单 bit 传输耗时约 104us 的情况来说是足够快的,但是对于 115200 波特率下单 bit 传输耗时约 8.68us 的情况来说就显得有点慢了(最小的下降沿之间间隔是 2bit 传输耗时 17.36us ),这也是 115200 无法被识别的原因,因为有 4 个下降沿中断被漏掉了。

8f7058c4-d8a3-11ec-ba43-dac502259ad0.png

8f9b973c-d8a3-11ec-ba43-dac502259ad0.png
  • Note: ISP 功能代码里配置的系统环境是:396MHz CPU 主频、不使能 L1 Cache、100MHz Flash 工作频率,普通 SPI 下 Fast Read Quad I/O SDR Non-Continuous 工作模式,并且使能了 FlexSPI 的 Prefetch 特性(AHB RX Buffer 为 1KB)。

二、提升代码性能的多种方法

既然代码执行性能不够,那就努力提升性能,文章标题叫十八般武艺,这只是一种夸张说法,不过痞子衡确实收集了如下六种提升性能的方法,让我们一一尝试吧,注意下述结果都是叠加前面方法而得的(所有测试均是在 115200 波特率下进行)。

Level 1:提升CPU主频

ISP 功能代码里配置的 CPU 主频是 396MHz,实际上这是根据 BootROM 默认运行配置而来的,而 i.MXRT1062 是可以跑到 600MHz 主频的,将 SDK 代码里 armPllConfig_BOARD_BootClockRUN.loopDivider 由 66 调大到 100 即可。

constclock_arm_pll_config_tarmPllConfig_BOARD_BootClockRUN={
.loopDivider=100,/*PLLloopdivider,Fout=Fin*50*/
.src=0,/*Bypassclocksource,0-OSC24M,1-CLK1_PandCLK1_N*/
};

voidBOARD_BootClockRUN(void)
{
//...

CLOCK_SetDiv(kCLOCK_AhbDiv,0);
CLOCK_SetDiv(kCLOCK_ArmDiv,1);
CLOCK_InitArmPll(&armPllConfig_BOARD_BootClockRUN);
CLOCK_SetMux(kCLOCK_PrePeriphMux,3);
CLOCK_SetMux(kCLOCK_PeriphMux,0);

//...
}

CPU 主频提升后第一次 GPIO 中断处理执行时间从 32.8us 下降到了 32.2us,性能仅有微小提升,看来此时主要性能瓶颈不在 CPU 主频上,应该是 Flash 访问性能在拖后腿。

8fb7324e-d8a3-11ec-ba43-dac502259ad0.png

Level 2:提升Flash访问速度

SBL 工程里启动头 FDCB 配置的是 100MHz Flash 工作频率,但 MIMXRT1060-EVK 板载 Flash(芯成IS25WP064A)最大工作频率是 133MHz,所以我们可以提升 Flash 工作频率。修改 qspiflash_config.memConfig.serialClkFreq 为 kFlexSpiSerialClk_133MHz 即可。不了解 FDCB 结构体工作机制的可以翻阅痞子衡旧文 《从头开始认识i.MXRT启动头FDCB里的lookupTable》

constflexspi_nor_config_tqspiflash_config={
.memConfig=
{
.tag=FLEXSPI_CFG_BLK_TAG,
.version=FLEXSPI_CFG_BLK_VERSION,
.readSampleClkSrc=kFlexSPIReadSampleClk_LoopbackFromDqsPad,
.csHoldTime=3u,
.csSetupTime=3u,
.sflashPadType=kSerialFlash_4Pads,
//.serialClkFreq=kFlexSpiSerialClk_100MHz,

.serialClkFreq=kFlexSpiSerialClk_133MHz,
.sflashA1Size=8u*1024u*1024u,
.lookupTable=
{
FLEXSPI_LUT_SEQ(CMD_SDR,FLEXSPI_1PAD,0xEB,RADDR_SDR,FLEXSPI_4PAD,0x18),
FLEXSPI_LUT_SEQ(DUMMY_SDR,FLEXSPI_4PAD,0x06,READ_SDR,FLEXSPI_4PAD,0x04),
},
},
.pageSize=256u,
.sectorSize=4u*1024u,
.blockSize=64u*1024u,
.isUniformBlockSize=false,
};

Flash 工作频率提升后第一次 GPIO 中断处理执行时间从 32.2us 下降到了 27.8us,这次的性能提升算有点明显了,但是还是不够,解决不了问题。

8fda8bf4-d8a3-11ec-ba43-dac502259ad0.png

Level 3:配置FlexSPI至最优模式

让我们继续从 Flash 传输模式上做文章,ISP 功能代码里配置的是普通 SPI 下 Fast Read Quad I/O SDR Non-Continuous 工作模式,这个模式已经算是非常高效的传输模式了,如果还想改进,要么是切换到 QPI 模式(将 CMD 子序列也从一线变到四线)要么是使能 Continuous Read(除了第一个 CMD 子序列,其后 CMD 子序列全部省掉),综合考虑应该是使能 Continuous Read 性能提升更大一些,具体方法参考 《在i.MXRT启动头FDCB里使能串行NOR Flash的Continuous read模式》

constflexspi_nor_config_tqspiflash_config={
.memConfig=
{
//...
.lookupTable=
{
FLEXSPI_LUT_SEQ(CMD_SDR,FLEXSPI_1PAD,0xEB,RADDR_SDR,FLEXSPI_4PAD,0x18),
//FLEXSPI_LUT_SEQ(DUMMY_SDR,FLEXSPI_4PAD,0x06,READ_SDR,FLEXSPI_4PAD,0x04),

//插入JUMP_ON_CS子序列
FLEXSPI_LUT_SEQ(MODE8_SDR,FLEXSPI_4PAD,0xA0,DUMMY_SDR,FLEXSPI_4PAD,0x04),
FLEXSPI_LUT_SEQ(READ_SDR,FLEXSPI_4PAD,0x04,JMP_ON_CS,FLEXSPI_1PAD,0x01),
},
},
//...
};

使能 Flash Continuous Read 后第一次 GPIO 中断处理执行时间从 27.8us 下降到了 27.4us,性能仅有微小提升,这应该跟我们使能了 FlexSPI prefetch 特性有关,1KB AHB RX Buffer 的存在导致 CMD 子序列在总传输时序中占比不明显。不过有点收获的是漏掉的下降沿中断从 4 个减少到了 3 个。

8ff4c910-d8a3-11ec-ba43-dac502259ad0.png

Level 4:打开L1 Cache

对于 XIP 工程来说,不开 L1 I-Cache 加速性能是非常吃亏的一件事,i.MXRT1062 内部有 32KB I-Cache,不把这个 Cache 用起来简直是暴殄天物。虽然工程 SystemInit() 函数里会执行一次 SCB_EnableICache(),但这只是一个 Cache 总开关,要想 Cache 对 Flash 映射地址(0x60000000 之后)产生作用还得借助 BOARD_ConfigMPU() 函数来具体配置 MPU。关于 Cache 对 Flash 读取的性能提升见 《实抓Flash信号波形来看i.MXRT的FlexSPI外设下AHB读访问情形(全加速)》

intmain(void)
{
//将MPU配置提到ISP代码之前
BOARD_ConfigMPU();

#if(defined(COMPONENT_MCU_ISP))
boolisInfiniteIsp=false;
isp_boot_main(isInfiniteIsp);
#endif

//BOARD_ConfigMPU();
//...
}

使能 Cache 后第一次 GPIO 中断处理执行时间从 27.4us 下降到了 19us,后面的 GPIO 中断执行耗时更是大大缩短(原因是中断处理函数相关代码在第一次中断触发执行时被顺便放到 Cache 里了),这时候 115200 高波特率已经能够被正常识别了。

901ca9c6-d8a3-11ec-ba43-dac502259ad0.png

到这里问题已经解决了,但我们还没有榨干 MCU 最后一滴血,优化继续。上图波形里第一次 GPIO 中断处理执行时间相比其后面的 6 次中断执行耗时要明显长,这还是有风险的,比如再高的波特率 256000 还是无法正常识别(至少第一次识别会失败,后面上位机再重复发暗号做第二次识别就可以了)。为了让第一次 GPIO 中断处理时间也大大缩短,我们可以在系统初始化的时候故意调用一下这些中断处理相关函数,将这些代码事先装载到 I-Cache里。

voidautobaud_init(void)
{
s_transitionCount=0;
s_firstByteTotalTicks=0;
s_secondByteTotalTicks=0;
s_lastToggleTicks=0;
s_ticksBetweenFailure=microseconds_convert_to_ticks(kMaximumTimeBetweenFallingEdges);
enable_autobaud_pin_irq(pin_transition_callback);

//故意调用一下,让I-Cache事先将代码Cache住
GPIO1_Combined_0_15_IRQHandler();
pin_transition_callback();//即第一节代码中的s_pin_irq_func()
}

将中断处理函数相关代码预装载到 I-Cache 后第一次 GPIO 中断处理执行时间从 19us 锐降到了 2.12us,跟其他中断处理执行差不多的耗时,现在即使是 256000 高波特率也能一次识别成功。

902c3e9a-d8a3-11ec-ba43-dac502259ad0.png

90497afa-d8a3-11ec-ba43-dac502259ad0.png

Level 5:拷贝到TCM里

靠 Cache 这种无法精准控制的优化策略始终让我们无法放心,还是将中断处理相关代码直接放到 TCM 里更可靠,我们在工程链接文件(MIMXRT1062xxxxx_flexspi_nor.icf)里做如下修改将第一节里列出了三个源文件全部弄到 RAM 区里执行(对于 XIP 工程来说,RAM 区是 DTCM, 当然对于代码来说 ITCM 效率要更高,不过 DTCM 也够用了)。

initialize by copy {
  readwrite,
  /* Place in RAM flash and performance dependent functions */

  object microseconds_pit.o,
  object autobaud_irq.o,
  object pinmux_utility_imxrt_series.o,

  // ...
  section .textrw
};

do not initialize  { section .noinit };

将中断处理函数相关代码重定位到 DTCM 执行后第一次 GPIO 中断处理执行时间从 2.12us 再降到了 520ns,这下 1M 超高波特率也能被识别了。

90684d4a-d8a3-11ec-ba43-dac502259ad0.png

9089337a-d8a3-11ec-ba43-dac502259ad0.png

Level 6:指定函数地址以八字节对齐

性能提升结束了吗?痞子衡还有一招,参见 《链接函数到8字节对齐地址或可进一步提升i.MXRT1xxx内核执行性能》 一文,将中断处理相关函数全部链接到八字节对齐地址还可以再利用 Cortex-M7 内核指令双发射特性。我们查看下工程映射文件(sbl.map),三个相关函数仅有计时函数 microseconds_get_ticks() 被自动分配到了八字节对齐的地址,其他两个函数不是,所以还有提升空间。

Entry                       Address   Size  Type      Object
;----                       -------   ----  ----      ------

GPIO1_Combined_0_15_IRQHandler
                        0x2000'0b2f   0x3e  Code  Gb  pinmux_utility_imxrt_series.o [1]
pin_transition_callback 0x2000'0175   0x8e  Code  Gb  autobaud_irq.o [1]
microseconds_get_ticks  0x2000'08e9   0x22  Code  Gb  microseconds_pit.o [1]

将非八字节地址对齐的中断处理相关函数调整到八字节地址对齐后(具体方法这里就不展开介绍了),第一次 GPIO 中断处理执行时间从 520ns 降到了 480ns,这几乎是性能极限了。

90b3e4d0-d8a3-11ec-ba43-dac502259ad0.png

至此,在串口波特率识别实例里逐步展示i.MXRT上提升代码执行性能的十八般武艺痞子衡便介绍完毕了,掌声在哪里~~~

审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 波特率
    +关注

    关注

    2

    文章

    280

    浏览量

    33758
  • 代码
    +关注

    关注

    30

    文章

    4555

    浏览量

    66750

原文标题:提升MCU代码执行性能的十八般武艺

文章出处:【微信号:mcu168,微信公众号:硬件攻城狮】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    如何构筑身份安全防线,避免被黑客“登入”企业网络?

    当黑客最常用的攻击手段,从用尽十八般武艺、不可告人的“侵入”,变成凭借有效账户、大摇大摆的“登入”,你会不会觉得不可思议?
    的头像 发表于 03-28 13:44 160次阅读
    如何构筑身份安全防线,避免被黑客“登入”企业网络?

    I.MX RT1052代码执行在哪里?

    试用的开发板里有了I.MX RT1052 但是看I.MX RT1052有代码与数据的SPI Flash,还有SRAM,还有片的TCM 代码
    发表于 11-09 06:49

    静态代码块、构造代码块、构造函数及普通代码块的执行顺序

    在Java中,静态代码块、构造代码块、构造函数、普通代码块的执行顺序是一个笔试的考点,通过这篇文章希望大家能彻底了解它们之间的执行顺序。 1
    的头像 发表于 10-09 15:40 432次阅读
    静态<b class='flag-5'>代码</b>块、构造<b class='flag-5'>代码</b>块、构造函数及普通<b class='flag-5'>代码</b>块的<b class='flag-5'>执行</b>顺序

    NXP之i.MX RT系列单片机上电操作

    i.MXRT系列单片机主频大多数为600Mhz,有些单片机可以达到800Mhz,外设也很丰富,性价比很高。
    发表于 10-08 17:13 316次阅读
    NXP之i.MX RT系列单片机上电操作

    关于内部spi flash执行代码效率问题

    新唐很多mcu可以在spi flash直接寻址,执行代码,很多自带内部spi flash作为代码存储器,这样虽然可以降低成本,但代码在sp
    发表于 08-24 07:21

    拒绝“爆雷”!GaussDB(for MySQL) 新上线了这个功能

    摘要:智能把控大数据量查询,防患系统奔溃于未然 什么是最大读取行 一直以来,大数据量查询是数据库 DBA 们调优的重点,DBA 们通常十八般武艺轮番上阵以期提升大数据查询的性能:例如分库
    的头像 发表于 06-30 09:51 174次阅读
    拒绝“爆雷”!GaussDB(for MySQL) 新上线了这个功能

    i.MXRT1024 MCU是否有用于NXP WiFi的驱动程序和中间件?

    i.MXRT1024 MCU 是否有用于 NXP WiFi 的驱动程序和中间件? MCUXpressoSDK 包含对多个基于 NXP WiFi 的模块以及许多 i.MXRT 处理器和 EVK
    发表于 06-09 07:37

    深入i.MXRT1050系列ROM中串行NOR Flash启动初始化流程

    我们知道外部串行NOR Flash是接到i.MXRT的FlexSPI外设引脚上,有时串行NOR Flash启动也叫FlexSPI NOR启动。
    的头像 发表于 06-02 17:43 1011次阅读
    深入i.<b class='flag-5'>MXRT</b>1050系列ROM中串行NOR Flash启动初始化流程

    i.MXRT1166在调试期间禁用WFI节能是为什么?如何让内核保持唤醒状态以进行调试?

    i.MXRT1160/i.MXRT1170 系列找到类似的东西。在这些芯片执行 WFI 指令期间,如何让内核保持唤醒状态以进行调试?
    发表于 06-01 07:22

    MCUBootUtility v5.0发布,初步支持i.MXRT1180

    痞子衡维护的NXP-MCUBootUtility工具距离上一个大版本(v4.0.0)发布过去4个多月了,期间痞子衡也做过两个小版本更新,但不足以单独介绍。这一次痞子衡为大家带来了全新大版本v5.0.0,这次更新主要是想和大家特别聊聊恩智浦新一代 i.MXRT 旗舰 RT1180。
    的头像 发表于 05-23 09:44 916次阅读
    MCUBootUtility v5.0发布,初步支持i.<b class='flag-5'>MXRT</b>1180

    试问谁家MCU能有三重启动保障?

    i.MXRT 系列内部均没有非易失性存储器,所以需要配置一个外部存储器存放启动代码,这个外部存储器我们也叫做启动设备。i.MXRT 支持的启动设备类型非常多,并且因为启动设备受板级环境影响可能导致启动
    的头像 发表于 05-19 11:03 334次阅读
    试问谁家MCU能有三重启动保障?

    i.MXRT1021的连接顺序是否与我从通用ARM文档中读到的不同?

    (例如 RST、SWO、ISPEN、DETECT)?i.MXRT1021 的连接顺序是否与我从通用 ARM 文档中读到的不同?
    发表于 05-18 07:14

    如何在i.mxrt1176部件使用otfad?

    我们正在为 i.mxrt1176 开发定制构建板,我们从 github(版本 4.1.1)下载 nxp 启动实用程序 1) 我们尝试了不加密的下载,我们使用的地址是 3000a000,一切看起来都很
    发表于 05-09 10:43

    i.MXRT1064 PMIC_ON_REQ输出在断电时未停用是怎么回事?

    我基于 i.MXRT1064 cpu 并参考 MIMXRT1064EVK 板开发了我的定制板。我使用了与 MIMXRT1064EVK 板相同的电源硬件架构:我的板子电正确,cpu 启动正常,断电也
    发表于 05-09 07:57

    i.MXRT1024可以从A0配置8位SDRAM的地址引脚吗?

    i.MXRT1024 的评估板中,一个 16 位 SDRAM (IS42S16160J-6TLI) 连接到控制器。其中,地址线配置为 A0 到 A13。 我们正在
    发表于 04-28 08:58