逐飞 RT1064 库 GCC (VSCode) 移植踩坑

Summary: 该文介绍了在 MacOS 下,使用 GCC 工具链在 VSCode 中利用 Cortex Debug 插件编译调试逐飞 RT1064 库的踩坑过程。(Linux 下类似,但是具体步骤本文并没有写)

本文所记录的是我整个过程的踩坑经历,关于项目和其使用说明请参考项目主页:hilookas/SeekFree_RT1064_Library_GCC_Porting | GitHub

从山外 K60 库开始,我开始了我的漫漫智能车之路。山外库只支持 IAR,但是可惜 IAR 并不支持 MacOS/Linux,逐飞 RT1064 库增加了对 Keil 的支持,但是 Keil 仍然只支持 Windows。我不甘心与在 Mac 上与嵌入式无缘,同时好在开源工具链 GCC 支持(几乎)所有平台,故有了这篇折腾文章。

单片机相关

现在的微控制单元(单片机)一般会有两个文档,一个是 Data Sheet ,负责介绍芯片电气相关的参数,另一个是 Reference Manual ,负责介绍硬件上一些功能如何使用,寄存器布局等。

初次之外厂商提供一些 Application Note,用于介绍特定的功能是如何使用,这个文档一般比较口语化,而且部分还提供中文翻译,推荐要实现一个功能的时候可以优先参考。

NXP 提供 SDK ,SDK 提供了许多样例供参考,和一些芯片的基础库,将寄存器封装起来,避免用户自己直接操作寄存器(太头疼)。

CMSIS 是一个 ARM 的标准,用于标准化来自不同厂商的 ARM 芯片差异。将芯片的 ARM 内核相关的寄存器封装起来。

CMSIS 还提供 CMSIS Pack,其中包含了不同厂商自己外设的驱动程序。

NXP 的 SDK 包含了 NXP 的 CMSIS Pack (SDK 的 SDK_2.9.1_MIMXRT1064xxxxA/devices/MIMXRT1064 里的内容其实就是 NXP 的 CMSIS Pack 的)

ref:

核心板启动基本流程

RT1064 芯片有内嵌的 BootROM (直接刻在芯片上的那种),其会根据启动引脚和内部保险丝的情况决定从哪个位置启动。同时 RT1064 芯片内部有一颗使用 FlexSPI2 的 Flash (即 RT1064 的 4 意义所在),没有特殊配置,芯片会从这个 Flash 启动。

BootROM 会从 Flash 的 IVT(Image Vector Table) 读取 BootROM 配置信息(如 PC 指针的值,和配置 SEMC SDRAM 的“脚本”)

ref:

芯片的寄存器

寄存器有两种,一种是处理器寄存器,ARM 核心使用,一种是外设寄存器,用于控制芯片中的外设的(如 UART)。后者被映射到处理器的内存空间中,可以被寻址,具体地址参见芯片手册。

ref:

统一文件编码为 UTF-8

逐飞的库文件编码是乱的,强迫症的我不能对此坐视不管。库里的绝大多数文件是使用 GB2312 编码,但是有两个文件是同时使用了 UTF-8 和 GB2312 编码,更加神奇的是,还有两个文件中参加了几个中文符号!这导致 iconv 的转换编码进程被卡住。这主要涉及到以下几个文件:

Libraries/seekfree_libraries/common/SEEKFREE_PRINTF.c
Libraries/seekfree_libraries/common/SEEKFREE_PRINTF.h
Libraries/nxp_libraries/middleware/usb/host/class/usb_host_cdc.c
Libraries/nxp_libraries/drives/fsl_semc.h
Libraries/nxp_libraries/utilities/debug_console/fsl_debug_console_conf.h

为了加快修改所有源代码文件编码为 UTF-8 的进程,我简单的写了一个脚本,供各位参考:

#!/bin/bash

# Convert GB2312 to UTF-8 (MacOS) and CRLF to LF
# from https://docs.moodle.org/310/en/Converting_files_to_UTF-8
# from https://gist.github.com/jappy/2012320
# change * to *.c *.h *.s to specify only code files
# for x; do
find . -type f -name "*" | while read x; do
  iconv -f GB2312 -t UTF-8 < "$x" | tr -d '\015' > "$x.tmp"
  mv "$x.tmp" "$x"
done

改了 deceive typo

Libraries/nxp_libraries/deceive 目录名错了,是 device

IAR 和 Keil 的配置也做了相应的调整

确定需要编译的文件

逐飞库里并不是所有的文件都需要编译的,如下文件不需要编译:

Libraries/nxp_libraries/CMSIS/Driver/DriverTemplates/Driver_MCI.c
Libraries/nxp_libraries/CMSIS/Driver/DriverTemplates/Driver_USBD.c
Libraries/nxp_libraries/CMSIS/Driver/DriverTemplates/Driver_SAI.c
Libraries/nxp_libraries/CMSIS/Driver/DriverTemplates/Driver_USART.c
Libraries/nxp_libraries/CMSIS/Driver/DriverTemplates/Driver_ETH_PHY.c
Libraries/nxp_libraries/CMSIS/Driver/DriverTemplates/Driver_CAN.c
Libraries/nxp_libraries/CMSIS/Driver/DriverTemplates/Driver_ETH_MAC.c
Libraries/nxp_libraries/CMSIS/Driver/DriverTemplates/Driver_USBH.c
Libraries/nxp_libraries/CMSIS/Driver/DriverTemplates/Driver_Flash.c
Libraries/nxp_libraries/CMSIS/Driver/DriverTemplates/Driver_SPI.c
Libraries/nxp_libraries/CMSIS/Driver/DriverTemplates/Driver_Storage.c
Libraries/nxp_libraries/CMSIS/Driver/DriverTemplates/Driver_I2C.c
Libraries/nxp_libraries/middleware/usb/templates/device/usb_device_ch9.c
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_printer.c
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_dfu.c
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_class.c
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_video.c
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_msc.c
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_cdc_rndis.c
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_ccid.c
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_audio.c
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_msc_ufi.c
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_cdc_acm.c
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_hid.c
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_phdc.c

Libraries/nxp_libraries/middleware/sdmmc/port/usdhc/freertos/fsl_sdmmc_host.c
Libraries/nxp_libraries/middleware/sdmmc/port/usdhc/freertos/fsl_sdmmc_event.c
Libraries/nxp_libraries/middleware/usb/dcd/usb_phydcd.c

如下头文件不必被包含在头文件搜索的索引里:

Libraries/nxp_libraries/middleware/fatfs/source/ffconf_template.h
Libraries/nxp_libraries/middleware/fatfs/template/ffconf.h
Libraries/nxp_libraries/middleware/usb/templates/config/usb_device_config.h
Libraries/nxp_libraries/middleware/usb/templates/config/usb_host_config.h
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_audio.h
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_cdc_acm.h
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_msc_ufi.h
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_ccid.h
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_cdc_rndis.h
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_hid.h
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_phdc.h
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_printer.h
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_dfu.h
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_video.h
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_class.h
Libraries/nxp_libraries/middleware/usb/templates/device/class/usb_device_msc.h
Libraries/nxp_libraries/middleware/usb/templates/device/usb_device_ch9.h

移植链接文件

链接文件负责将代码合适的摆放在芯片所需要的位置上(如下文提到的 IVT 必须放到指定位置上,芯片才能正常启动)

MIMXRT1064xxxxx_seekfree.ld 文件修改自 NXP SDK 的 SDK_2.9.1_MIMXRT1064xxxxA/devices/MIMXRT1064/gcc/MIMXRT1064xxxxx_flexspi_nor.ld

逐飞核心板所需要的内存布局可以参考 IAR 的链接文件 Project/IAR/icf/MIMXRT1064xxxxx_flexspi_nor.icf 和逐飞的 Libraries/doc/read me.txt 说明文档。

ref:

关于链接的加载地址(LMA)与运行地址(VMA)

一段代码的加载地址并不一定与运行地址相同,比如,在程序运行前,先从 ROM 里加载代码到 RAM 中,再在 RAM 中执行代码,速度会比直接从 ROM 中执行快很多。GCC Linker 默认情况下,加载地址为运行地址,但是其也支持自行使用 AT 执行指定加载地址。

ref:

加载地址设置这里有坑,设置了 AT 之后的代码好像并不会自动与前面的代码错开放置,需要自行计算排好。

Linker output.map 是一个很好的调试工具

ref:

移植启动文件

主要涉及到芯片上电启动后 Reset_Handler 和,在其中运行的 SystemInit

需要配置 DTCM 和 ITCM 的内存分配,以及复制全局变量等到对应的位置。

Libraries/nxp_libraries/startup/GCC-ARM/startup_MIMXRT1064.s 文件修改自 NXP SDK 的 SDK_2.9.1_MIMXRT1064xxxxA/devices/MIMXRT1064/gcc/startup_MIMXRT1064.S

同时还修改了 Libraries/nxp_libraries/device/system_MIMXRT1064.cLibraries/seekfree_libraries/common/common.h (代码放置位置的快捷宏)

ref:

解决 GCC 并不帮助我初始化全局变量的问题

IAR 提供了 __iar_program_start 函数,拷贝在 icf 链接文件中定义的需要拷贝的数据。GCC 提供的相同地位的函数 _start 好像不帮我拷贝那些 LMA 与 VMA 不同的块。

解决方案:在 startup_MIMXRT1064 手动把变量初始化了。

ref:

移植 CMake 文件

NXP SDK 提供的样例是是使用 CMake 编译的,逐飞的核心板由于其外置 RAM ,需要手动开启一些编译 Flag,具体可以参见 flags.cmakeLibraries/doc/read me.txt

CMakeLists.txt*.cmake*.sh 修改自 NXP SDK 的 SDK_2.9.1_MIMXRT1064xxxxA/boards/evkmimxrt1064/demo_apps/led_blinky/armgcc 下相关文件。

ref:

关于 GDB 直接使用不好使的问题

在使用 Cortex Debug 之前,我有尝试直接使用 GDB (通过 JLinkGDBServer)一段时间,但是 GDB 直接使用好像不是特别好使,continue monitor halt 很烦,并且使用 GDB 的自带寄存器显示功能 info registers 的显示好像是错的。Cortex Debug 用完后,感觉还不错,就没有再继续研究 GDB 了。

ref:

解决 JLink 调试器启动芯片后外置 RAM 访问出错的问题

现象是使用 Cortex Debug 插件启动芯片后,程序会第一次死在 Libraries/nxp_libraries/device/system_MIMXRT1064.c 的复制向量表(到外置 RAM)过程中,进入 HardFault 中断,使用 JMem 查看后发现对应位置(0x80000000 外置 RAM)没有数据(显示为 -)。在 Reset_Handler 中设置断点的情况下,芯片自动重启后会恢复正常(进入到 main 函数)。

芯片自带的 BootROM 负责根据内置 Flash 上的 IVT 区域的 DCD(Device Configuration Data) 配置信息配置 SEMC 与外置 RAM 的通信。猜测 JLink 启动芯片的时候并不会运行芯片自带的 BootROM ,故需要 JLinkScript 在每次启动芯片的时候,手动配置一下外置 RAM 相关的数据。

使用从 NXP SDK 中提取到的 evkmimxrt1064_sdram_init.jlinkscript (来自 SDK_2.9.1_MIMXRT1064xxxxA/boards/evkmimxrt1064/sdmmc_examples/sdcard_interrupt/evkmimxrt1064_sdram_init.jlinkscript),附加到 Cortex Debug JLink 启动选项(文件 launch.json)后,可以正常调试运行了。

ref:

关于 CMSIS-DAP

CMSIS-DAP 可以用 pyOCD 与其通讯。在 MacOS 下,运行 pyOCD 与 CMSIS-DAP 通讯是“免驱”的。OpenOCD 好像也可以与它通讯,但是没有继续尝试…另外根据 Cortex Debug 的文档,Homebrew 的 OpenOCD 版本比较老,推荐使用编译方式安装或者直接从官网下载

ref:

解决 Cortex Debug 调试界面,外设寄存器无法正常显示的问题

复制 RT1064 的 SVD 文件即可

MIMXRT1064.xml 即 SVD 文件来自 NXP SDK 的 SDK_2.9.1_MIMXRT1064xxxxA/devices/MIMXRT1064/MIMXRT1064.xml ,也是 NXP CMSIS-Pack 的 NXP.MIMXRT1064_DFP.13.0.0.pack/MIMXRT1064.xml

ref:

解决没有初始化无线的时候,无线中断处理函数内无法正常打断点的问题

rt,经过测试发现,如果没有调用初始化函数,Cortex Debug 反汇编根本无法找到中断处理函数,只有在初始化后才能找到。故认为是如果没有调用初始化函数,编译器自动优化掉了处理函数。

在使用断点功能的时候需要注意并不是所有位置打了断点都能生效的。

ref:

JLink 和核心板之间的连接

DIO CLK GND 是必须连接的,VCC 可以连接(这四根引脚即为核心板调试引脚最靠近 Type-C 口的那四个),RST 实测不连也可以 Reset (该功能不依赖该引脚)

如果有转换板的话,板子上其实有一个四线的口,用那个口即可

Cortex Debug 参考

解决 Mac 上 Chrome CPU 占用率高的问题

TL;DR: 如果硬盘快满了,删除硬盘上的一些文件,使其有一定空余空间

最近电脑风扇狂转,发现 Chrome CPU 占用率非常高,使用 Chrome 内任务管理器发现“浏览器”一项占用很高。

经过一次浏览器重置后,占用率有所下降,但是使用一段时间后,占用率又保持非常高的状态,尤其是在看视频的时候,这现象更为严重。

使用 Safari 后,有所缓解,但是由于缺少许多插件,使用起来比较难受,故又换回 Chrome。

经过一些搜索,删除硬盘上一些文件,让硬盘有一定空闲空间后,占用率下降了许多,问题解决。

Ref

解决 Proxmox VE 无法安装到 eMMC 上的问题

最近在折腾锐角云…

看到商家的介绍,8g 内存 64g 存储只要 200 多?!赶紧剁手下单,结果到手后才发现,内存和存储都是焊接到主板上的…不禁感叹,买的还是没有卖的精啊。

这个设备原装是两个存储设备,一个板载 64G 的 eMMC 另外一个是采用 mSATA 口的 SSD ,到手的时候这台机器只剩下板载的存储了,那个 SSD 已经不翼而飞了,为这设备再添购一个 SSD 实在是不划算,同时为了最大化利用这个硬件,我在这台设备上折腾了一下,尝试使用 PVE,结果安装的过程中提示 unable to get device for partition 1 on device /dev/mmcblk1

解决

经过一天的尝试,通过以下方式可以绕开官方的限制,在 eMMC 上安装 Proxmox VE 6.3:

⚠️警告:PVE 并未针对这种设备优化,eMMC 也并非针对这种使用设计。PVE 每天要往存储设备中写入一定量的日志信息,USE AT YOUR OWN RISK!

  1. 启动 PVE 安装程序,进入安装初始界面
  2. 启动后点击 Install Proxmox VE (Debug mode)
  3. 在第一次提示你可以输入命令的时候输入 Ctrl-D ,继续安装过程
  4. 在第二次提示你可以输入命令的时候输入 vi /usr/bin/proxinstall 编辑文件(或者使用其他文字编辑器如 nano)
  5. 输入 /unable to get device 定位到对应位置
  6. 你可以看到类似下方的内容:
    ...
        } elsif ($dev =~ m|^/dev/[^/]+/hd[a-z]$|) {
            return "${dev}$partnum";
        } elsif ($dev =~ m|^/dev/nvme\d+n\d+$|) {
            return "${dev}p$partnum";
        } else {
            die "unable to get device for partition $partnum on device $dev\n";
        }
    ...
    
    将其修改(添加)为:
    ...
        } elsif ($dev =~ m|^/dev/[^/]+/hd[a-z]$|) {
            return "${dev}$partnum";
        } elsif ($dev =~ m|^/dev/nvme\d+n\d+$|) {
            return "${dev}p$partnum";
        } elsif ($dev =~ m|^/dev/mmcblk\d+$|) {
            return "${dev}p$partnum";
        } else {
            die "unable to get device for partition $partnum on device $dev\n";
        }
    ...
    
  7. 然后输入 Ctrl-D ,继续安装过程
  8. 此时应该进入了正常的安装程序,硬盘选择的时候选择 /dev/mmcblk1 (没有 bootX 后缀)(建议关闭 swap)
  9. 最后安装完成后输入 Ctrl-D ,重启系统

另外,也可以使用官方提供的方式,先安装 Debian 再安装 PVE,只不过那样安装很慢,而且网卡和分区并没有提前配置好,需要自己手动配置。具体参见:Install Proxmox VE on Debian Buster – Proxmox VE

网上还有使用 bin 等类似 ghost 的方法直接 dd 进 eMMC,或者先安装到另外一个硬盘上再使用 DiskGenius 乾坤大挪移到 eMMC 等方法在此不再做过多叙述,可以参见下方文章:

原理

Install Proxmox VE (Debug mode) 提供了在安装过程中各个阶段执行脚本的能力。

修改的文件是为安装程序提供 MMC 设备检测支持。

没有直接修改 ISO 主要是由于,一是该文件在 pve-installer.squashfs 中,由安装程序在运行的时候加载,修改需要解包后重新打包,二是这样更透明,避免使用一个来源不是很明确的二进制文件。

安装过程中,配置信息那里使用了 Linux 的图形界面,类似于 Ubuntu 的使用,按下 Ctrl+Alt+F1/F2 为相应的日志信息,按下 Ctrl+Alt+F3 可以切换出命令行,按下 Ctrl+Alt+F4 可以切换回图形界面。

Ref

人,讨厌的不是强权,而是自己没有得到强权。

高考成绩下来了,与我而言可能并不能去自己非常想去的学校,于是抱怨社会,为什么大家都认同985/211,而不认同双非?

其实说自己在翻找去年的学校录取分数表时,尝试“捡漏”,这何尝不是一种歧视呢?

拿着重点学校的名字,哈,我多么强大,去看不起对方的时候,有曾想过自己也会成为“被看不起的”那一方呢?

所以啊,人,要学会遇到强者,能安然面对,面对弱者,能保持敬畏之心。

这也算是这次高考给我带来的一个深刻的教训吧。

人文社科类学科其实是蛮重要的,不仅仅体现在成绩,而且体现在人的文化修养上。

每次想到一些头疼的事情,总是想,如果多读一点书,是不是现在心态就会稍微好一点呢?

CDN是一种多播

之前在接触到多播这个概念的时候一直在想这么美好的东西为什么不在互联网上使用,后来想了想,现在一直在说的 CDN 不就是做到这一点了嘛,减少骨干网络上重复数据的传输,在近用户端复制成多份分发。

今天看到腾讯云的这个演讲,同时也印证了这个观点:腾讯云PCDN:从P2P到万物互联框架

另外,P2P真的是一个很有趣的东西,与人斗,其乐无穷~

永远不要对他人的不幸落井下石,因为你无法确保你自己不会遇到类似情况。

什么是 Web 2.0 ?

Web 本质就是一个传递信息的工具,Web 有着用于传递信息的方法 HTTP,Web 有着用于表示信息的方法 HTML,那 Web 2.0 到底特殊在哪里?

我的理解, Web 2.0 将如何传递信息以及如何表达信息与用户的使用做了隔离,也就是说,在Web 2.0 时代,用户没有必要去考虑如何做出一个美观的网页,如何将自己的信息一直放在网上,如何用flash嵌入自己的视频作品,他只需要去做的是写一段文字,或者上传一些视频,其余的内容由计算机城区去帮忙处理。

Web 2.0 使得互联网的分工进行了细化,界面的美观设计有专业的设计师去处理,信息的保存有专门的运维去处理,解决各种疑难问题有程序员去做等等。

Web 2.0 其实就是应用程序这个概念的互联网延伸。

Web 2.0 的特性导致为了完成上述事宜,以及某些决策人员恬不知耻的产品“护城墙”战略,导致了互联网的中心化,从互联网变成了互联树,让用户的信息成为了一些公司的摇钱树,以及让控制信息的传播成为了[censered]。

将程序与数据分开吧!去掉那些本来就没有必要存在的中心化节点吧!还网络一个自由、开放的世界吧!

#拥抱IPFS ~~hashtag…~~

什么是邮件列表(Mailing List)?

这是一个对知乎问题的回答,什么是邮件列表(mailing list)?

简单的来说就是一组接收者的集合,他可以表示为一个邮箱地址,你向这个地址发送邮件,这个地址会帮你转发给其他用户。本质上就是一对多发信。

相对于抄送、秘抄需要将每个收信者写到邮件头中,这个列表是可以动态变化的,新的接收者可以通过网页或者其他方式告诉邮件列表自己愿意接收向这个邮件列表发送的邮件,同理也可以退出。

请区别各大邮件商提供的服务,如QQ的群邮件以及所谓订阅列表,邮件列表是平台无关的,邮件列表服务商不一定必须与发送者接收者同在一个服务商下,而且邮件列表应该使用的是通用的邮箱地址标识符(如me@example.com),不应该用QQ号什么的作为标识。

邮件列表可以用来做消息订阅,也可以用来做社区。具体例子就是你收到的各种推广信息从一种程度上可以认为是一种邮件列表。另外国外的较大型开源社区一般使用邮件列表作为其沟通的工具。

邮件列表相对于bbs等网上论坛,发信较为麻烦,实时性不高(分钟级,相比于IM即时通讯的秒级),学习成本比较高,又因为其去中心化的特点(每个人都可以作为邮件列表),较难审查(不是不行,邮件列表不是审查安全的信息交流工具),在国内一直没有推广开来(国内的公共邮件列表服务商少的可怜啊)。

其实邮件列表还是蛮有价值的,他不绑定任何一个平台,这意味着,你只要有一个有一定信誉的邮件服务商账户,你可以向世界上任何地方任何服务商的人们构建一个群组。不像微信,你不可以跟QQ用户组群。而且正因为邮件发送有一定门槛,人们一般会倾向于使用正式文体,讨论文体的效率会稍微高那么一丢丢。

OpenWrt 使用自带的 Dnsmasq 屏蔽网站(设置解析)

有些时候,我们可能希望屏蔽一个网站(例如:屏蔽小米广告),或者为特定的网站设置一个解析(例如:自己网站发布前在本地进行测试)。OpenWrt 提供了一个比本地 Host 强大许多的解析工具 Dnsmasq ,相比于本地 Host,他支持通配,并且可以解析特殊类型的记录。

网上有教程,需要安装 adblock 什么的,但其实就是一行(准确的来说是三行)命令的事情(luci 上没有提供修改的位置,所以不能在网页上解决)。

# 设置 example.com 域名 a 类型记录为 192.168.0.1
uci add_list dhcp.@dnsmasq[0].address="/example.com/192.168.0.1"
# 屏蔽 ad.mi.com 域名的 a 类型解析
# uci add_list dhcp.@dnsmasq[0].address="/ad.mi.com/"
# 生效配置(写入到 /etc/config/dhcp )
uci commit dhcp
# 重启服务
service dnsmasq restart

Ref