AI Agent 驱动开发范式:在 RDK X5 上开发 SPI LCD 的内核态 DRM 渲染与点屏

在嵌入式开发中,点亮一块屏幕往往是一场与底层硬件的“肉搏”。以往我们依靠人工翻阅长篇累牍的 Datasheet、反复试错编译内核。但随着 AI 时代的到来,我们完全可以引入 AI Agent 协作范式,将杂乱的驱动移植过程转化为结构化、可复现的工程流水线。

今天,我们将以 RDK X5 开发板点亮 3.5 寸 ST7796S SPI LCD (480x320) + GT911 触摸屏为例,抛弃低效的用户态 spidev,直击内核 DRM 驱动。

开发工具: Antigravity 2.0 、Claude

大模型:Gemini 3.1 pro、GLM 5.1、Mimo V2.5 Pro

综合体验下来,大模型解决问题的能力,Gemini 3.1 Pro > GLM 5.1 > Mimo V2.5 Pro

Prompt 构思与生成思路:

一开始先把现有资料喂给Agent,包括屏幕驱动示例,rdk 40pin 引脚定义等,让他先把屏幕点亮,并显示桌面

但是发现他采用了再应用层截取X11桌面显示去完成的思路:应用 → X11 → SHM 截帧 → BGRA转RGB565 → spidev ioctl(PIO) → ST7796S,这套方案能跑,但 FPS 只有 16 左右。瓶颈很明显:spidev 是 PIO 传输,CPU 阻塞等着;DC 引脚靠 sysfs GPIO 切换,每次 5us;还有各种 usleep 占着 CPU 不干活。加上 SHM 截帧和像素格式转换的额外拷贝,能跑 16 FPS 已经是极限了。

于是,我把rdk X5的Linux 驱动等资料发给agent ,让他先阅读一下,然后参考树莓派Bookworm系统桌面显示 的方式修改rdk x5的设备树,移植树莓派这块屏幕的内核,换用内核的 panel-mipi-dbi DRM 驱动,现在思路变成了这样:应用 → X11 → modesetting → /dev/fb0 → panel-mipi-dbi → SPI DMA → ST7796S,在环境方面,我在window电脑下开发 配备wsl,交叉编译是跑在Ubuntu 22.04的docker 容器中,具体的环境配置也是让Agent 参考下面文档配置的。

于是,Agent 便自动规划了以下执行步骤并执行,以下是Agent 工作的内容:

1. 接线

按上面的表接好线。特别注意 CS 接 pin 26、两个设备都用 3.3V、地线都接好。

2. 编译内核模块

RDK X5 内核 (6.1.83) 默认没有启用 CONFIG_DRM_PANEL_MIPI_DBI,需要编译两个模块:panel_mipi_dbi.kodrm_mipi_dbi.ko

好消息是,x5-rdk-gen 源码树里这些驱动的源文件、Kconfig、Makefile 都已经就位了,你只需要在 defconfig 里加一行然后编译就行。

获取源码树

x5-rdk-gen 是 RDK X5 的完整内核构建系统:

# 安装 repo
mkdir -p ~/bin
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo
export PATH=~/bin:$PATH

# 克隆(清华镜像加速)
export REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo/'
cd ~
repo init -u git@github.com:D-Robotics/x5-manifest.git -b main
repo sync

克隆完成后,关键目录长这样:

x5-rdk-gen/
├── source/kernel/drivers/gpu/drm/tiny/panel-mipi-dbi.c   ← 驱动源码,已存在
├── source/kernel/drivers/gpu/drm/drm_mipi_dbi.c          ← 辅助模块,已存在(含 ST7796S 复位时序补丁)
├── source/kernel/arch/arm64/configs/                      ← defconfig 在这里
├── mk_kernel.sh                                           ← 编译脚本
└── mk_debs.sh                                             ← 打包脚本

源码树里已经有的东西:

  • panel-mipi-dbi.c — 397 行,从 Linux 主线引入的完整驱动

  • drm_mipi_dbi.c — 第 603 行的复位时序已改成 usleep_range(10000, 15000),适配 ST7796S(默认的 20us 不够,ST7796S 复位需要 10ms)

  • Kconfig 里已有 CONFIG_DRM_PANEL_MIPI_DBI 条目

  • Makefile 里已有 panel-mipi-dbi.o 编译规则

唯一要做的就是在 defconfig 里启用这个模块。

启用模块配置

编辑 arch/arm64/configs/hobot_x5_rdk_ubuntu_defconfig,末尾加一行:

CONFIG_DRM_PANEL_MIPI_DBI=m

或者用 menuconfig:

cd x5-rdk-gen
./mk_kernel.sh menuconfig
# Device Drivers → Graphics support → Display panels → MIPI DBI compatible display panels
# 按 M 设为模块,保存退出

搭建编译环境

x5-rdk-gen 的交叉编译需要 Ubuntu 22.04。如果你跑的是其他版本(比如 WSL Ubuntu 26.04),用 Docker:

# 装 Docker
sudo apt install docker.io
sudo usermod -aG docker $USER
# 重新登录 shell

# 起 22.04 容器,把源码挂进去
docker run -it --name x5-build -v ~/x5-rdk-gen:/work ubuntu:22.04 bash

# 容器里装依赖
apt update && apt install -y \
  build-essential bc bison flex python3 \
  libncurses5-dev libssl-dev \
  device-tree-compiler u-boot-tools ccache wget git

# 装 ARM 交叉编译工具链
cd /opt
wget http://archive.d-robotics.cc/toolchain/gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu.tar.xz
tar -xf gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu.tar.xz

# 验证
/opt/gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc --version

编译

cd /work
./mk_kernel.sh

编译完成后,模块在:

  • source/kernel/drivers/gpu/drm/tiny/panel_mipi_dbi.ko

  • source/kernel/drivers/gpu/drm/drm_mipi_dbi.ko

如果只想编单个模块(更快),可以:

cd /work/source/kernel
make ARCH=arm64 \
  CROSS_COMPILE=/opt/gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu- \
  M=drivers/gpu/drm/tiny \
  modules

验证一下模块对不对:

modinfo panel_mipi_dbi.ko | grep depends   # 应看到依赖 drm_mipi_dbi, drm, spi 等
modinfo drm_mipi_dbi.ko | grep vermagic     # 应显示 6.1.83

3. 生成 ST7796S 固件文件

panel-mipi-dbi 驱动启动时会从 /lib/firmware/panel-mipi-dbi-spi.bin 加载初始化命令。这个 bin 文件的格式是:

15 字节:magic "MIPI DBI\0\0\0\0\0\0\0"
 1 字节:版本号 (0x01)
 N 字节:命令序列,每条 [cmd, 参数个数, param1, param2, ...]
         延时用 NOP 表示:[0x00, 0x01, 延时ms]

本仓库有生成脚本 generate_st7796s_fw.py,直接跑:

python3 generate_st7796s_fw.py    # 生成 st7796s.bin (109 bytes)

然后传到板子上,注意文件名必须和设备树 compatible 一致(驱动会自动拼 `.bin` 后缀):

scp st7796s.bin sunrise@192.168.128.10:/tmp/
ssh sunrise@192.168.128.10 "sudo cp /tmp/st7796s.bin /lib/firmware/panel-mipi-dbi-spi.bin"

4. 设备树 Overlay

需要两个 overlay:一个给 LCD,一个给触摸屏。

ST7796S LCD overlay

保存为 overlay-st7796s.dts

/dts-v1/;
/plugin/;

/ {
    fragment@0 {
        target-path = "/soc/a55_apb0/spi@34010000";
        __overlay__ {
            #address-cells = <1>;
            #size-cells = <0>;

            /* 禁用 CS1 上的 spidev,避免冲突 */
            spidev@1 {
                status = "disabled";
            };

            panel-mipi-dbi@1 {
                compatible = "panel-mipi-dbi-spi";
                reg = <1>;                         /* CS1,不是 CS0 */
                spi-max-frequency = <40000000>;    /* 40MHz */
                dc-gpios = <&ls_gpio0_porta 9 0>;  /* BCM 22 */
                reset-gpios = <&ls_gpio0_porta 0 0>; /* BCM 27 */
                write-only;                        /* ST7796S SPI 读取不可靠 */

                width-mm = <85>;
                height-mm = <53>;

                panel-timing {
                    clock-frequency = <0>;
                    hactive = <480>;
                    vactive = <320>;
                    hfront-porch = <0>;
                    hsync-len = <0>;
                    hback-porch = <0>;
                    vfront-porch = <0>;
                    vsync-len = <0>;
                    vback-porch = <0>;
                };
            };
        };
    };
};

几个要注意的点:

  • reg = <1> 表示 CS1。CS0 上可能挂着 IMU,别抢

  • write-only 必须加。ST7796S 的 SPI 读取不可靠,不加的话驱动读 ID 失败会跳过整个初始化

  • width-mm / height-mm 和各种 porch 是 of_get_drm_panel_display_mode() 的校验必填项,虽然 ST7796S 是 write-only 设备没有同步信号,但驱动要检查这些字段

GT911 触摸 overlay

保存为 overlay-gt911.dts

/dts-v1/;
/plugin/;

/ {
    fragment@0 {
        target-path = "/soc/a55_apb0/i2c@341c0000";
        __overlay__ {
            #address-cells = <1>;
            #size-cells = <0>;

            gt911@5d {
                compatible = "goodix,gt911";
                reg = <0x5d>;
                interrupt-parent = <&dsp_gpio_porta>;
                interrupts = <9 2>;                  /* 下降沿触发 */
                reset-gpios = <&ls_gpio1_porta 7 1>; /* 低电平有效 */

                touchscreen-size-x = <320>;
                touchscreen-size-y = <480>;
            };
        };
    };
};

重要:不要加 `touchscreen-inverted-x`、`touchscreen-inverted-y` 或 `touchscreen-swapped-x-y`!这些属性会让内核驱动交换/翻转坐标值,但 ABS_X/Y 的 min/max 范围仍然是竖屏值(X:0-319, Y:0-479)。交换后值超出范围,libinput 归一化会把坐标扭曲,越到边缘偏差越大。坐标变换放在 X11 层处理(后面第 7 步会说)。

编译和部署 overlay

# 主机上编译(需要 device-tree-compiler)
dtc -@ -I dts -O dtb overlay-st7796s.dts -o overlay-st7796s.dtbo
dtc -@ -I dts -O dtb overlay-gt911.dts -o overlay-gt911.dtbo

# 传到板子
scp overlay-st7796s.dtbo overlay-gt911.dtbo sunrise@192.168.128.10:/tmp/
ssh sunrise@192.168.128.10 \
  "sudo cp /tmp/overlay-st7796s.dtbo /boot/overlays/ &&
   sudo cp /tmp/overlay-gt911.dtbo /boot/overlays/"

然后在板子上的 /boot/config.txt 末尾加上:

dtoverlay=overlay-st7796s
dtoverlay=overlay-gt911

5. 部署内核模块

把编译好的 .ko 传到板子上:

scp panel_mipi_dbi.ko drm_mipi_dbi.ko sunrise@192.168.128.10:/tmp/
ssh sunrise@192.168.128.10 \
  "sudo mkdir -p /lib/modules/6.1.83/extra &&
   sudo cp /tmp/panel_mipi_dbi.ko /lib/modules/6.1.83/extra/ &&
   sudo cp /tmp/drm_mipi_dbi.ko /lib/modules/6.1.83/extra/ &&
   sudo depmod -a"

模块会通过设备树 compatible 字符串在启动时自动加载。想手动加载也行:

sudo modprobe drm_mipi_dbi
sudo modprobe panel_mipi_dbi

6. 背光 GPIO 服务

ST7796S 背光由 GPIO 控制(BCM 18,sysfs 编号 421)。写个 systemd 服务让开机自动点亮:

sudo nano /etc/systemd/system/st7796s-backlight.service

写入:

[Unit]
Description=ST7796S LCD Backlight
After=multi-user.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c 'echo 421 > /sys/class/gpio/export 2>/dev/null; echo out > /sys/class/gpio/gpio421/direction; echo 1 > /sys/class/gpio/gpio421/value'
ExecStop=/bin/sh -c 'echo 0 > /sys/class/gpio/gpio421/value'

[Install]
WantedBy=multi-user.target

启用:sudo systemctl enable st7796s-backlight.service

7. X11 配置

显示驱动

创建 /etc/X11/xorg.conf.d/99-spi-lcd.conf

Section "Device"
    Identifier "SPI LCD"
    Driver     "modesetting"
    Option     "kmsdev" "/dev/dri/card0"
EndSection

**一定要用 `modesetting`,不要用 `fbdev`。** fbdev 不支持 DRI,GLX 走软件渲染容易出错。modesetting 虽然也没有硬件 GPU,但 swrast 能正常工作。

触摸校准

GT911 报的是竖屏坐标(X: 0-319, Y: 0-479),但 LCD 显示是横屏 (480x320)。需要做 90° 旋转才能把竖屏触摸映射到横屏。

创建 /etc/X11/xorg.conf.d/90-touchscreen.conf

Section "InputClass"
    Identifier "Goodix TouchScreen Calibration"
    MatchProduct "Goodix Capacitive TouchScreen"
    MatchDriver "libinput"
    Option "CalibrationMatrix" "0 -1 1 1 0 0 0 0 1"
EndSection

这个矩阵做的事是:

  • screen_x = 1 - y_norm(Y 翻转到 X,取反)

  • screen_y = x_norm(X 映射到 Y)

其中 x_norm = raw_x / 319y_norm = raw_y / 479,libinput 会根据 ABS 范围归一化到 0-1。

用配置文件的好处是持久化的,重启不会丢。如果用 `xinput set-prop` 命令设,重启后就没了。

禁用冲突的 X11 配置

RDK X5 默认的一些 X11 配置可能冲突,禁用掉:

sudo mv /etc/X11/xorg.conf.d/1-resolution.conf /etc/X11/xorg.conf.d/1-resolution.conf.disable
sudo mv /etc/X11/xorg.conf.d/2-dr-accel.conf /etc/X11/xorg.conf.d/2-dr-accel.conf.bak

8. 重启和验证

sudo reboot

重启后逐项检查:

# DRM panel 是否加载
dmesg | grep -i "mipi.dbi\|panel-mipi"
ls /dev/fb*                              # 应有 /dev/fb0
cat /sys/class/graphics/fb0/name         # 应显示 panel-mipi-dbid
cat /sys/class/graphics/fb0/modes        # 应显示 U:480x320p-0

# 触摸驱动是否加载
dmesg | grep -i "goodix"
cat /proc/bus/input/devices | grep -A5 Goodix

# DRM 设备
ls /dev/dri/                              # card0 (panel-mipi-dbi), card1 (vs_drm), renderD128

# 触摸校准是否生效
DISPLAY=:0 xinput list-props 7 | grep "Calibration Matrix"   # 应显示 0 -1 1 1 0 0 0 0 1

# framebuffer 测试
cat /dev/urandom > /dev/fb0               # LCD 显示噪点
dd if=/dev/zero of=/dev/fb0               # LCD 变黑

如果 X11 配置正确,重启后 LCD 上应该能看到桌面。

快速部署

如果你不想走我的老路,这里也提供了这块屏幕编译好的驱动可以直接传到板子上运行。

编译好的文件都在仓库里了: Cathay-Launch-UI/SPI-LCD-SETUP.zh.md at main · shockley6668/Cathay-Launch-UI · GitHub

直接clone 该仓库后再目录下执行就好,下面的ip改成x5的ip

# overlay
scp overlay-st7796s.dtbo overlay-gt911.dtbo sunrise@192.168.128.10:/tmp/
ssh sunrise@192.168.128.10 \
  "sudo cp /tmp/*.dtbo /boot/overlays/ &&
   grep -q overlay-st7796s /boot/config.txt || printf 'dtoverlay=overlay-st7796s\ndtoverlay=overlay-gt911\n' | sudo tee -a /boot/config.txt"

# 固件
scp st7796s.bin sunrise@192.168.128.10:/tmp/
ssh sunrise@192.168.128.10 "sudo cp /tmp/st7796s.bin /lib/firmware/panel-mipi-dbi-spi.bin"

# 内核模块
scp kernel-modules/panel-mipi-dbi.ko kernel-modules/drm_mipi_dbi.ko sunrise@192.168.128.10:/tmp/
ssh sunrise@192.168.128.10 \
  "sudo mkdir -p /lib/modules/6.1.83/extra &&
   sudo cp /tmp/panel-mipi-dbi.ko /lib/modules/6.1.83/extra/ &&
   sudo cp /tmp/drm_mipi_dbi.ko /lib/modules/6.1.83/extra/ &&
   sudo depmod -a"

# X11 显示配置
ssh sunrise@192.168.128.10 "sudo tee /etc/X11/xorg.conf.d/99-spi-lcd.conf << 'EOF'
Section \"Device\"
    Identifier \"SPI LCD\"
    Driver     \"modesetting\"
    Option     \"kmsdev\" \"/dev/dri/card0\"
EndSection
EOF"

# X11 触摸校准
ssh sunrise@192.168.128.10 "sudo tee /etc/X11/xorg.conf.d/90-touchscreen.conf << 'EOF'
Section \"InputClass\"
    Identifier \"Goodix TouchScreen Calibration\"
    MatchProduct \"Goodix Capacitive TouchScreen\"
    MatchDriver \"libinput\"
    Option \"CalibrationMatrix\" \"0 -1 1 1 0 0 0 0 1\"
EndSection
EOF"

# 背光服务
ssh sunrise@192.168.128.10 "sudo tee /etc/systemd/system/st7796s-backlight.service << 'EOF'
[Unit]
Description=ST7796S LCD Backlight
After=multi-user.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c 'echo 421 > /sys/class/gpio/export 2>/dev/null; echo out > /sys/class/gpio/gpio421/direction; echo 1 > /sys/class/gpio/gpio421/value'
ExecStop=/bin/sh -c 'echo 0 > /sys/class/gpio/gpio421/value'

[Install]
WantedBy=multi-user.target
EOF"

ssh sunrise@192.168.128.10 "sudo systemctl enable st7796s-backlight.service"

# 禁用旧的 x11-to-spi(如果存在)
ssh sunrise@192.168.128.10 "sudo systemctl disable x11-to-spi 2>/dev/null; sudo systemctl stop x11-to-spi 2>/dev/null"

# 重启
ssh sunrise@192.168.128.10 "sudo reboot"

# 重启后验证
ssh sunrise@192.168.128.10 \
  "ls /dev/fb* &&
   cat /sys/class/graphics/fb0/name &&
   DISPLAY=:0 xinput list-props 7 | grep Calibration"
1 个赞