跳转到内容

嵌入式底层开发手册

1. 开发概要

1.1. 嵌入式底层开发概要

嵌入式底层开发通常指 U-Boot、Kernel、Rootfs 的开发。

  • U-Boot:(Universal Boot Loader,通用引导加载程序)是主要用于嵌入式系统(Kernel,内核)的引导加载程序,支持 ARM、AVR32、x86、Nios 等多种计算机架构。它是遵循 GPL 许可证的开源项目。

  • Kernel: Linux 内核是使用 C 语言编写、符合 POSIX 标准的类 Unix 操作系统。它通过 CPU 资源的时分复用实现多进程管理与调度,并通过各类子模块实现内存、磁盘等资源的管理与调度。同时,通过编写各种外设的设备驱动并修改设备树,实现外设与 Linux 系统之间的交互。实际上,嵌入式内核开发的大部分工作是对外设资源进行添加、删除和修改,通常通过“设备驱动开发”和“设备树修改”来完成。

  • Rootfs:(Root file system,根文件系统)是内核启动时最先挂载的文件系统。文件系统是用于组织和管理存储设备上的数据与元数据的机制。该机制使用户与操作系统之间的交互更加方便。

1.2. EAI UbuntuSDK 概要

EAI UbuntuSDK 是 EASY-EAI 团队基于 Ubuntu 系统,面向嵌入式系统专门优化和适配的系统。与传统嵌入式 SDK 相比,具有以下特点:

  1. 支持快速启动: 普通应用从上电到可运行仅需 6~7 秒,也支持秒级快速启动功能。

  2. 继承优秀生态: 继承了传统 Ubuntu 系统“依赖库安装方便、软件生态强大”的特点,可快速部署 ROS2 和 Python 库。

  3. 文件系统可靠性高: 支持 RamDisk,可自动修复文件系统坏块,防止因磁盘异常或断电导致文件系统启动失败。

  4. 系统初始化可靠性高: 默认支持外部看门狗(1.6 秒),防止任何异常导致 U-Boot 或 Kernel 初始化失败。(用户应用程序通过调用芯片内置看门狗来保护应用程序。)

  5. 底层开发简单: 无需研究庞大的 Buildroot SDK 代码,只需修改内核和设备树。

  6. 底层开发效率高: 常规 SDK 完整编译仅需约 15 分钟,而传统 Buildroot SDK 需要约 90 分钟。(对比环境为同等性能 PC:CPU @ 4.2GHz,12 核 24 线程。)

  7. 支持 Rootfs 一键导出: 用户在开发板上安装的库和开发的应用,可通过以太网一键导出,便于生成量产镜像。

  8. 支持软件库在线升级: 只需执行 sudo apt-get upgrade,即可从专用服务器(os.easy-eai.com)同步并更新与底层高度相关的依赖库(如 rkaiq、rga、gstreamer 等),及时修复芯片厂商的 Bug。

  9. 应用开发效率高: 支持开发板本地编译和在线交叉编译。在线交叉编译时,应用依赖库直接依赖开发板文件系统,既可避免库兼容性问题,又可利用 PC 的编译性能和源码编辑器。

  10. 支持 OTA 方案: 可避免由软件导致的量产事故,并实现应用和系统的迭代更新。

  11. 支持 A/B 分区方案: 系统可靠性非常高,但会占用较多存储空间。

  12. UI 交互方式选择灵活: 支持安装桌面系统和浏览器,用户可使用多种技术方式开发自己的 UI 界面。(默认未预装和运行,因此不会消耗系统资源。)

  13. 软件部署更加灵活: 支持 Docker 部署。

1.3. EAI UbuntuSDK 开发流程

固件开发流程概要:

EAI UbuntuSDK 开发流程 图1

EAI UbuntuSDK 开发流程 图1

  • 【uboot.img】 通常由 UbuntuSDK 源码编译生成,但 99% 的情况下无需修改或替换。

  • 【boot.img】 内核镜像。通常也由 UbuntuSDK 源码生成,但用户可以自由修改。

  • 【rootfs.img】 文件系统镜像。通常通过【从开发板完整导出】的方式获取,并用于替换固件中的 rootfs.img。

2. 获取源码

2.1. 获取源码

RV1126B 的 Ubuntu 系统 SDK 基于 Ubuntu 22.04 进行优化和移植,因此同样建议在 Ubuntu 22.04 环境(如 EASY-EAI 编译环境)中进行编译和开发。

2.1.1 准备开发环境

如果需要使用 EASY-EAI 编译环境,请阅读《入门指南/开发环境准备/Easy-Eai 编译环境的准备与更新》,并按照相关步骤搭建编译环境。

在 PC 端 Ubuntu 系统中运行 run 脚本,进入 EASY-EAI 编译环境。具体如下。

Terminal window
cd ~/develop_environment
./run.sh 2204

安装编译辅助工具:

Terminal window
sudo apt-get update && sudo apt-get install file

2.1.2 获取源码(Pull)

进入 /opt 目录,并创建 rv1126b_sdk 目录:

Terminal window
mkdir /opt/rv1126b_sdk
cd /opt/rv1126b_sdk

2 获取源码(Pull) 图3

2 获取源码(Pull) 图3

https://dl.dragonwake.com/download/rv1126b/embedded/RV1126SDK/ubuntu.tar.gz 从上述链接下载 ubuntu.tar.gz 压缩包,并上传到虚拟机的 /opt/EASY-EAI-Toolkit 目录中解压。

2.2. 源码编译指南

进入源码目录后,可以看到以下内容(请参考原文档中的图片)。

2.2.1 查看配置文件

编译的第一步是预加载板卡配置。

1 查看配置文件 图5

1 查看配置文件 图5

查看 configs 目录时,可以看到与各种板卡【对应】的配置文件。

1 查看配置文件 图6

1 查看配置文件 图6

这里选择【easy-eai-nano-tb-gstreamer-2204-cfg.sh】。

Terminal window
./build.sh configs/easy-eai-nano-tb-gstreamer-2204-cfg.sh

2.2.2 编译说明

配置文件加载完成后,再次执行 build.sh,将显示可编译的各个模块。

2 编译说明 图7

2 编译说明 图7

  • all:编译整个固件。

  • uboot:仅编译并生成 U-Boot 镜像(uboot.img)。

  • kernel:仅编译并生成内核镜像(boot.img)。

  • rootfs:仅编译并生成 Ubuntu 文件系统镜像(rootfs.img)。

通常,首次编译选择 ./build.sh all。

Terminal window
./build.sh all

2 编译说明 图8

2 编译说明 图8

出现交互提示后,按【Enter 键】确认执行。随后脚本会自动从服务器获取【各镜像模块】的源码,并开始【镜像编译与构建】。

*注意: 编译 【内核】 时,会有一个检查磁盘文件时间戳的步骤。此时如果 /mnt 连接已断开,会因 NFS 无限超时而导致流程停止。解决方法是,先按 Ctrl+C 结束编译,然后使用 sudo umount -f /mnt 卸载该目录,再重新执行编译。

2.3. 目录简要说明

2.3.1 固件输出目录

编译完成后,固件会输出到【output】目录。

1 固件输出目录 图9

1 固件输出目录 图9

只需将固件中的对应文件替换为生成的 boot.img、MiniLoaderAll.bin、parameter.txt、rootfs.img、uboot.img 即可。

2.3.2 uboot

U-Boot 源码保存目录为 u-boot。

2 uboot 图10

2 uboot 图10

修改 U-Boot 后,如果需要重新编译 U-Boot,请不要返回上一级目录执行 ./build.sh uboot,而应在 u-boot 目录中执行以下命令:

Terminal window
./make.sh

2.3.3 kernel

内核发布源码保存目录为 kernel。

3 kernel 图11

3 kernel 图11

不建议直接在 kernel 目录中修改源码。因为重新编译整个固件时,为了确保【整个固件】的一致性,kernel 目录会被完全删除,并从已配置的远程服务器重新获取。

2.3.4 rootfs

Ubuntu 系统 rootfs.img 的制作稍微复杂,涉及 configs、hooks、overlay 三个目录。

4 rootfs 图12

4 rootfs 图12

3. kernel

3.1. 准备环境并获取源码

3.1.1 下载并放置烧写工具

链接:

将下载好的烧写工具放置到虚拟机中的任意位置(例如:~/rv1126b_sdk)。

然后进入工具保存目录(例如:~/rv1126b_sdk),并授予执行权限。

Terminal window
cd ~/rv1126b_sdk/
chmod 777 upgrade_tool

接着,将工具移动到虚拟机的 /usr/bin 目录。

Terminal window
sudo mv upgrade_tool /usr/bin

1 下载并放置烧写工具 图13

1 下载并放置烧写工具 图13

最后,重启终端后工具即可生效。(注:该工具只需在虚拟机环境中配置一次,后续操作无需重复执行。)

3.1.2 准备开发环境

在 PC 端 Ubuntu 系统中运行 run 脚本,进入 EASY-EAI 编译环境。具体如下。

Terminal window
cd ~/develop_environment
./run.sh

3.1.3 获取源码

3.1.4 创建内核开发目录

SDK 首次编译完成后,执行以下命令复制 kernel 仓库。

Terminal window
cp kernel -r kernel_dev

4 创建内核开发目录 图14

4 创建内核开发目录 图14

进入 kernel_dev 目录,并创建用于管理用户自定义代码的分支。

Terminal window
cd kernel_dev/

3.2. 内核使用指南

3.2.1 编译配置说明

返回 SDK 根目录,编辑板卡编译配置文件。

Terminal window
cd /opt/rv1126b_sdk/ubuntu
vim configs/easy-eai-nano-tb-cfg.sh

1 编译配置说明 图15

1 编译配置说明 图15

  • 内核驱动配置文件位于以下目录:/opt/rv1126b_sdk/ubuntu/kernel_dev/arch/arm64/configs/

  • **设备树文件(dts)**位于以下目录:/opt/rv1126b_sdk/ubuntu/kernel_dev/arch/arm64/boot/dts/rockchip/

3.2.2 编译内核

在 SDK 根目录(/opt/rv1126b_sdk/ubuntu/)执行以下命令,将内核编译目标目录从“kernel”切换为“kernel_dev”。

Terminal window
export KERNEL_DEVELOP=kernel_dev
  • 该环境变量仅在【当前环境】的【当前终端窗口】中有效。如果打开新的终端窗口,编译目标会恢复为默认的“kernel”目录。

  • 也可以将此命令添加到 .config 文件中使其始终生效,但不再需要时请记得将其注释掉。

再次执行 build kernel 命令。

Terminal window
./build.sh kernel

2 编译内核 图16

2 编译内核 图16

按【Enter 键】确认编译目标为内核源码。当尝试编译“kernel 目录以外”的内核源码时,会出现类似“要编译的内核目录是 /opt/xxx/xxx/kernel_dev 吗?”的提示。再次按【Enter 键】确认后,即会执行编译。

2 编译内核 图17

2 编译内核 图17

💡 注意:内核编译过程中会有一个检查磁盘文件时间戳的步骤。此时如果 /mnt 连接已断开,会因 NFS 无限超时而导致流程停止。**解决方法:**先按 Ctrl+C 强制结束编译,执行 sudo umount -f /mnt 卸载目录,然后重新编译。

3.2.3 编译 Wi-Fi 驱动

如果对内核的【驱动配置】或【设备树配置】进行了较大修改,内核驱动的符号表可能会发生变化。如果 rootfs 上加载的 Wi-Fi 驱动符号表未更新,可能会因符号冲突导致 Wi-Fi 驱动加载失败。**解决方法:**内核源码编译完成后,重新编译 Wi-Fi 驱动。

在 ubuntu 目录中执行以下命令,编译 Wi-Fi 驱动。

Terminal window
./hooks/hook-none-install-wifibt-db37.sh

💡 注意:该命令依赖 rootfs,因此执行前必须先执行 ./build.sh all 或 ./build.sh rootfs。

3 编译 Wi-Fi 驱动 图18

3 编译 Wi-Fi 驱动 图18

3 编译 Wi-Fi 驱动 图19

3 编译 Wi-Fi 驱动 图19

3.2.4 编译结果说明

编译完成后,output 目录中的以下三个文件会被更新。

4 编译结果说明 图20

4 编译结果说明 图20

  • boot.img:内核镜像文件(Rockchip 的 U-Boot 镜像称为 uboot.img)。

  • db37-ko.tar.gz: Wi-Fi、Bluetooth 驱动及相关配置(Wi-Fi 驱动编译比较特殊,需要单独生成)。

  • lib_modules.tar.gz:rootfs 阶段需要加载的驱动(例如 CONFIG 设置为 =m 的驱动等)。

※如果 dts 或驱动设置变更较多,导致驱动符号表发生变化,则需要将 lib_modules.tar.gz 和 db37-ko.tar.gz 解压并放置到 rootfs 中。

3.3. 更新内核

内核编译完成后,按快捷键【Ctrl+Shift+T】打开新的终端窗口。

更新内核 图21

更新内核 图21

进入刚才编译内核的输出目录。

Terminal window
cd ~/rv1126b_sdk/ubuntu/output

更新内核 图22

更新内核 图22

接下来,打开第三个终端窗口。

更新内核 图23

更新内核 图23

3.3.1 更新 db37-ko.tar.gz

回到【第二个窗口】,使用 adb 命令将 db37-ko.tar.gz 推送到开发板。

Terminal window
adb push db37-ko.tar.gz /userdata

1 更新 db37-ko.tar.gz 图24

1 更新 db37-ko.tar.gz 图24

进入【第三个窗口】,解压 db37-ko.tar.gz。

Terminal window
tar -xvf /userdata/db37-ko.tar.gz -C / && sync

1 更新 db37-ko.tar.gz 图25

1 更新 db37-ko.tar.gz 图25

3.3.2 更新 lib_modules.tar.gz

回到【第二个窗口】,使用 adb 命令将 lib_modules.tar.gz 推送到开发板。

Terminal window
adb push lib_modules.tar.gz /userdata

2 更新 lib_modules.tar.gz 图26

2 更新 lib_modules.tar.gz 图26

进入【第三个窗口】,将 lib_modules.tar.gz 解压到 /usr。

Terminal window
cd /userdata
tar -xvf lib_modules.tar.gz -C /usr && sync

2 更新 lib_modules.tar.gz 图27

2 更新 lib_modules.tar.gz 图27

3.3.3 更新 boot.img

在【开发板上(第三个窗口)】执行【重启到 loader 模式】命令。

Terminal window
reboot loader
  • 如果无法通过此方法进入 loader 模式,也可以使用硬件按键进入(参见《固件烧写与更新》==2.1-手动进入 Loader 模式==)。

回到【第二个窗口】,执行以下命令将 kernel.img 更新到开发板。

Terminal window
sudo upgrade_tool di -boot boot.img

3 更新 boot.img 图28

3 更新 boot.img 图28

如果显示“Download image ok.”,表示内核烧写成功。执行以下命令,或按【RST 按钮】重启设备。

Terminal window
sudo upgrade_tool rd

※如果显示错误,开发板可能处于 MASKROM 模式。此时需要使用百度网盘中的固件重新烧写 MiniLoaderall.bin。

3 更新 boot.img 图29

3 更新 boot.img 图29

除上述方法外,也可以使用【固件烧写工具】更新 boot.img。具体步骤是:复制编译生成的 boot.img,覆盖本地【固件文件夹】中的 boot.img,然后使用烧写工具烧录到开发板。

3.4. 修改内核配置

在 ubuntu 目录中执行 ./build.sh all 或 ./build.sh kernel 时,会在对应的内核目录(kernel 或 kernel_dev)中生成 .config 文件。

修改内核配置 图30

修改内核配置 图30

使用以下命令将平台环境变量指定为 arm64。

Terminal window
export ARCH=arm64
  • 请特别注意,打开新终端或重新进入编译环境时,该环境变量会丢失。

接着执行 make menuconfig,打开内核配置菜单。

Terminal window
make menuconfig

修改内核配置 图31

修改内核配置 图31

  • 这里的 menuconfig 仅用于“搜索”内核配置。如果不熟悉内核编译操作,不建议直接在 menuconfig 中修改配置。

按键盘上的【/】键,会显示搜索菜单,然后输入要查找的驱动配置(例如:CH343)。

修改内核配置 图32

修改内核配置 图32

按【Enter 键】选择后,会显示对应菜单。

修改内核配置 图33

修改内核配置 图33

  • 【Symbol】 是当前需要的驱动(例如:USB_SERIAL_CH343)。

  • 【Depends on】 表示该驱动依赖的前置驱动(例如:USB_SUPPORT、USB、USB_SERIAL)。

修改内核配置 图34

修改内核配置 图34

为这些项目加上“CONFIG_”前缀,并直接追加到 rv1126b_eai.config 中,即可完成配置。

(至此,内核开发的完整流程说明已经结束。如果没有其他需要,后续内容无需继续阅读。)

3.5.==== 补充说明 ====

3.5.1 关于 menuconfig

如果直接使用 menuconfig 配置内核,则无法使用 ubuntu 目录中的 ./build.sh kernel 编译内核。需要直接进入内核源码目录,并手动编译。步骤如下:

Terminal window
cd kernel_dev # 进入内核源码目录
export ARCH=arm64 # 将平台设置为 arm64(注意:打开新终端后会被重置)
make rockchip_linux_defconfig rv1126b.config rv1126b_eai.config # 使用这些配置文件生成 .config
make savedefconfig # 将 .config 保存为 defconfig(备份)
cp defconfig mydefconfig # 复制 defconfig,防止丢失

在该状态下,可以执行 make menuconfig 来修改 .config。

Terminal window
make menuconfig

配置完成后,手动编译内核的命令如下:

Terminal window
make rv1126b-nano.img -j8 # 以 rv1126b-nano.dts 作为根设备树来编译内核驱动
# 编译 rootfs 上需要加载的驱动,并打包为 lib_modules.tar.gz
make modules -j8
make modules_install INSTALL_MOD_PATH=/tmp
cd /tmp/
tar czvf /tmp/lib_modules.tar.gz lib
cd -
# 将 lib_modules.tar.gz 移动到当前目录(即使不复制也没有问题)
mv /tmp/lib_modules.tar.gz ./
# 最后,将带有 emmc 分区修复功能的 ramdisk 打包到 boot.img 中
./mk-fitimage.sh boot.img boot4recovery.its arch/$ARCH/boot/Image
$DTB_PATH resource.img rootfs.cpio.gz

完成上述手动操作后,请参考本文 2.3 Wi-Fi 驱动的编译 来更新 db37-ko.tar.gz。

所有功能调试完成并需要【归档发布】时,执行 make savedefconfig,将当前 .config 导出为 defconfig。将其与之前的 mydefconfig 进行比较,并将差异手动追加到 rv1126b_eai.config 中保存。

4. 导出开发板 rootfs

4.1. 导出前准备

4.1.1 进入 EASY-EAI 编译环境

在 PC 端 Ubuntu 系统中运行 run 脚本,进入 EASY-EAI 编译环境。具体如下。

Terminal window
cd ~/develop_environment
./run.sh

注:如果不知道如何进入或使用 EASY-EAI 编译环境,请先阅读《入门指南/开发环境准备/Easy-Eai 编译环境的准备与更新》,并按照其步骤搭建编译环境。

4.1.2 挂载开发板

接下来,通过串口调试确认开发板 IP 地址为 192.168.3.121(挂载参数需要根据实际环境修改),并将开发板挂载到 /mnt 目录。如下所示。

Terminal window
sudo mount -t nfs -o nolock 192.168.3.121:/ /mnt

2 挂载开发板 图35

2 挂载开发板 图35

4.2. 导出操作

在 EASY-EAI 编译环境的【任意位置】(以 /opt/rv1126b_sdk 为例)直接执行以下导出命令,即可将开发板的文件系统导出为 rootfs.img。

Terminal window
export_rootfs

导出操作 图36

导出操作 图36

  • ※该操作所需等待时间约为 2GB 1 分钟左右。

会显示导出成功的提示。

导出操作 图37

导出操作 图37

请确认生成的 rootfs.img。默认会导出到【执行命令的目录】。

导出操作 图38

导出操作 图38

最后,用该 rootfs.img 替换【标准固件】中的 rootfs.img,即可用于量产固件烧写。

4.3. 导出说明

/export_rootfs 导出脚本也可以通过传入参数来指定 rootfs.img 的输出目录。如下所示。

导出说明 图39

导出说明 图39

此时,rootfs.img 会被导出到 /opt/work/z_test/ 目录中。

4.3.1 注意事项

  • 作为参数传入的目录必须事先【存在】。

  • 导出目标目录必须具有【写入权限】。

  • 导出目标磁盘的剩余空间必须大于新的 rootfs.img 所需的大小。

  • 导出执行过程中,不得断开 /mnt 的挂载。