一、概述
本次试用的板子是 Luckfox Lyra Zero W,主控为瑞芯微 RK3506B。
Luckfox Lyra Zero W 官方产品图(正面 45°):左侧 40Pin 排针,右侧 USB-C OTG / USB-C HOST / Micro SD / MIPI DSI 座RK3506B 配有 3 颗 Cortex-A7(1.2GHz)和 1 颗 Cortex-M0,板上有 512MB DDR3L、256MB SPI NAND,以及 2.4GHz WiFi 6 和 Bluetooth 5.2/BLE。这次主要做 SPI 小屏显示、无线扫描和 AMP 调试。文中的 panel、bootanim、cfont、wifi-connect 和 rfradar 是本次自编程序,不是官方固件自带工具;相关命令用于说明实现和测试过程。
二、开箱与硬件解析
2.1 开箱与接线须知


2.2 规格参数
2.3 接口资源一览



启动介质是板载 256MB SPI NAND,不是 SD 卡,固件直接烧进 NAND,选 defconfig 时要对应这一点(§3.2)。

2.4 40Pin 引脚资源
2.4.1 RM_IO 是什么
RK3506B 的 Matrix IO(RM_IO)是一组交叉开关,不是固定复用:98 个功能信号可以任意映射到 32 个接口(RM_IO0~RM_IO31)。这 32 路里有 4 个没引到排针(RM_IO15、RM_IO19、RM_IO20、RM_IO21),其余 28 路就是 40Pin 上全部可复用的脚。

2.4.2 完整 40Pin 接口分配表
图中蓝色为系统占用引脚,紫色为本次接线,橙色为预留功能,灰色为空闲 GPIO;π 标记表示与树莓派对应的物理脚位。分配时保留默认调试串口和 I2C,尽量让同类接口相邻。图中是基础配置和预留方案;后文 AMP 实测改用 pin7/11 作 UART4 TX/RX、pin13/15 作 UART3 TX/RX,M0 还将 UART4 映射到 pin33/37。这些脚不能再按图中的空闲或预留用途接入其他外设。
2.4.3 与树莓派 40Pin 的兼容性
本方案将 SPI0(pin19/21/23/24/26)、I2C2(pin3/5)和 UART0(pin8/10)安排在与树莓派对应的物理脚位上,但这不代表模块可直接插用:仍需核对供电、逻辑电平,以及屏幕的 DC、RESET、背光接线。pin2 和 pin4 是 VSYS,不是独立稳压的 5V 输出,不应当作通用 5V 电源或反向供电入口。
三、开发环境搭建与固件编译
本次使用 Luckfox_Lyra_SDK_250815,目标是编译出适用于板载 SPI NAND 的 update.img。SDK 和原理图可从官方资料页获取。
3.1 构建主机与依赖
SDK 官方在 Ubuntu 22.04 LTS x86_64 上开发和验证。本次实测环境为 Intel Xeon Platinum 8488C,2 vCPU、7.6 GiB 内存、96 GB 根分区,系统是 Ubuntu 26.04 LTS(内核 7.0.0-1006-aws)。
依赖先按官方 SDK 编译说明准备。本次安装命令如下:
sudo apt-get install -y build-essential git gnupg flex bison gperf \
squashfs-tools zip curl zlib1g-dev libncurses-dev libssl-dev \
libxml2-utils xsltproc unzip bc rsync device-tree-compiler lz4 lzop \
ccache file wget cpio python3-pip u-boot-tools \
libgmp-dev libmpc-dev libelf-dev libdw-dev
这里遇到三个依赖问题:

Python 2 需要另外准备。下载并解压 Python 2.7.18 源码,进入源码目录后执行;gnu17 用于避开 gcc-15 默认 C23 对老代码的兼容问题:
export CFLAGS="-O2 -fcommon -std=gnu17 -Wno-implicit-function-declaration -Wno-error"
export OPT="$CFLAGS"
./configure --prefix=/usr/local --enable-unicode=ucs4 && make -j2 && sudo make install -j2
SDK 从官方资料页提供的网盘获取。解压后先将旧版 repo 1.x 替换为新版,避开 Python 3.12+ 的 No module named 'imp' 错误,再从本地 Git 对象离线检出源码:
mkdir -p ~/lyra-sdk
tar xzf Luckfox_Lyra_SDK_250815.tar.gz -C ~/lyra-sdk
cd ~/lyra-sdk
mv .repo/repo .repo/repo.py2bak
git clone --depth 1
https://gerrit.googlesource.com/git-repo .repo/repo
python3 .repo/repo/repo sync -l -j4
3.2 关键配置:defconfig 和编译器
板级配置。 Lyra SDK v1.4 通过子命令和 defconfig 选择板型,./build.sh -h 可查看选项:
available defconfigs:
luckfox_lyra_zero-w_buildroot_spinand_defconfig ← ★ 选这个
luckfox_lyra_zero-w_buildroot_sdmmc_defconfig
all / uboot / kernel / rootfs / firmware / updateimg / print-parts / cle***l
./build.sh luckfox_lyra_zero-w_buildroot_spinand_defconfig
这条命令只应用配置,不开始编译。配置写入 output/.config:
RK_CHIP="rk3506"
RK_KERNEL_CFG="rk3506_luckfox_defconfig"
RK_KERNEL_DTS_NAME="rk3506b-luckfox-lyra-zero-w"
RK_ROOTFS_SYSTEM="buildroot"
RK_ROOTFS_TYPE="ubi"
RK_PARAMETER="parameter-lyra-spinand.txt"
分区表可以用 ./build.sh print-parts 核对:uboot 4M、boot 12M,rootfs 吃满剩余空间,正好适配 256 MB SPI NAND。
主机编译器。 本次环境默认使用 gcc-15,部分老代码无法通过其默认 C23 标准的检查,因此改用 gcc-14:
sudo apt-get install -y gcc-14 g++-14
echo '__STDC_VERSION__' | gcc-14 -E -P - # 201710 ← C17
echo '__STDC_VERSION__' | gcc-15 -E -P - # 202311 ← C23
export HOSTCC=gcc-14 HOSTCXX=g++-14 CC=gcc-14 CXX=g++-14 # ★ 必须
两个典型症状:
# 症状 A:host-m4 1.4.19 的 gnulib
gl_oset.h:275:40: error: expected identifier or '(' before 'int'
# 症状 B:host-cmake 3.28.1 bootstrap
CMake Error at CMakeLists.txt:93 (message):
The C++ compiler does not support C++11 (e.g. std::unique_ptr).
HOST_CFLAGS="-std=gnu17" 可以解决 m4 的问题,但不适用于 cmake 的 C++ 编译。本次统一使用 gcc-14。
3.3 全量编译与产物
nohup ./build.sh all > ~/build-lyra.log 2>&1 &





产物清单(实测):


自编译update.img 的md5为 6ac1e443a7a9a374ec23460bb37eef6c
固件头为 FW Ver 8.1,Chip Tag 是 350F
四、系统烧录与首次启动
4.1 烧录
安装 DriverAssistant 驱动,然后再插板子
进烧录模式:按住 BOOT 键的同时插 USB 线,然后松开
打开 RKDevTool(右键以管理员身份运行),确认设备识别为LOADER
点Firmware,选择 update.img,等加载完成后点升级(Upgrade)
显示「下载成功」即完成


4.2 启动日志判读
本文用 adb 取日志,直接读内核 ring buffer:adb -s b57290249a9b3206 shell dmesg。正常启动应该看到:
[ 1.308750] rockchip-cpuinfo cpuinfo: SoC : 35060000
[ 1.418917] spi-nand spi2.0: Winbond SPI NAND was found.
[ 1.418985] spi-nand spi2.0: 256 MiB, block size: 128 KiB, page size: 2048, OOB size: 128
[ 1.420535] Creating 3 MTD partitions on "spi-nand0":
[ 1.420563] 0x000000400000-0x000000800000 : "uboot"
[ 1.423018] 0x000000800000-0x000001400000 : "boot"
[ 1.425408] 0x000002000000-0x00000ff60000 : "rootfs"
异常日志与含义:

启动停在 Waiting for root device 时,内核还没挂载根文件系统,无法执行用户态命令。串口输入有回显,不代表 shell 已启动。
4.3 首次启动后的系统信息
uname -a; cat /proc/cpuinfo; free -h; df -h; cat /proc/mtd
/proc/mtd 中 mtd1: 00c00000 表示 boot 分区为 12 MB,对应 parameter.txt 的 0x00006000@0x00004000(boot);本次 boot.img 为 6,372,352 字节,未超过分区容量。能否单独更新 boot 还取决于分区表和其他镜像是否发生变化。
五、ST7735S SPI 小屏移植
5.1 驱动选型

驱动已内建:drivers/staging/fbtft/fb_st7735r.c 第 177 行有 FBTFT_REGISTER_DRIVER(DRVNAME, "sitronix,st7735r", &display);,DT 里直接写 compatible = "sitronix,st7735r" 即可。
本次未测试 DSI 屏。设备树中 &dsi、&vop 和 &dsi_panel 默认启用,simple-panel-dsi 又没有 detect 回调,因此不接屏也会出现 800×1280 的 framebuffer。实际点亮还需要匹配的屏幕和 22PIN 排线。
5.2 硬件接线

接线使用 pin12、pin17、pin18、pin19、pin20、pin22、pin23、pin24,共 8 根线:

MISO(pin21)可以不接。 ST7735S 是只写显示,fbtft 也不需要读回。DT 里保留它是为了以后在同一路 SPI 上挂别的从设备,也方便做 SPI 回环自测。
5.3 内核配置与设备树
内核配置:追加到kernel-6.1/arch/arm/configs/rk3506_luckfox_defconfig:
CONFIG_FB_TFT=y
CONFIG_FB_TFT_ST7735R=y
前置依赖 SDK 里已经全部满足,不用改动(FB、SPI、SPI_ROCKCHIP、DRM、BACKLIGHT_CLASS_DEVICE、BACKLIGHT_PWM 都是 =y)。FB_TFT 会自动 select 掉 FB_SYS_FILLRECT / COPYAREA / IMAGEBLIT / FPOPS / DEFERRED_IO / BACKLIGHT。
编成 =y 而不是 =m:fbtft 作为模块要在 rootfs 里额外放 .ko,而开机 logo 要在 init 之前出图,内建最省事。
设备树:在板级文件 kernel-6.1/arch/arm/boot/dts/rk3506b-luckfox-lyra-zero-w.dts 中追加以下配置:
&spi0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&rm_io8_spi0_clk &rm_io6_spi0_mosi
&rm_io7_spi0_miso &rm_io10_spi0_csn0>;
st7735s: display@0 {
compatible = "sitronix,st7735r";
reg = <0>;
spi-max-frequency = <24000000>; /* 花屏就降到 15000000 */
pinctrl-names = "default";
pinctrl-0 = <&st7735s_gpios>; /* RES/RS 两个脚的 pinmux */
/* 复位脚必须 ACTIVE_LOW,理由见 §5.5 第 2 条 */
reset-gpios = <&gpio0 RK_PB4 GPIO_ACTIVE_LOW>; /* pin18 */
dc-gpios = <&gpio0 RK_PB3 GPIO_ACTIVE_HIGH>; /* pin22 */
rotate = <0>;
fps = <30>;
buswidth = <8>; /* 必填,缺了会 probe 失败 */
width = <128>;
height = <160>;
txbuflen = <16384>; /* 开写回缓冲,刷新率提升明显 */
debug = <0>;
};
};
/* RES/RS 两个脚的 pinmux:RK_FUNC_GPIO 就是普通 GPIO。
* 复位后 IOC 默认就是 GPIO,写出来是为了自文档化 + 防止被别的驱动抢走。
* 注意 mux 0 才是普通 GPIO;mux 7 表示"走 RM_IO 交叉开关"。 */
&pinctrl {
st7735s {
st7735s_gpios: st7735s-gpios {
rockchip,pins =
<0 RK_PB4 RK_FUNC_GPIO &pcfg_pull_none>, /* pin18 RES */
<0 RK_PB3 RK_FUNC_GPIO &pcfg_pull_none>; /* pin22 RS */
};
};
};
&pinctrl 下必须按“功能节点 + group”组织两层节点。若把 rockchip,pins 直接放在 st7735s 下,编译不会报错,但内核不会应用这组引脚配置。

背光使用 PWM 调节亮度:
&pwm0_4ch_0 {
status = "okay";
pinctrl-names = "active";
pinctrl-0 = <&rm_io14_pwm0_ch0>;
assigned-clocks = <&cru CLK_PWM0>;
assigned-clock-rates = <100000000>;
};
/ {
lcd_bl: lcd-backlight {
compatible = "pwm-backlight";
pwms = <&pwm0_4ch_0 0 25000 0>; /* 40kHz */
brightness-levels = <0 16 32 48 64 80 96 112
128 144 160 176 192 208 224 240 255>;
default-brightness-level = <12>; /* 索引,12 => 192/255 */
status = "okay";
};
};
&spi0 和 &pwm0_4ch_0 在 rk3502.dtsi 里默认都是 status = "disabled",打开不会冲突。板载 DSI 背光已占 &pwm0_4ch_2(映射在 RM_IO15,没引到排针),所以 LCD 背光改用 pwm0_4ch_0(pin12),不要和 ch2 抢。
Lyra SDK v1.4 没有 dtbo 加载框架(overlay/ 目录是空的,build.sh 只编译 RK_KERNEL_DTS 指定的那一个 dts),所以直接改板级 dts 是唯一稳妥的路。要做 A/B 切换,复制一份 dts、#include 原文件后追加这段,再把 RK_KERNEL_DTS 指过去。
5.4 编译打包与验证
cd ~/lyra-sdk
export HOSTCC=gcc-14 HOSTCXX=g++-14 CC=gcc-14 CXX=g++-14
./build.sh kernel && ./build.sh firmware && ./build.sh updateimg # 只改设备树,不用重编 buildroot
本次设备树增量构建约 2 分钟。对照对应的两版固件,只有 boot 内容变化,才采用了单独更新 boot.img 的方式;这不适用于后面会改变分区表的 AMP 固件。
5.5 关键踩坑速查(14 条)

两个屏会同时存在两个 fb:实测 SPI 小屏拿到 fb0(1.68s 完成 probe),DSI 拿到 fb1(2.64s 才绑上),和谁先 okay 无关,只和 probe 先后有关。所以别写死编号,遍历 /sys/class/graphics/fb* 的 virtual_size 找 128,160 才稳。
像素格式也不一样:fbtft 的 framebuffer 是 RGB565(16bpp),DSI 走 VOP 是 XRGB8888(32bpp,pitch 等于宽度乘 4)。复用旧代码前先确认 bpp 和 pitch。
5.6 状态面板程序 panel
状态面板 panel 用于持续显示系统信息,采用单文件 C 实现,无第三方依赖。
实现方式:

三个运行模式:

界面布局(以板端抓帧为底图添加标注,不是未经处理的原图):

这里的 8×8 是 panel 内嵌的字库,和 §7 的控制台字号不是一回事,它直接往 framebuffer 画点阵,不经过 fbcon,所以 cfont 改字号不影响 panel。
编译与产物(注意 rootfs 是 glibc,不是 uclibc):
CC=~/lyra-sdk/buildroot/output/rockchip_rk3506_luckfox/host/bin/arm-buildroot-linux-gnueabihf-gcc
$CC -O2 -Wall -Wextra -o panel panel.c

工具链别选错:Lyra 的 rootfs 是 glibc(有 /lib/ld-linux-armhf.so.3),要用 buildroot 生成的 arm-buildroot-linux-gnueabihf-gcc。prebuilts/ 里那个 gcc-arm-10.3-...-arm-none-linux-gnueabihf 是给内核用的裸工具链,编译能过但链接有坑。这一点和 RV1106 项目正好相反(那个是 uclibc),换平台先确认 libc。
先在 Linux 开发机上检查数据采集:
gcc -O2 -Wall -Wextra -o panel-host panel.c && ./panel-host -p
# cpu_temp : n/a ← 开发机没有 thermal zone,正确降级
# loadavg : 0.31
# memory : 1216 MB used / 7775 MB total (15%)
# ipv4 : 172.31.33.217
上板后若 -p 输出正常但画面异常,可优先排查显示部分。
5.7 上板验证结果
步骤 1:确认驱动加载
adb -s b57290249a9b3206 shell "dmesg | grep -i -E 'fbtft|st7735'"

[ 1.681254] fb_st7735r spi0.0: fbtft_property_value: width = 128
[ 1.681312] fb_st7735r spi0.0: fbtft_property_value: height = 160
[ 1.681340] fb_st7735r spi0.0: fbtft_property_value: buswidth = 8
[ 1.681494] fb_st7735r spi0.0: fbtft_property_value: fps = 30
[ 1.681524] fb_st7735r spi0.0: fbtft_property_value: txbuflen = 16384
[ 2.583993] graphics fb0: fb_st7735r frame buffer, 128x160, 40 KiB video memory, 16 KiB buffer memory, fps=30, spi0.0 at 24 MHz
步骤 2:确认 framebuffer 编号(不要猜)
adb -s b57290249a9b3206 shell "cat /proc/fb"
# 0 fb_st7735r
# 1 rockchipdrmfb

实测小屏拿到的是 fb0,但稳妥做法是按分辨率认屏,不认编号:
for f in /sys/class/graphics/fb*; do
[ "$(cat $f/virtual_size)" = "128,160" ] && echo "小屏 = $f"
done
步骤 3:背光
adb -s b57290249a9b3206 shell "ls /sys/class/backlight/"
# ff640000.dsi.0 lcd-backlight <- 两个设备,别搞混
adb -s b57290249a9b3206 shell "cat /sys/class/backlight/lcd-backlight/brightness" # 12
adb -s b57290249a9b3206 shell "cat /sys/class/backlight/lcd-backlight/max_brightness" # 16
lcd-backlight 的量程是 0~16(不是 0~255),默认值 12;ff640000.dsi.0 是 DSI 那一路的背光,和 SPI 小屏无关。
实测结果:


六、开机动画
6.1 需求与设计
console=tty1 将内核控制台绑定到 fb0,但 fbcon 只在有输出时重绘,启动期间可能出现黑屏。这里增加一段约 3 秒的动画,播放结束后恢复控制台。
128×160 的 RGB565 画面每帧占 40,960 字节,30fps 约需传输 1.2 MB/s,本次 SPI 时钟为 24MHz。
动画程序 bootanim 共 96 帧:前 15 帧淡入上滑,第 10~40 帧叠加微光,淡入结束后进度条逐步增加至 100%。帧间隔为 33ms,设计时长约 3.2 秒;进度条是动画效果,不表示系统启动任务的实际完成比例。
动画帧率设为 30fps,与 fbtft 刷新频率一致。每帧先在 canvas[160][128] 中合成,再一次写出。开机图单独存为 RGB565 文件,换图不需要重新编译。
6.2 调试记录

/* 坑 1:二次衰减,中心亮、边缘柔和收掉 */
if (d <= GLOW_H)
glow = GLOW_MAX * (GLOW_H - d) * (GLOW_H - d) / (GLOW_H * GLOW_H);
/* 坑 4:execvp 之前必须 flush,否则上面所有 printf 一条都不落盘 */
fflush(NULL);
execvp(exec_prog, eargv);
# 坑 2:动画播完把屏要回来(重设字体 = 触发 fbcon 全屏重绘)
bootanim -s /usr/share/bootanim/splash.rgb565
cfont 6x8
# 坑 3:控制权收拢到一个地方,不要都默认开
BOOTANIM=${BOOTANIM:-1}
PANEL=${PANEL:-0} # ★ 默认关,要当仪表盘再手动开
CONSOLE_FONT=${CONSOLE_FONT:-6x8}

bootanim -e <prog> 在动画结束后用 execvp() 启动目标程序。调用前要刷新 stdio 缓冲区,否则尚未写出的日志会丢失。
6.3 抓帧验证
adb shell "bootanim -1 -n -s /usr/share/bootanim/splash.rgb565" # 一次性播完只留最后一帧
adb exec-out "dd if=/dev/fb0 bs=40960 count=1 2>/dev/null" > fb0.raw # 把整屏 40,960 字节抓回来
这条二进制重定向命令应在 Bash 或 PowerShell 7.4+ 中执行。抓取结果是 128×160、每像素 2 字节的 RGB565 原始图像,转成 PNG 后即可检查。下方动画拼图和控制台图片均由板端抓帧得到。
末帧与源图的差异主要位于底部进度条和文字覆盖区。
6.4 产物与开关

命令行参数:-s <splash> 指定开机图,-d <fb> 指定 framebuffer(默认按 128x160 自动认屏),-e <prog> 播完 exec 目标程序,-1 只画最后一帧,-n 无延时,-q 安静模式。



七、控制台字体
7.1 方案选择
小屏是 128×160,内核默认字库为 8×8,算下来只有 16 列乘 20 行共 320 个字符,跑一次 dmesg 就滚没了。

cmdline 里的 fbcon=font:6x8 会被静默忽略:它依赖 CONFIG_FONTS,而 .config 里是 # CONFIG_FONTS is not set,内核连警告都不打。参数不生效时,先去 .config 确认它依赖的选项。
运行时加载字体不需要为此重新编译内核,本次采用这一方式。
7.2 提取与加载字库
KDFONTOP 是 fbcon 提供的 ioctl 接口,BusyBox 的 loadfont 可通过它加载 PSF2 字库。本次字模取自内核的 lib/fonts/font_*.c。
字库转换时先从 C 数组中提取字节,正则限定数据行:
# ★ 只匹配行首的 0x??,
# 否则会把 /* 0 0x00 '^@' */ 这类注释里的数字也抠进去
vals = [int(m, 16) for m in re.findall(r"^[ \t]*(0x[0-9a-fA-F]{2}),",
text, re.M)]
font_*.c 的字形注释也含有 0x00 等十六进制数字。提取时用 ^[ \t]* 限定数据行,避免把注释混入字模。
PSF2 头部共 32 字节:4 字节 magic(72 B5 4A 86),加上 7 个小端 32 位字段(version、headersize、flags、length、charsize、height、width)。自编的 /usr/bin/cfont 负责列出字库,并调用 busybox loadfont 加载。
busybox 只有 loadfont,没有 setfont,敲 setfont 会得到 setfont: applet not found。
7.3 四种字库实测对比
# cfont
当前:21列 x 20行 = 420 字符/屏
可用字体(在 /usr/share/consolefonts):
6x10 21列 x 16行 = 336 字符/屏
6x8 21列 x 20行 = 420 字符/屏
8x8 16列 x 20行 = 320 字符/屏
mini_4x6 32列 x 26行 = 832 字符/屏
字库 列×行 字符/屏 可读性 适用
8x8(内核默认) 16×20 320 最好 还原默认
6x10 21×16 336 好,但行数少 不推荐(行数没多,宽度白占)
6x8 21×20 420 好 默认选它
mini_4x6 32×26 832 勉强,笔画偏细 需要一屏看大量日志时
选 6x8 的理由:比默认的 8x8,宽度从 16 列增到 21 列(+31%),行数不变,容量从 320 涨到 420,字形清晰度损失很小。6x10 宽度也够,但行数掉到 16,总容量反而更小。
7.4 开机自动生效
改字体是运行时生效、掉电即失的,所以写进 init 脚本,每次开机设一遍。/etc/init.d/S15consolefont 内容如下:
CONSOLE_FONT="${CONSOLE_FONT:-6x8}"
[ -x /usr/bin/cfont ] && /usr/bin/cfont "$CONSOLE_FONT"
# 顺手关掉光标闪烁 —— 小屏上闪动的光标很干扰
if [ -w /sys/class/graphics/fbcon/cursor_blink ]; then
echo 0 > /sys/class/graphics/fbcon/cursor_blink
fi
S15consolefont 先设置字体,S20bootanim 随后播放动画。动画结束后再次加载字体,触发控制台重绘。
重启验证(stty 输出的顺序是「行 列」):
adb -s b57290249a9b3206 shell "stty -F /dev/tty1 size" # 20 21 -> 21列 x 20行,6x8 生效
adb -s b57290249a9b3206 shell "uptime" # 确认真的重启过
adb -s b57290249a9b3206 shell "ps | grep panel" # 确认没有 panel 在抢屏
切换字体的实际输出(cfont <字号>,不带参数则列出全部可用字体):
$ cfont 8x8
字体 8x8 -> 16列 x 20行 = 320 字符/屏
$ cfont 6x8
字体 6x8 -> 21列 x 20行 = 420 字符/屏
$ cfont mini_4x6
字体 mini_4x6 -> 32列 x 26行 = 832 字符/屏
$ cfont 6x8
字体 6x8 -> 21列 x 20行 = 420 字符/屏
同一段测试文本在两种字号下的屏上对比(抓 /dev/fb0):


上图使用 9 行、最长 21 字符的测试文本。也可以直接输出一行 21 字符的数字,观察两种字号下是否折行:
adb shell "cfont 8x8; echo 123456789012345678901 > /dev/tty1"
adb shell "cfont 6x8; echo 123456789012345678901 > /dev/tty1"
往 /dev/tty1 重定向这一步不能省。在 adb shell 里直接 cat 或 echo,输出只进 adb 自己的 pty,屏上不会有反应。
21 个字符在 8×8 字库下会折行,在 6×8 字库下恰好占一行。
八、WiFi 6:模块识别、射频通路与天线开关
本节检查 AIC8800DC 的连接方式、驱动和工具链,再测试 WiFi 扫描、天线切换和 AP 模式。
8.1 AIC8800DC 与 USB 连接
cat /sys/kernel/debug/usb/devices | grep -E "^T:|^P:|^S:|^I:"
T: Bus=01 Lev=00 Prnt=00 Port=00 Dev#=1 Spd=480 MxCh=1
S: Product=DWC OTG Controller <- SoC 自带的 USB2.0 OTG(dwc2)
T: Bus=01 Lev=01 Prnt=01 Port=00 Dev#=2 Spd=480 MxCh=4
P: Vendor=1a86 ProdID=8091 <- 一颗 4 口 USB HUB
T: Bus=01 Lev=02 Prnt=02 Port=00 Dev#=4 Spd=480 MxCh=0
P: Vendor=a69c ProdID=88dc Rev=1.00
S: Manufacturer=AICSemi
S: Product=AIC8800DC
I:* If#=0 Alt=0 #EPs=3 Cls=e0(wlcon) Driver=btusb <- 蓝牙
I:* If#=1 Alt=0 #EPs=2 Cls=e0(wlcon) Driver=btusb <- 蓝牙(6 个 Alt 设置)
I:* If#=2 Alt=0 #EPs=4 Cls=ff(vend.) Driver=aic8800_fdrv <- WiFi

WiFi 模组通过 USB 枚举。排查 WiFi 和蓝牙同时消失的问题时,也需要检查 USB 链路及模组供电。

本次启动后 hci0 已处于 UP RUNNING,BlueZ 的 bluetoothd 也在运行。hcitool lescan 报 Set scan parameters failed: Input/output error,改用 bluetoothctl --timeout 20 scan on 可以扫描,btmon 可记录 HCI 事件。这里没有进一步确认旧命令失败的底层原因。
8.2 驱动与固件:两颗模块 + 一份 full-MAC 固件
开机日志里能看到驱动分两步加载:aic_load_fw 先把固件灌进芯片,aic8800_fdrv 是主驱动,负责注册出 wlan0。
日志里那行 aic_load_fw: loading out-of-tree module taints kernel. 说明驱动不在内核树里,是厂商外挂的:内核被标记 tainted,换内核版本必须同步更新这颗驱动。
固件目录 /lib/firmware/aic8800DC/ 里的文件名都带 fmacfw 前缀(fmacfw_calib_8800dc_u02.bin 40284 字节、fmacfw_patch_8800dc_u02.bin 26984 字节等)。fmac 指 full-MAC,MAC 层跑在芯片里而不是主机上。好处是主机侧驱动比较薄,对 A7 的 CPU 占用更友好,而且能直接用标准的 cfg80211/nl80211,wpa_supplicant 和 hostapd 都能用;代价是灵活性差,想在固件层面改点什么基本没戏。
官方还提供了一个便利工具,可以直接把芯片认出来:wifibt-util.sh info 返回 Aicsemi AIC8800DC usb a69c:88dc aic8800_fdrv.ko。
8.3 驱动上报的能力
用 hostapd -dd 的启动日志可以把驱动上报的能力打出来,这比 iw 更详细,何况这块板子上并没有 iw:
nl80211: key_mgmt=0xd0f enc=0x10f auth=0x7 flags=0x10800001b7437bc0
max_stations=0 max_remain_on_chan=500 max_scan_ssids=3
nl80211: Regulatory information - country=00
nl80211: 2380-2520 @ 40 MHz 20 mBm
nl80211: 5140-5980 @ 80 MHz 20 mBm

max_scan_ssids=3 3 每次扫描请求最多带 3 个 SSID
max_scan_ssids=3 表示单次定向扫描请求最多携带 3 个 SSID;更多目标需由调用方分批提交。
country=00 表示没设监管域。上面那段 5140-5980 是驱动声称支持的,但模组本身是 2.4GHz 单频,实测只能扫到 2412~2472,5GHz 一个都没有。
8.4 用户态工具链现状
rootfs 是 buildroot 配 busybox 的精简系统,很多平时顺手的命令并不在 PATH 里:

wpa_cli / wpa_supplicant / hostapd v2.10 —
timeout /usr/bin/timeout —
wpa_cli 不是 busybox applet,写 busybox wpa_cli -i wlan0 scan 会报 wpa_cli: applet not found;顺手把 stderr 重定向掉的话,表面只是扫不到 AP,看不出是命令不存在。判断方法是 busybox --list | grep xxx。
8.5 官方脚本 wifi-connect.sh 的问题与重写
SDK 里的 /usr/bin/wifi-connect.sh 全文只有 9 行:读入 SSID 和密码,用 sed 替换进 /tmp/wpa_supplicant.conf,killall wpa_supplicant 后重新启动。它跑完会告诉你连上了,但实际接口 UP 却没有 IP:
busybox ifconfig wlan0
# wlan0 Link encap:Ethernet HWaddr 54:01:4A:4C:1A:93
# UP BROADCAST MULTICAST MTU:1500 Metric:1 <- 没有 inet addr

重写后的脚本命名为 wifi-connect,增加了以下处理:

用法是 wifi-connect <SSID> <密码> 连接,--status 看状态,--scan 扫描周边 AP,--off 断开;后三个都不需要密码。
$ wifi-connect --help
wifi-connect —— 连接 2.4G WiFi 并自动获取 IP
为什么不用 SDK 自带的 /usr/bin/wifi-connect.sh:
那个脚本只做三件事:sed 改配置 → 起 wpa_supplicant → 退出。
**它不跑 DHCP**,所以连上之后 `ifconfig wlan0` 是 UP 但没有 IP,ping 也不通。
本脚本补上了 DHCP、DNS、结果校验和失败诊断。
用法:
wifi-connect <密码> 连接并获取 IP
wifi-connect --status 查看当前连接状态
wifi-connect --scan 扫描周围 AP
wifi-connect --off 断开连接
wifi-connect --save 把当前配置固化到 /etc/wpa_supplicant.conf
注意: 密码里如果含空格或 shell 特殊字符,用单引号包起来。
连上之后 --status 的输出(2026-09-30 实测):
$ wifi-connect --status
=== wpa_supplicant 状态 ===
ssid=iQOO
freq=2437
key_mgmt=WPA2-PSK
wpa_state=COMPLETED <- 关联完成
ip_address=10.45.193.152
address=54:01:4a:4c:1a:93
=== wlan0 接口 ===
wlan0 Link encap:Ethernet HWaddr 54:01:4A:4C:1A:93
inet addr:10.45.193.152 Bcast:10.45.193.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
=== 路由 ===
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.45.193.4 0.0.0.0 UG 0 0 0 wlan0 <- 默认路由就位
10.45.193.0 0.0.0.0 255.255.255.0 U 0 0 0 wlan0
=== DNS ===
nameserver 10.45.193.4 # wlan0
--status 同时显示关联状态、IP、路由和 DNS。wpa_state=COMPLETED 只能确认关联成功,不能单独证明网络可用。
8.6 WiFi 扫描
固定位置、连续多次扫描,取稳定值(节选):

本次共记录到 17 个 BSS,其中信道 6 有 5 个。表中也有信道 5 和 9,并非全部集中在 1、6、11。最低记录到 −88 dBm,但仅凭扫描结果还不能评价接收灵敏度。
wifi-connect --scan 的实际输出(2026-09-30,当次结果):
$ wifi-connect --scan
扫描中(约 5 秒)...
SSID 信道 信号 flags
iQOO 2437 -49 [WPA2-PSK-CCMP][ESS]
ChinaNet-bK4z 2462 -63 [WPA-PSK-CCMP][WPA2-PSK-CCMP][ESS]
ChinaNet-27Z3 2462 -77 [WPA-PSK-CCMP][WPA2-PSK-CCMP][ESS]
ChinaNet-dDUP 2412 -79 [WPA-PSK-CCMP][WPA2-PSK-CCMP][ESS]
不同批次记录到 4~17 个 AP。上表和后面的命令输出来自不同时间,不能直接按数量比较接收性能。
8.7 GPIO1_C7 天线开关测试
原理图中的 WIFI_ANT_CTRL 连接到 GPIO1_C7,射频通路如下:

对应关系可以在原理图里用坐标验证:GPIO1_C7 和 WIFI_ANT_CTRL 在同一行,和上一行 GPIO1_C5 对 WIFI_PWRKEY 的写法完全一致。
检查 Linux GPIO 占用:
cat /sys/kernel/debug/gpio
# gpiochip0: GPIOs 0-31, parent: platform/ff940000.gpio, gpio0:
# gpio-11 ( |dc ) out hi
# gpio-12 ( |reset ) out hi ACTIVE LOW
# gpiochip1: GPIOs 32-63, parent: platform/ff870000.gpio, gpio1:
# gpio-32 ( |work-led ) out lo
# gpio-46 ( |cd ) in hi IRQ ACTIVE LOW
# gpio-53 ( |bt_default_poweron ) out hi
gpio-53 等于 GPIO1_C5,名为 bt_default_poweron,正好对上原理图里的 WIFI_PWRKEY,说明编号换算(bank 乘 32 加组乘 8 加序号)是对的;
gpio-55(GPIO1_C7)在表里根本不存在,没有任何驱动认领它。
在本次 SDK 的 rk3506*.dts* 中检索 wifi_ant、ant_ctrl 和 aic8800,未找到该开关的描述。结合 GPIO 占用结果,本次通过用户态控制 gpio55 进行对比。
实测:两个状态差 17 dB。 导出 gpio55,设成输出,在两态之间交替切换,每次都用 wpa_cli 扫描记录目标 AP 的 RSSI。
# 切换天线状态;每次切换后重新扫描并记录同一 AP 的 RSSI
echo 55 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio55/direction
echo 0 > /sys/class/gpio/gpio55/value # 状态 A
echo 1 > /sys/class/gpio/gpio55/value # 状态 B
交替 6 次(0,1,0,1,0,1)的结果如下:

结论:
本轮两个 AP 的 RSSI 差为 17~27 dB,切换方向一致;结合原理图,可确认 gpio55 控制射频通路。
上电时该脚为输入,实测电平为 0。在本次接线和测试位置下,状态 0 的 RSSI 较低。
蓝牙是否同样受影响,这一条没能复现,撤回。 早前一次非受控测量里,gpio55=0 只听到 4 个 BLE 设备、gpio55=1 听到 13 个,当时据此判断开关也影响蓝牙。后来停掉板上其它扫描进程、按 ABAB 交替重测四轮(0/1/0/1,每轮 20 秒),两态稳定都是 4 个设备,没有差异。所以蓝牙侧不下结论,只保留 WiFi 侧的结论。
BLE 设备数容易受广播时间、随机地址及其他扫描进程影响,不宜直接用来比较射频性能。后续 A/B 测试应停掉其他扫描者,比较同一固定设备的 RSSI。
RSSI 差值不能直接换算成实际通信距离;本次未做距离测试。
RF1 和 RF2 哪个是板载哪个是外接,软件判断不了,得物理上插拔一次外接 IPEX 天线:把 gpio55 固定为 1 后拔掉外接天线,RSSI 明显变差说明这一档走外接座,几乎不变说明走板载。这步留作后续。
wpa_cli scan_results 可能返回上一次扫描的缓存。scan 返回 OK 只表示请求被接受,应等待对应的 CTRL-EVENT-SCAN-RESULTS 完成事件后再读结果;遇到 FAIL-BUSY 则等待并重试,不能把“不再忙”当作扫描完成。
复测一遍(2026-09-30)。 同一块板、同一位置再跑一次 v3 脚本,这次 iQOO 的 RSSI 序列是 gpio55=0 时 −47 / −47 / −49,gpio55=1 时 −24 / −24 / −25,均值差 +23.3 dB:
$ sh /usr/bin/anttest3.sh
=============== v3 交替复测(6 次状态切换)===============
---------- [1/6] gpio55 = 0 ----------
蓝牙: hci0 存在 | BD Address: 54:01:4A:4C:1A:94
WiFi: 4 个 BSS
ChinaNet-dDUP|-79
ChinaNet-27Z3|-76
ChinaNet-bK4z|-63
iQOO|-47
---------- [2/6] gpio55 = 1 ----------
WiFi: 9 个 BSS
ChinaNet-SYzS|-81
ChinaNet-27Z3|-76
SUSE_Hostel|-62
ChinaNet-dDUP|-58
ChinaNet-bK4z|-47
iQOO|-24
...(第 3~6 次交替略,序列见下表)...
=============== 逐 AP 状态跟随性 ================
AP RSSI 序列(0/1 交替) avg@0 avg@1 Δ
iQOO -47 -24 -47 -24 -49 -25 -47.7 -24.3 +23.3
ChinaNet-bK4z -63 -47 -63 -47 -47 -47 -57.7 -47.0 +10.7
ChinaNet-27Z3 -76 -76 -76 -64 -76 -64 -76.0 -68.0 +8.0
ChinaNet-dDUP -79 -58 -58 -58 -58 -57 -65.0 -57.7 +7.3
SUSE_Hostel -62 -70 -75 -62 -70 -75 ... -68.8 -68.8 +0.1
ChinaNet-5xDh -80 -80 -83 -80.0 -81.5 -1.5
复测结果有两点需要保留:
不同 AP 对切换的响应不同:iQOO 差约 23 dB,ChinaNet-5xDh 基本不变。现有记录不足以解释差异来源。
三轮测试中,iQOO 的差值分别约为 +17、+23、+22.3 dB。第三轮停掉了其他扫描进程,RSSI 序列为 −43 / −22 / −47 / −25 / −49 / −25。各轮差值不完全相同,但状态 1 的 RSSI 均高于状态 0。

原理图还显示 WIFI_ANT_CTRL 通过 R67 200K 下拉到地,与上电读到低电平一致。
图片裁自官方 Lyra Zero W 原理图。
8.8 AP 模式
固件包含 hostapd v2.10。/etc/dn***asq.conf 已配置 AP 地址 10.201.126.1 和 DHCP 地址池 10.201.126.50~10.201.126.150。
本次另建 /tmp/hostapd-ap.conf,使用以下配置。先将口令占位符替换为自设的 8~63 位 ASCII 口令:
interface=wlan0
driver=nl80211
ssid=LyraZeroW-AP
hw_mode=g
channel=1
ieee80211n=1
wpa=2
wpa_key_mgmt=WPA-PSK
rsn_pairwise=CCMP
wpa_passphrase=REPLACE_WITH_YOUR_PASSWORD
本次测试中,先配 IP 再启动 hostapd 出现以下错误:
nl80211: kernel reports: Match already configured
nl80211: Could not configure driver mode
nl80211 driver initialization failed.
停止 wpa_supplicant、将接口置为 down 后再启动 hostapd,本次得以进入 AP 模式。以下操作会中断现有 WiFi 连接,应通过 USB ADB 或串口执行:
killall wpa_supplicant # 它占着 wlan0
busybox ifconfig wlan0 down # ★ 关键:先 down,不要先 up
hostapd -B /tmp/hostapd-ap.conf # hostapd 自己把接口切成 AP 并 up
busybox ifconfig wlan0 10.201.126.1 netmask 255.255.255.0 up
这次 nl80211 顺利切到 AP 模式(Set mode ifindex 3 iftype 3 (AP)、AP started: ch=0, bcmc_idx=32 channel=2412),接口状态也对了,注意是 RUNNING 而不是之前的 MULTICAST:
busybox ifconfig wlan0
# wlan0 Link encap:Ethernet HWaddr 54:01:4A:4C:1A:93
# inet addr:10.201.126.1 Bcast:10.201.126.255 Mask:255.255.255.0
# UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
/etc/init.d/S80dn***asq 的 start) 分支漏写了 $DAEMON,实际执行的是 --pid-file="$PIDFILE",因此启动失败。测试时 dn***asq 已由其他路径启动,不能用进程存在来证明这个脚本正常。
AP 模式下的接口状态就是上面那段 busybox ifconfig wlan0 的输出,关键看 UP BROADCAST RUNNING 和 inet addr:10.201.126.1 同时出现。对照 §8.5 里那个官方脚本的结果,那里只有 UP BROADCAST MULTICAST,没有 IP。
尚未补齐手机连接 AP 后的 DHCP 租约记录,不能仅凭接口地址确认客户端已成功联网。
8.9 无线扫描面板 rfradar
rfradar将 WiFi 和 BLE 扫描结果汇总到小屏和网页。小屏轮换显示信道评分与设备列表,浏览器提供完整列表和背光控制。


上面两张是抓 framebuffer 得到的干净图。下图是同一套程序在小屏上跑起来的实拍,屏幕通过 FPC 排线接在 40Pin 上:
信道评分根据 AP 的信道和 RSSI 估算,不是直接测量频谱能量。
程序使用一个进程:主线程处理 HTTP,扫描线程采集数据,小屏线程刷新显示,共享数据通过互斥锁保护。
扫描线程通过 wpa_cli 和 bluetoothctl 获取数据,主线程通过 HTTP 返回 JSON。两种工具已包含在 rootfs 中,使用 popen 不需要额外引入 libnl 或 D-Bus 库,也便于在终端复现扫描问题。
实测(2026-09-30,冷启动):

手机浏览器打开 http://<板子 IP>:8080 看到的就是下面这一页:
应用、资源和 init 脚本通过 Buildroot 的 rootfs overlay 打入固件,目录结构与板端路径一致。修改后需按 rootfs → firmware → updateimg 重建;仅用 ADB 推送的文件会在重烧 rootfs 后被覆盖。开机自启还遇到了两个问题:
AIC8800 是 USB 网卡,wlan0 要到 12.7 秒才出现(dmesg 里 aic8800_fdrv 注册得晚)。init 跑到 S50 时设备还不存在,所以脚本不能「不存在就退出」,得 fork 一个后台子进程等设备,而且要把子进程的 stdout 重定向掉,否则十几秒后它往控制台打字,会把小屏上刚画好的画面盖掉。
cfont 换字号会触发 fbcon 全屏重绘,把 rfradar 画好的内容整个盖掉。所以渲染程序必须有「与数据无关」的周期性强制重绘,不能只在数据变化时才画(FORCE_REDRAW_S 5)。这个坑和 §6.2 表格里第 2 条是同一个根因。
8.10 小结
2026-09-30 冷启动测试中,WiFi 在 21 秒内完成关联和 DHCP:IP 为 10.45.193.152,默认路由及 DNS 均指向 10.45.193.4。下载速度为 74 KB/s~1.21 MB/s,记录到 RSSI −48、NOISE −89、LINKSPEED 65 Mbps、带宽 20 MHz。仅凭这些数据尚不能确定吞吐瓶颈。
还没做完的:板载天线与外接 IPEX 的归属确认,需要插拔一次外接天线做物理对比。
九、AMP 双核异构:Linux + RT-Thread
RK3506B 有 3 颗 Cortex-A7 和 1 颗 Cortex-M0。SDK 的 AMP 配置将其中一颗 A7 分配给 RT-Thread,其余两颗运行 Linux,两侧通过 RPMsg 通信。M0 的启动问题另见 §9.4。
9.1 架构与内存布局
SDK 里的 AMP 参考配置(device/rockchip/.chips/rk3506/amp_linux_mcu.its)是这样分工的:
rk3506-amp.dtsi 用 /delete-node/ cpu@f02 把 A7-CPU2 从 Linux 手里拿走交给 RT-Thread,所以烧了 AMP 固件后 Linux 只剩 2 颗 A7。
内存划分与分区表的变化见下图(reserved-memory 与 ITS 的 share{} 交叉验证,两者逐字节一致):
所以刷 AMP 固件是重新分区,不是局部更新,不能只烧 boot.img。
9.2 构建
构建 AMP 有几个前提:
stock 的 AMP 板级文件是 Lyra Ultra 的,不是 Zero W:rk3506b-luckfox-lyra-amp-spinand.dts 缺 wireless_bluetooth(gpio1 RK_PC5),多了 gmac1 以太网,且 ubi.mtd=2。要基于 Zero W 的 dts 派生一份,直接用会丢 WiFi、蓝牙、LCD。
需要 scons(服务器原本没有):sudo apt-get install -y scons,否则跑到 AMP 会报 Your scons is missing,check-amp.sh 退出 1。
板级 defconfig 名字里不能有连字符:build.sh 的裸名字路径只认 ^[0-9a-z_]*_defconfig$,自定义配置一律用下划线。
RK_ROOTFS 的 Kconfig 是 default y if !RK_AMP,开了 RK_AMP=y 之后必须显式写 RK_ROOTFS=y,否则根文件系统直接不编。
本次派生的 DTS 命名为 rk3506b-luckfox-lyra-zero-w-amp.dts:在 Zero W 基础上引入 rk3506-amp.dtsi,将 ubi.mtd 改为 3,并为 RT-Thread 在 0x03e00000 保留 1 MiB、标记 no-map;LCD 引脚冲突按下方代码处理。
板级配置命名为 luckfox_lyra_zero_w_buildroot_spinand_amp_defconfig,保留 Zero W 的 AIC8800DC 配置,启用 RK_AMP=y 和 RK_ROOTFS=y,指定 amp_linux_mcu.its、上述 DTS 及 parameter-lyra-spinand-amp.txt。U-Boot 配置片段加入 rk-amp,内核片段加入 rockchip_amp.config。这是本次自建配置,不是 SDK 原有板型;完成这些修改后全量构建:
export HOSTCC=gcc-14 HOSTCXX=g++-14 CC=gcc-14 CXX=g++-14
./build.sh luckfox_lyra_zero_w_buildroot_spinand_amp_defconfig
./build.sh all
产物是 output/firmware/amp.img。其中 RT-Thread 载荷为 168,064 字节,M0 HAL 载荷为 24,064 字节,合计 192,128 字节;完整 FIT 文件为 197,120 字节,另含头部、描述信息及对齐空间。
提醒:往 AMP 的 dtsi 追加外设时,必须把该 dtsi 里 pinctrl-0 申领的 RM_IO 全列出来对照。RM_IO 是交叉开关,冲突不在编译期报错,只在运行期 probe 失败。实测里 rockchip_amp 把 I2C0 映射到 RM_IO12,而 SPI 小屏的 RES 脚也是 RM_IO12(pin18),Linux pinmux 检测到同一根脚两个 owner 返回 -EBUSY,fbtft 绑不上,结果是没有 fb(不是黑屏)。修法是在板级 dts 末尾覆盖:
&rockchip_amp {
pinctrl-0 = <&rm_io2_uart4_tx>, <&rm_io3_uart4_rx>,
<&rm_io4_uart3_tx>, <&rm_io5_uart3_rx>;
};
这 4 根线落在 40Pin 的 7、11、13、15(UART4 和 UART3),与屏幕占用的 RM_IO6/7/8/10/11/12/14 不重叠。
「Buildroot config changed!」多半是假警报:mk-buildroot.sh 拿新生成的 .config 和陈旧快照 .config.orig 比对,而这个快照只在不存在时存一次、永不刷新。判据是直接 grep ROOTFS_OVERLAY buildroot/output/<cfg>/.config,别按提示去 rm -rf buildroot/output/...,那要重编几十分钟。
9.3 上板验证
烧录后逐条核对(全部实测通过):
uname -a # → #3 SMP PREEMPT Mon Sep 28 14:49:21 UTC 2026 ← 新内核
cat /proc/mtd # → 4 个分区:uboot / boot / amp(4MB) / rootfs
cat /proc/cmdline # → root=ubi0:rootfs ubi.mtd=3 且 mtdparts 里有 (amp)
ls -l /dev/fb* # → /dev/fb0 和 /dev/fb1 都在
dmesg | grep -iE "EBUSY|unable to find group" # → 无 st7735 相关报错(pin 修复生效)
ls /proc/device-tree/reserved-memory/ # → amp-shmem/rpmsg/rpmsg-dma/amp@…/mcu@… 齐全
ls -l /dev/ttyRPMSG* # → /dev/ttyRPMSG0
rpmsg 链路的关键日志:
rockchip-rpmsg 3c00000.rpmsg: rpdev vdev0: vring0 0x3c00000, vring1 0x3c08000
virtio_rpmsg_bus virtio0: rpmsg host is online
virtio_rpmsg_bus virtio0: creating channel rpmsg-tty addr 0x3003
rpmsg host is online 说明从核侧已经把 vring 建好并应答;creating channel rpmsg-tty 说明对端主动用 name service 宣告了一个端点。
/sys/class/remoteproc 不存在是正常的:RK3506 的 AMP 不走 Linux 的 remoteproc 框架,FIT 由 U-Boot 从 amp 分区加载并启动从核,Linux 只负责 rpmsg。
怎么确认是哪颗核在跑? 同一份测试代码 rpmsg_test.c 里有这样的分支:
#ifdef HAL_AP_CORE
#define RPMSG_RTT_REMOTE_TEST3_EPT_ID 0x3003U /* ← dmesg 里出现的是这个 */
#else
#define RPMSG_RTT_REMOTE_TEST3_EPT_ID 0x3004U
#endif
0x3003 只对应 HAL_AP_CORE 分支,所以宣告端点的是 A7-CPU2 上的 RT-Thread,也就是 ITS 里的 amp2。
往 /dev/ttyRPMSG0 写数据没有回显,不是故障:RT-Thread 侧的 rpmsg_tty_recv_thread 只做 rt_kprintf 到自己的控制台,不回发。反方向要用 remote 侧的 MSH 命令 rpmsg_tty_send,而它依赖一个「首次收到 Linux 消息才置位」的对端地址,所以顺序是 Linux 先写一条,再去 RT-Thread 控制台发命令。
9.4 修复 M0 启动配置
现象:amp 分区里确实有 mcu 的二进制,但 U-Boot 不会去启动它。
根因:amp_linux_mcu.its 的 loadables 里只写了 amp2:
images {
amp2 { cpu = <0xf02>; load = <0x03e00000>; compile { sys = "rtt"; core = "ap"; }; };
mcu { type = "standalone"; load = <0xfff84000>; compile { sys = "hal"; core = "mcu"; }; };
};
configurations {
conf {
loadables = "amp2"; /* ← 只有 amp2,mcu 没列 */
linux { cpu = <0xf00>; load = <0x00900000>; };
};
};
U-Boot 的加载逻辑(u-boot/drivers/cpu/rockchip_amp.c 里的 brought_up_all_amp())只遍历 linux 子节点和 loadables 列表,而 mcu 两个集合都不在,于是 brought_up_amp() 从不被调用,不报错、静默跳过。
但通路本身是完整的。 brought_up_amp() 里有 if (type == IH_TYPE_STANDALONE) standalone_handler(...),会走到 fit_standalone_release(),而 RK3506 实现了这个钩子(u-boot/arch/arm/mach-rockchip/rk3506/rk3506.c:164):
int fit_standalone_release(char *id, uintptr_t entry_point)
{
/* address map: map 0 to sram, enable TCM mode for sram
* 0xfff84000 for sram, 0x03e00000 for ddr */
sip_***c_mcu_config(ROCKCHIP_SIP_CONFIG_BUSMCU_0_ID,
ROCKCHIP_SIP_CONFIG_MCU_CODE_START_ADDR, entry_point);
writel(0x0c000000, CRU_BASE + CRU_GATE_CON5); /* 开 m0 swclktck & hclk */
writel(0xbcd3d80, 0xff288090); /* m0 系统时间校准 */
writel(0x00060004, 0xff90000c); /* 使能 m0 中断 */
return 0;
}
这段代码通过 SMC 释放 M0 复位,并设置入口地址、时钟和中断;原配置没有触发这条路径。
对照整个 SDK 可以发现,约定是「要启动哪个核就写进 loadables」:
原 ITS 未将 mcu 列入 loadables,因此上述加载循环不会启动它。下面再通过烧录后的 FIT 和寄存器读值检查修复结果。
说明(一处已更正的错误推理):我最初拿 pinmux-pins 里的 pin 50 (gpio1-18): (MUX UNCLAIMED) 当"没人占这根脚、所以 M0 没跑"的旁证。这个推理不成立,已删。MUX UNCLAIMED 只反映 Linux pinctrl 自己的记账(desc->mux_usecount == 0 就打印这一行),读不出硬件寄存器 —— 从核就算把 pad mux 掉了,这一行照样是 UNCLAIMED。要判从核死活,得直接读它自己写过的那个寄存器。
M0的main() 调 HAL_PINCTRL_SetRMIO(GPIO_BANK1, GPIO_PIN_C2, RMIO_UART4_TX),HAL 最终写的是 RM_IO 交叉开关寄存器,地址可以从 SDK 里查出来:
先读取 M0 初始化代码会修改的寄存器:
busybox devmem 0xFF9100EC # 低 7 位为 0x9 时,与本次 M0 初始化配置一致
busybox devmem 0xFF9100F0 # 应为 0xA = RMIO_UART4_RX
将 mcu 加入启动列表:
loadables = "amp2", "mcu";
改完重编重烧(因为动了 amp 分区):./build.sh amp && ./build.sh firmware && ./build.sh updateimg。
上板验证。 检查烧入的 FIT 配置,以及 M0 初始化代码写入的寄存器。
一是烧进去的 FIT 本身。把板上 amp 分区(mtd2)抠出来,loadables 属性的值是 9 字节:
偏移 0x37C: 61 6d 70 32 00 6d 63 75 00
"amp2" \0 "mcu" \0
两个镜像名都在列表中,确认烧入的配置已包含 M0;是否实际执行还需结合运行记录判断。
二是 M0 自己留下的痕迹。它 main() 里那次 HAL_PINCTRL_SetRMIO() 会写 RM_IO 交叉开关寄存器,读出来是:
$ busybox devmem 0xFF9100EC # rm_gpio1c2_sel
0x00000009
$ busybox devmem 0xFF9100F0 # rm_gpio1c3_sel
0x0000000A
0x9 和 0xA 分别对应 RMIO_UART4_TX 和 RMIO_UART4_RX。这些读值与本次 M0 初始化代码一致,可作为执行过初始化的旁证,但寄存器会保留状态,不能据此判断 M0 当前是否仍正常运行。下面的串口启动输出提供了另一条证据。
此时 pinmux-pins 仍输出:
$ cat /sys/kernel/debug/pinctrl/*/pinmux-pins | grep -E "pin (2|50) "
pin 2 (gpio0-2): rockchip-amp (GPIO UNCLAIMED) function rm_io2 group rm-io2-uart4-tx
pin 50 (gpio1-18): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 2 由 Linux 的 rockchip-amp 驱动申领,因此显示 function 和 group。pin 50 由 M0 直接配置,Linux 仍显示 MUX UNCLAIMED。该字段只描述 Linux 的占用记录,不能判断 M0 是否执行过初始化。
其余一切照旧:4 个 MTD 分区、/dev/ttyRPMSG0、creating channel rpmsg-tty addr 0x3003 都在,说明加了 M0 之后 RT-Thread 那侧没受影响。
UART4 引脚映射。 将 RM_IO 寄存器读值与 SDK 头文件对照:

前四行对应 DTS 中 rockchip_amp 申领的 pad,后两行对应 M0 的配置。0x88 和 0xEC 都为 0x9,表示 UART4_TX 被路由到两个 pad。这解释了引脚映射,但不代表两个核同时写 UART 时不会发生输出交错。
已实测(2026-10-04):接 USB-TTL 到 pin 7 / pin 11,1500000 8N1,一条线上确实同时出现了 RT-Thread 的启动横幅和 M0 的 Hello RK3506 mcu,与上面从寄存器推出的结论一致。两者会互相穿插,M0 的 HAL 还会持续刷 GIC_AMPCheckIRouter error,所以这条线上抓日志必须存成文件再检索,靠眼睛看会被刷掉。
9.5 远程核控制台
RT-Thread 的板级配置(rtos/bsp/rockchip/rk3506-32/board/evb1/defconfig):
CONFIG_RT_CONSOLE_DEVICE_NAME="uart4"
CONFIG_RT_USING_UART3=y
CONFIG_RT_USING_UART4=y
CONFIG_RT_USING_LINUX_RPMSG=y
CONFIG_RT_USING_COMMON_TEST_LINUX_TTY_RPMSG_LITE=y
用 USB-TTL 的 RX 接 pin 7(UART4_TX)、TX 接 pin 11(UART4_RX),并与开发板共地,设置 1500000 8N1。先打开终端再复位板子,才能保留完整的 RT-Thread 启动日志。
板端实测当前 pinmux 为:
$ cat /sys/kernel/debug/pinctrl/*/pinmux-pins | grep "pin 2 "
pin 2 (gpio0-2): rockchip-amp (GPIO UNCLAIMED) function rm_io2 group rm-io2-uart4-tx
波特率是 1500000(rtos/bsp/rockchip/rk3506-32/board/common/board_base.c 里的 UART_BR_1500000)。
分区表(注意多出来的 mtd2 就是 amp,这是 AMP 固件和普通固件的分水岭):
$ uname -a
Linux luckfox 6.1.99 #3 SMP PREEMPT Mon Sep 28 14:49:21 UTC 2026 armv7l GNU/Linux
$ cat /proc/mtd
dev: size erasesize name
mtd0: 00400000 00020000 "uboot"
mtd1: 00c00000 00020000 "boot"
mtd2: 00400000 00020000 "amp" <- RT-Thread + M0 的镜像
mtd3: 0db60000 00020000 "rootfs"
rpmsg 通道建立的过程(dmesg):
$ dmesg | grep -iE 'rpmsg|virtio'
[ 2.655172] rockchip-rpmsg 3c00000.rpmsg: rockchip rpmsg platform probe.
[ 2.655991] rockchip-rpmsg 3c00000.rpmsg: assigned reserved memory node rpmsg-dma@3d00000
[ 2.656815] rockchip-rpmsg 3c00000.rpmsg: rpdev vdev0: vring0 0x3c00000, vring1 0x3c08000
[ 2.658202] virtio_rpmsg_bus virtio0: rpmsg host is online
[ 2.658309] virtio_rpmsg_bus virtio0: creating channel rpmsg-tty addr 0x3003
字符设备随之出现:
$ ls -l /dev/ttyRPMSG*
crw------- 1 root root 245, 0 Jan 1 00:00 /dev/ttyRPMSG0
这些日志确认 Linux 与 A7-CPU2 上的 RT-Thread 建立了地址为 0x3003 的 rpmsg-tty 通道。
串口记录:RT-Thread 启动信息、RPMsg 初始化,以及仍待排查的 HAL 中断路由告警

十、结语
这次从一块 SPI 小屏入手,做了状态显示、开机动画和无线扫描面板,又调通了 Linux 与 RT-Thread 的 RPMsg 通信。Lyra Zero W 对我最有吸引力的地方,也就在这里:既能做一个带屏的 Linux 小设备,也能继续尝试 RTOS 协同开发。
不过,M0 启动后的 GIC_AMPCheckIRouter error 还没解决,天线两档对应的物理通路和蓝牙吞吐也没有测清楚。功能跑起来和长期稳定使用是两回事,这些遗留问题还得逐个处理。