你们好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给你们介绍的是在串口波特率识别实例里逐步展现i.MXRT上提高代码执行性能的十八般武艺。html
恩智浦 MCU SE 团队近期一直在加班加点赶 SBL 项目(解决客户产品 OTA 需求),这个项目里集成了 ISP 本地升级(UART/USB)功能,其中 UART 口下载升级实现里加入了自动波特率识别支持,具体识别方法见 《串口(UART)自动波特率识别程序设计与实现(中断)》 一文,这一套 ISP 代码实际上是移植于 i.MXRT Flashloader(更早期的时候叫 KBOOT)。微信
ISP 代码放在 SBL 工程里会出现高波特率(好比115200)没法识别的问题,但在低波特率的状况下(好比9600,19200),ISP 代码是功能正常的,说明代码自己并不存在逻辑缺陷,但高波特率下就异常了,大几率是遇到了代码执行性能瓶颈。今天痞子衡就尝试在 i.MXRT 上使用各类方法去提高性能来解决这个高波特率没法识别问题:ide
SBL 项目是支持全系列 i.MXRT 平台的,为了具体化问题,咱们就选取 i.MXRT1062 型号为例,官方配套 MIMXRT1060-EVK 板子上搭配了一颗四线串行 NOR Flash(芯成IS25WP064A)用于存放代码。函数
SBL 程序主体是 XIP 执行的,仅部分涉及 IAP 操做的代码被分散加载到了 RAM 里。SBL 中 ISP 功能代码主体固然也是 XIP 为主,且在 SBL 程序里是最早执行的(本地升级超时后才进入 SBL 主体),SBL 工程里跟串口波特率识别相关的源文件一共以下三个:oop
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 小节里的改进)
void GPIO1_Combined_0_15_IRQHandler(void) { // ****辅助调试:进入中断时拉低 GPIO1[12],标志执行时间起点 GPIO1->DR &= (uint32_t)~(1U << 12); uint32_t interrupt_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 个降低沿中断被漏掉了。测试
- Note: ISP 功能代码里配置的系统环境是:396MHz CPU 主频、不使能 L1 Cache、100MHz Flash 工做频率,普通 SPI 下 Fast Read Quad I/O SDR Non-Continuous 工做模式,而且使能了 FlexSPI 的 Prefetch 特性(AHB RX Buffer 为 1KB)。
既然代码执行性能不够,那就努力提高性能,文章标题叫十八般武艺,这只是一种夸张说法,不过痞子衡确实收集了以下六种提高性能的方法,让咱们一一尝试吧,注意下述结果都是叠加前面方法而得的(全部测试均是在 115200 波特率下进行)。fetch
ISP 功能代码里配置的 CPU 主频是 396MHz,实际上这是根据 BootROM 默认运行配置而来的,而 i.MXRT1062 是能够跑到 600MHz 主频的,将 SDK 代码里 armPllConfig_BOARD_BootClockRUN.loopDivider 由 66 调大到 100 便可。flex
const clock_arm_pll_config_t armPllConfig_BOARD_BootClockRUN = { .loopDivider = 100, /* PLL loop divider, Fout = Fin * 50 */ .src = 0, /* Bypass clock source, 0 - OSC 24M, 1 - CLK1_P and CLK1_N */ }; void BOARD_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 访问性能在拖后腿。优化
SBL 工程里启动头 FDCB 配置的是 100MHz Flash 工做频率,但 MIMXRT1060-EVK 板载 Flash(芯成IS25WP064A)最大工做频率是 133MHz,因此咱们能够提高 Flash 工做频率。修改 qspiflash_config.memConfig.serialClkFreq 为 kFlexSpiSerialClk_133MHz 便可。不了解 FDCB 结构体工做机制的能够翻阅痞子衡旧文 《从头开始认识i.MXRT启动头FDCB里的lookupTable》 。
const flexspi_nor_config_t qspiflash_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,此次的性能提高算有点明显了,可是仍是不够,解决不了问题。
让咱们继续从 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模式》。
const flexspi_nor_config_t qspiflash_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 个。
对于 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读访问情形(全加速)》 。
int main(void) { // 将 MPU 配置提到 ISP 代码以前 BOARD_ConfigMPU(); #if (defined(COMPONENT_MCU_ISP)) bool isInfiniteIsp = false; isp_boot_main(isInfiniteIsp); #endif // BOARD_ConfigMPU(); // ... }
使能 Cache 后第一次 GPIO 中断处理执行时间从 27.4us 降低到了 19us,后面的 GPIO 中断执行耗时更是大大缩短(缘由是中断处理函数相关代码在第一次中断触发执行时被顺便放到 Cache 里了),这时候 115200 高波特率已经可以被正常识别了。
到这里问题已经解决了,但咱们尚未榨干 MCU 最后一滴血,优化继续。上图波形里第一次 GPIO 中断处理执行时间相比其后面的 6 次中断执行耗时要明显长,这仍是有风险的,好比再高的波特率 256000 仍是没法正常识别(至少第一次识别会失败,后面上位机再重复发暗号作第二次识别就能够了)。为了让第一次 GPIO 中断处理时间也大大缩短,咱们能够在系统初始化的时候故意调用一下这些中断处理相关函数,将这些代码事先装载到 I-Cache里。
void autobaud_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 高波特率也能一次识别成功。
靠 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 超高波特率也能被识别了。
性能提高结束了吗?痞子衡还有一招,参见 《连接函数到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,这几乎是性能极限了。
至此,在串口波特率识别实例里逐步展现i.MXRT上提高代码执行性能的十八般武艺痞子衡便介绍完毕了,掌声在哪里~~~
文章会同时发布到个人 博客园主页、CSDN主页、知乎主页、微信公众号 平台上。
微信搜索"痞子衡嵌入式"或者扫描下面二维码,就能够在手机上第一时间看了哦。