1. 项目概述与核心需求解析
最近在折腾一块搭载全志D1s芯片的开发板,想在上面跑RT-Smart实时操作系统。这个想法源于一个具体的需求:我需要一个成本极低、功耗友好,但又能稳定运行实时任务的小型嵌入式平台,用于一个数据采集和简单控制的边缘节点项目。D1s这颗RISC-V芯片,搭配RT-Smart这个国人主导的、对RISC-V支持不错的实时系统,看起来是个绝佳的组合。然而,从零开始搭建这套开发环境,远不是“下载-安装-编译”那么简单。网上能找到的教程要么过于简略,要么步骤陈旧,导致我在配置过程中踩了无数个坑。这篇笔记,就是把我从一片空白到成功点亮第一个RT-Smart应用的全过程,以及那些官方文档里不会写的“坑”和“技巧”,完整地记录下来。如果你也打算在D1s上玩转RT-Smart,希望这篇超过五千字的实操记录,能帮你省下至少两天的折腾时间。
简单来说,这个环境搭建的核心目标就三个:第一,在你的Ubuntu开发主机上,准备好全套的交叉编译工具链、RT-Smart源码以及D1s的BSP(板级支持包);第二,正确配置环境变量和编译选项,让它们能协同工作;第三,成功编译出能烧录到D1s开发板SD卡或SPI Flash中的固件,并让系统正常启动。听起来步骤清晰,但魔鬼全在细节里。接下来,我会按照实际操作的逻辑顺序,拆解每一个环节。
2. 环境准备:工具链与源码获取的“正确姿势”
搭建任何嵌入式开发环境,第一步永远是准备工具。对于D1s (RISC-V架构) 和 RT-Smart,我们需要三样东西:RISC-V架构的GCC交叉编译工具链、RT-Smart操作系统源码、以及针对D1s这块特定开发板的BSP。
2.1 交叉编译工具链的选择与安装
RISC-V的工具链有很多版本,官方的、平头哥的、各个Linux发行版仓库里的。这里第一个坑就来了:不要直接用apt-get install gcc-riscv64-unknown-elf这类包。虽然方便,但它们往往版本较旧,或者缺少RT-Smart编译所需的一些特定库支持(比如newlib的特定配置),极易在链接阶段报各种奇奇怪错的错误。
经过实测,最稳妥的方案是使用RT-Thread官方推荐或验证过的工具链。我使用的是从“RT-Thread/riscv-gnu-toolchain”仓库构建的,或者使用平头哥(T-Head)官方发布的工具链。这里以平头哥工具链为例,因为其对D1s(玄铁C906核心)有深度优化。
实操步骤:
- 访问平头哥开源社区或相关镜像站,下载适用于Linux的RISC-V 64位工具链压缩包,文件名通常类似
xuantie-900-gcc-linux-5.10.4-glibc-x86_64-V2.6.1.tar.gz。 - 将其解压到你喜欢的目录,我习惯放在
/opt下:sudo tar -xzf xuantie-900-gcc-*.tar.gz -C /opt - 解压后,工具链的路径大概是
/opt/xuantie-900-gcc-linux-5.10.4-glibc-x86_64-V2.6.1/bin。你需要将这个路径添加到系统的PATH环境变量中。# 编辑你的shell配置文件,比如 ~/.bashrc 或 ~/.zshrc echo 'export PATH=$PATH:/opt/xuantie-900-gcc-linux-5.10.4-glibc-x86_64-V2.6.1/bin' >> ~/.bashrc source ~/.bashrc - 验证安装:执行
riscv64-unknown-linux-gnu-gcc -v。如果能看到版本信息且前缀匹配,说明工具链已就位。
注意:这里的环境变量配置是全局生效的。如果你同时进行多个不同架构的嵌入式项目,担心冲突,可以在编译RT-Smart时,通过修改其
rtconfig.py文件中的EXEC_PATH来指定工具链绝对路径,这是更干净的做法。
2.2 获取RT-Smart源码与D1s BSP
RT-Smart是RT-Thread的操作系统分支,代码托管在Gitee上。这里不建议直接git clone主仓库,因为我们需要的是特定BSP。D1s的BSP是作为一个子模块存在于RT-Smart仓库中的。
最靠谱的获取方式:
- 使用
git命令克隆仓库,并递归下载子模块。这一步网络稳定性很重要,子模块可能包含Linux内核或U-Boot等大仓库。git clone --recursive https://gitee.com/rtthread/rt-thread.git cd rt-thread # 切换到稳定分支,例如 `smart` 或最新的LTS版本分支,master分支可能处于活跃开发状态 git checkout smart # 确保子模块更新 git submodule update --init --recursive - D1s的BSP位于
rt-thread/bsp/allwinner/d1s目录下。进入这个目录,就是我们后续所有操作的主战场。cd bsp/allwinner/d1s
踩坑点:如果网络不佳,git submodule update可能会失败。如果遇到,可以尝试单独进入bsp/allwinner/d1s目录,查看.gitmodules文件,手动克隆对应的子模块仓库到指定路径。另一个常见问题是权限,确保你对这些目录有读写权限,编译过程中会生成大量文件。
3. 系统配置与编译前的关键调整
拿到源码后,别急着输入make。RT-Smart的编译系统基于scons,并搭配一个rtconfig.h和rtconfig.py进行配置。我们需要根据D1s的硬件特性进行针对性调整。
3.1 配置工具链路径(rtconfig.py)
进入bsp/allwinner/d1s目录,找到rtconfig.py文件。这个文件定义了交叉编译工具链的前缀、路径等关键信息。
用编辑器打开它,找到类似下面的部分:
# 工具链定义 ARCH='risc-v' CPU='c906' CROSS_TOOL='gcc' if os.getenv('RTT_EXEC_PATH'): EXEC_PATH = os.getenv('RTT_EXEC_PATH') else: EXEC_PATH = r'/opt/xuantie-900-gcc-linux-5.10.4-glibc-x86_64-V2.6.1/bin'你需要将EXEC_PATH修改为你实际解压工具链的bin目录路径。如果你之前没有设置RTT_EXEC_PATH环境变量,那么修改这里的默认值就是最直接的方法。
3.2 内核与组件配置(menuconfig)
RT-Smart使用类似Linux内核的menuconfig进行功能裁剪。这是第二个容易踩坑的地方:默认配置可能不包含你需要的驱动,或者包含了一些对D1s不必要的组件。
在BSP根目录下执行:
scons --menuconfig这会启动一个图形化配置界面。这里有几个关键配置项需要关注:
- Hardware Drivers Config -> Allwinner D1 Series Peripheral Drivers:确保你开发板上的外设驱动被选中。例如,如果你需要通过UART0进行调试输出,那么
UART0驱动必须开启。如果使用SD卡,需要开启SD/MMC驱动。 - RT-Thread Components -> Device Drivers:确认
Using GPIO drivers、Using Serial drivers等基础驱动框架已开启。 - RT-Thread Components -> POSIX layer and C standard library:如果你希望应用能使用标准的C库函数(如
printf、open等),需要开启Enable POSIX layer和对应的libc(如newlib)。注意:工具链必须配套对应的libc实现。 - Board Configuration -> Allwinner D1s:这里可以配置系统主频、内存大小等。D1s通常有64MB或128MB DDR,需要根据你的板子准确设置。
配置完成后,选择保存并退出。配置会被保存到.config文件,并最终影响rtconfig.h。
实操心得:对于初次尝试,建议在默认配置基础上,只开启最必要的驱动(如一个UART)和基础组件,先追求系统能跑起来。功能丰满可以后续慢慢加。一次开启太多未知驱动,可能会引入编译依赖或内存布局冲突问题。
4. 编译、链接与固件生成全流程
配置妥当后,就可以开始编译了。但编译过程本身也可能遇到问题。
4.1 执行编译命令
在BSP根目录下,执行:
scons -j$(nproc)-j参数用于指定并行编译的作业数,$(nproc)会自动获取你CPU的核心数,加快编译速度。
编译过程可能遇到的坑:
- 头文件找不到:错误信息通常包含
fatal error: xxx.h: No such file or directory。这往往是因为:- 在
menuconfig中开启了某个功能,但对应的源码路径或头文件路径未正确包含。需要检查sconscript文件或组件的SConscript。 - 工具链的
sysroot路径有问题。平头哥工具链通常没问题,但如果使用其他工具链,可能需要通过rtconfig.py中的CFLAGS或LDFLAGS手动添加-I和-L参数指定库和头文件路径。
- 在
- 链接阶段失败:出现
undefined reference to xxx。这是最棘手的问题之一。可能原因:- 库缺失:某个功能需要链接特定的库(如
-lm数学库),但在scons的链接脚本中未添加。需要修改rtconfig.py中的LINKFLAGS。 - 编译选项不匹配:特别是浮点运算相关选项。D1s的C906核心支持单精度浮点,如果工具链默认是软浮点(
soft-float),而内核或驱动编译时用了硬浮点(hard-float)选项,就会链接失败。必须确保工具链、RT-Smart内核、用户应用三者的浮点ABI一致。在rtconfig.py中检查CFLAGS是否包含-march=rv64imafdcvxthead -mabi=lp64d这类参数(其中d代表双精度浮点ABI)。 - 启动文件不兼容:某些工具链的
crt0.o等启动文件可能与RT-Smart的链接脚本不兼容。如果遇到非常早期的链接错误,可以考虑使用RT-Smart BSP自带的启动文件(如果有的话),或者研究链接脚本(link.lds)。
- 库缺失:某个功能需要链接特定的库(如
4.2 理解输出文件:rtthread.elf 与 rtthread.bin
编译成功后,在BSP目录下会生成几个重要文件:
rtthread.elf:包含调试信息、符号表的可执行文件,用于调试。rtthread.bin:纯粹的二进制镜像文件,用于烧录到存储设备。rtthread.disk:有时会生成,这是一个包含了分区表、bootloader和rtthread.bin的完整磁盘镜像,可以直接写入SD卡。
对于D1s,我们通常需要将rtthread.bin(或.disk)烧录到SD卡或SPI NOR Flash中。D1s芯片上电后,内部的BROM会从SD卡0扇区或SPI Flash的特定地址加载引导程序。
4.3 制作可启动SD卡(以SD卡启动为例)
这是将编译成果部署到硬件的关键一步,步骤繁琐但必须精确。
- 准备一张SD卡(建议8GB或更小,FAT32格式兼容性最好),插入读卡器并连接到Ubuntu主机。
- 确定设备节点:使用
lsblk命令查看新增的块设备,例如/dev/sdb。请务必确认无误,否则可能格式化错误磁盘! - 使用
dd命令烧录:- 如果生成了
rtthread.disk,可以直接烧录整个镜像到SD卡:sudo dd if=rtthread.disk of=/dev/sdb bs=1M status=progress - 如果只有
rtthread.bin,情况更复杂一些。D1s的BROM要求SD卡第一个扇区(512字节)开始放置一个特殊的引导头,然后是真正的引导程序(如u-boot或opensbi+rtthread.bin的组合)。通常BSP里会提供打包脚本(例如mkimage.sh或spl_tool)来帮你生成符合要求的sunxi-spl.bin和u-boot.img。你需要: a. 根据BSP的README,找到如何生成这些引导文件。 b. 使用fdisk或parted对SD卡分区,第一个分区通常为FAT32,用于存放内核和根文件系统。 c. 使用dd分别写入引导文件和rtthread.bin到精确的扇区偏移位置。# 示例,偏移量需根据具体BSP要求调整 sudo dd if=sunxi-spl.bin of=/dev/sdb bs=512 seek=16 sudo dd if=u-boot.img of=/dev/sdb bs=512 seek=80 sudo dd if=rtthread.bin of=/dev/sdb bs=512 seek=1024 # 假设内核从1024扇区开始
- 如果生成了
- 同步并弹出:执行
sync确保数据写入,然后安全移除SD卡。
重大注意事项:
dd命令非常危险,of=参数指定错误的目标磁盘会瞬间摧毁你硬盘上的数据。操作前反复确认/dev/sdb是你的SD卡。一个保险的做法是,先拔掉所有其他USB存储设备,只插SD卡读卡器,这样新增的设备就基本可以确定是它。
5. 上电调试与问题排查实录
将制作好的SD卡插入D1s开发板,连接串口调试线(通常是板上的UART0,TX、RX、GND三根线)到电脑的USB转TTL模块,波特率设置为115200。上电,期待在串口终端(如minicom、picocom或PuTTY)里看到RT-Smart的启动日志。
然而,现实往往是骨感的。下面是我遇到过的几种典型情况及其排查思路:
5.1 串口无任何输出
这是最令人沮丧的情况。排查需要按照信号流逐级进行:
- 硬件连接检查:
- 确认USB转TTL模块的TX接开发板的RX,RX接TX,GND接GND。
- 确认串口终端软件选择了正确的串口设备(如
/dev/ttyUSB0),波特率115200,数据位8,停止位1,无校验。 - 尝试按一下开发板的复位键。
- 供电检查:确认开发板供电正常,电源指示灯亮。
- SD卡检查:确认SD卡已插入到位。有些板子对SD卡兼容性敏感,换一张品牌SD卡试试。
- 引导程序问题:这是最常见的原因。BROM没有成功从SD卡加载引导程序。
- 验证SD卡镜像:将SD卡插回电脑,用
hexdump或dd命令读取前几个扇区,看看是否写入了预期的数据(如sunxi-spl.bin的魔数)。 - 检查烧录偏移:确认
dd命令的seek参数完全符合D1s BROM的要求。不同板子、不同版本的BSP可能有细微差别。 - 尝试SPI Flash启动:如果板载有SPI Flash,可以尝试通过全志的官方烧录工具
PhoenixSuit或Xboot,将镜像烧录到SPI Flash中,并设置启动拨码开关为SPI启动。这种方式往往更稳定。
- 验证SD卡镜像:将SD卡插回电脑,用
5.2 有输出但卡在特定位置
如果有日志输出,但停住了,问题就相对好定位。
- 卡在“bootloader”阶段:例如,只打印了
HELLO! BOOT0或U-Boot的少量信息就停了。这说明BROM和SPL(第一阶段引导)成功了,但U-Boot或OpenSBI加载失败。可能原因:u-boot.img损坏或版本不兼容。- SD卡上
u-boot.img存放的扇区位置不对。 - U-Boot的环境变量配置错误,无法找到并加载
rtthread.bin。检查U-Boot的启动脚本。
- 卡在“Jump to RT-Thread”或类似信息之后:这说明引导程序成功将控制权交给了RT-Smart内核,但内核自身初始化失败。
- 内存配置错误:在
menuconfig中设置的DDR大小与实际板载内存不符,导致内核访问了不存在的内存地址而崩溃。仔细核对板卡规格! - 设备树(DTS)或硬件初始化错误:内核在初始化某个驱动(如时钟、串口本身)时挂掉。可以尝试在
menuconfig中关闭所有非核心驱动,只保留最基础的CPU和串口驱动,看能否进入shell。如果能,再逐个开启驱动,定位问题驱动。 - 栈溢出:在启动早期,中断或函数调用栈溢出。可以尝试在
rtconfig.h中增大主线程或中断栈的大小。
- 内存配置错误:在
5.3 成功进入Shell但外设不工作
如果能见到RT-Thread的LOGO和msh />提示符,恭喜,系统核心已经跑起来了!但可能UART0不是作为调试串口,或者SD卡、GPIO等不工作。
- 检查驱动是否使能:在
msh下使用list_device命令,查看所有注册的设备。确认你需要的设备(如uart0、sd0)是否存在。 - 检查引脚复用:D1s的引脚功能是复用的。可能BSP默认的引脚配置与你的板子实际连接不符。需要查看开发板原理图,核对UART0、SD卡等关键外设使用的具体引脚编号(如
PE2,PE3),然后去BSP的drv_gpio.c或类似的引脚初始化文件中,确认配置是否正确。 - 使用命令测试:对于串口,可以尝试
echo hello > /dev/uart0,然后用另一个串口工具监听对应TX引脚。对于GPIO,可以使用pin命令家族来读写。
6. 进阶:应用开发与文件系统挂载
当基础系统稳定运行后,下一步就是让它“有用”起来,即运行我们自己的应用程序,并可能挂载一个文件系统来存储数据。
6.1 编译与运行用户应用程序
RT-Smart支持动态加载和静态链接两种应用模式。对于初期,静态链接更简单。
- 编写应用代码:在BSP目录下的
applications文件夹内,新建一个.c文件,例如my_app.c。编写一个简单的入口函数,例如:#include <stdio.h> int main(void) { printf("Hello, D1s RT-Smart!\n"); return 0; } - 修改SConscript:编辑
applications目录下的SConscript文件,将你的my_app.c添加到编译源文件列表中。 - 重新编译:在BSP根目录执行
scons。你的应用代码会被编译并链接到最终的rtthread.bin中。 - 自动启动:为了让应用开机自动运行,可以在
main.c的rt_application_init()函数中,创建并启动一个线程来调用你的main函数。
6.2 挂载SD卡为文件系统
如果应用需要读写文件,就需要挂载文件系统。RT-Smart支持多种文件系统,如FAT、LittleFS等。
- 确保SD卡驱动正常:如前所述,在
menuconfig中开启SD/MMC驱动,并确认在msh中能看到sd0设备。 - 开启文件系统支持:在
menuconfig中,找到RT-Thread Components -> Device virtual file system,开启它。然后在其子菜单下选择你想要的文件系统,例如Enable elm-chan fatfs。 - 格式化与挂载:重新编译烧录后,启动系统。
- 首先,使用
mkfs sd0命令在SD卡上创建文件系统(如FAT)。注意:这会清空SD卡数据! - 然后,创建一个目录作为挂载点,例如
mkdir /sdcard。 - 最后,挂载设备:
mount sd0 /sdcard。如果成功,就可以通过/sdcard路径访问SD卡了。
- 首先,使用
- 自动挂载:可以将挂载命令写入
/etc/fstab文件(如果支持)或在一个初始化脚本中执行,实现开机自动挂载。
这个过程同样可能遇到驱动不稳定、SD卡兼容性、文件系统损坏等问题,需要结合串口日志耐心调试。
搭建D1s的RT-Smart开发环境,是一个典型的嵌入式Linux式开发流程的微缩版:获取源码、配置工具链、裁剪系统、编译、烧录、调试。每一步的坑,都源于对细节的忽视和对硬件-软件协同工作理解的不深。我最深的体会是,嵌入式开发没有银弹,官方文档只是路标,真正的道路需要自己用调试器和串口日志一步步踩出来。当你第一次在串口终端看到熟悉的msh />提示符时,之前所有的折腾都值了。这个环境一旦搭稳,就是一片广阔的天地,你可以在此基础上开发智能家居节点、工业数据采集器、小型机器人控制器等等。希望这份详细的踩坑笔记,能成为你探索这片天地的一块坚实垫脚石。如果在操作中遇到新的问题,不妨多翻翻BSP目录下的README和代码,或者去RT-Thread社区搜索相关关键词,很多时候,你遇到的坑,早已有前辈填过了。