2. 智能座舱基础

做8155平台开发,第一关就是搞明白硬件接口怎么跟软件对上。很多人一上来就扎进代码里,结果连GPIO的电压域都没搞清楚,板子一跑就冒烟。我刚开始搞车机时也犯过这种错,后来养成了一个习惯——先看原理图,再看设备树,最后才碰代码。

这一章,咱们就把GPIO、I2C、SPI、UART这几个最常用的外设接口,从硬件到驱动框架再到设备树配置,一条线捋清楚。顺便把内核编译和模块加载也带上,这些都是你每天都要打交道的活儿。

2.1 高通8155的硬件接口概览

8155这颗SoC,说白了就是一颗专为座舱设计的超级单片机。它集成了大量的外设控制器,通过引脚复用(Pin Mux)的方式,让你在同一组物理引脚上切换不同的功能。

接口类型 控制器数量 典型用途 速率/特性
GPIO ~150+ 按键、LED、中断检测、控制信号 可配置上下拉、驱动能力
I2C 12组 触摸屏、传感器、音频Codec 标准/快速/高速模式
SPI 8组 显示屏、闪存、CAN控制器 最高50MHz,支持DMA
UART 10组 调试串口、蓝牙、GNSS模块 最高4Mbps,支持硬件流控
I3C 4组 新一代传感器总线 兼容I2C,速率更高

你看这个表,I2C有12组,SPI有8组,资源其实挺富裕的。但有个坑——这些控制器不是所有引脚都能同时用,因为引脚复用有冲突。我遇到过一块板子,设计时用了两组SPI,结果发现它们共用了一个中断线,调试了整整两天才找到原因。

注意: 8155的GPIO电压域分为1.8V和3.3V两种。接外设前一定要确认电平匹配,否则轻则信号异常,重则烧毁GPIO。我见过有人直接把3.3V的传感器接到1.8V的GPIO上,结果传感器输出高电平时,GPIO直接冒烟了。

2.2 GPIO驱动框架与设备树配置

GPIO是最基础的外设,但也是最容易出问题的。8155的GPIO驱动基于Linux内核的pinctrl子系统和GPIO子系统。说白了,pinctrl管引脚功能切换,GPIO子系统管高低电平读写。

2.2.1 设备树中的GPIO配置

在8155平台上,每个GPIO都需要在设备树里明确配置。我给你看一个典型的例子:

// 设备树片段:配置一个GPIO按键
&tlmm {
    key_vol_up {
        gpios = &tlmm 42 0x0;
        function = "gpio";
        bias-pull-up;
        drive-strength = <2>;
    };
};

/ {
    gpio-keys {
        compatible = "gpio-keys";
        pinctrl-names = "default";
        pinctrl-0 = <&key_vol_up>;

        vol-up {
            label = "volume_up";
            gpios = &tlmm 42 GPIO_ACTIVE_LOW;
            linux,code = <KEY_VOLUMEUP>;
            debounce-interval = <5>;
        };
    };
};

这里有几个关键点:

  • tlmm:这是8155的引脚控制器节点,所有GPIO都挂在这个下面
  • gpios = &tlmm 42 0x0:表示使用tlmm控制器的第42号引脚,0x0是标志位
  • bias-pull-up:使能内部上拉,防止引脚悬空
  • debounce-interval:消抖时间,单位毫秒。机械按键一般设5-20ms
我的习惯: 调试GPIO时,先用sysfs接口验证硬件是否正常。进入系统后执行 echo 42 > /sys/class/gpio/export,然后操作对应目录下的direction和value文件。如果sysfs能正常工作,那问题肯定出在设备树配置上。

2.2.2 GPIO中断配置

8155的GPIO支持边沿触发和电平触发两种中断模式。配置时要注意:

// 设备树中的GPIO中断配置
gpio-keys {
    compatible = "gpio-keys";
    interrupt-parent = &tlmm;
    interrupts = <42 IRQ_TYPE_EDGE_FALLING>;

    vol-up {
        gpios = &tlmm 42 GPIO_ACTIVE_LOW;
        linux,code = <KEY_VOLUMEUP>;
    };
};

IRQ_TYPE_EDGE_FALLING表示下降沿触发。我建议你尽量用边沿触发,电平触发容易产生重复中断,尤其是在信号有毛刺的时候。

2.3 I2C与SPI总线驱动

I2C和SPI是座舱里用得最多的总线。触摸屏、传感器、音频Codec、甚至部分显示屏都用它们通信。

2.3.1 I2C驱动框架

8155的I2C控制器驱动已经集成在内核里了。你要做的是在设备树里描述外设,然后写一个客户端驱动。来看一个典型的I2C设备节点:

// I2C总线控制器配置
&qupv3_se2_i2c {
    status = "okay";
    clock-frequency = <400000>;  // 400kHz快速模式

    touchscreen@38 {
        compatible = "goodix,gt9271";
        reg = <0x38>;
        interrupt-parent = <&tlmm>;
        interrupts = <65 IRQ_TYPE_EDGE_FALLING>;
        reset-gpios = <&tlmm 64 GPIO_ACTIVE_LOW>;
        AVDD28-supply = <&pm8150_l5>;
    };
};

这里要注意:

  • reg = <0x38>:这是I2C从设备地址,7位地址左移一位后的值
  • clock-frequency:总线频率,触摸屏一般用400kHz,传感器用100kHz就够了
  • AVDD28-supply:电源域,必须和硬件设计一致
我曾经踩过的坑: 有一次触摸屏死活不通,用示波器量I2C波形,SCL和SDA都有信号,但设备就是不响应。查了半天发现是I2C地址写错了。芯片手册上写的是0x38,但那是7位地址,设备树里要填左移一位后的值。实际上0x38左移一位是0x70,但有些驱动又要求填原始7位地址。这个混乱让我浪费了一整天。

2.3.2 SPI驱动框架

SPI比I2C简单,但速率高,对时序要求更严格。8155的SPI控制器支持4种模式,通过CPOL和CPHA组合决定:

模式 CPOL CPHA 数据采样边沿
模式0 0 0 上升沿采样
模式1 0 1 下降沿采样
模式2 1 0 下降沿采样
模式3 1 1 上升沿采样

设备树配置示例:

&qupv3_se4_spi {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&qupv3_se4_spi_active>;

    display@0 {
        compatible = "ilitek,ili9488";
        reg = <0>;  // SPI片选编号
        spi-max-frequency = <32000000>;
        spi-cpol;
        spi-cpha;
    };
};

spi-cpol和spi-cpha分别对应CPOL=1和CPHA=1,也就是模式3。大多数显示屏都工作在模式3下。

2.4 UART与调试串口

UART在座舱里主要干两件事:调试和通信。调试串口是你的救命稻草,系统起不来的时候,全靠它看log。

2.4.1 调试串口配置

8155的调试串口通常用UART0,在设备树里这样配:

&qupv3_se0_uart {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&qupv3_se0_uart_active>;
};

就这么简单?对,因为调试串口不需要额外的外设节点,内核启动时直接用它打印信息。但有个细节——波特率。8155的调试串口默认是115200,但有些板子为了兼容老设备会改成9600。我建议你拿到板子后先确认一下,不然log全是乱码。

2.4.2 蓝牙模块的UART配置

蓝牙模块通常走UART,而且需要硬件流控(RTS/CTS)。配置时要注意:

&qupv3_se1_uart {
    status = "okay";
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&qupv3_se1_uart_active>;
    pinctrl-1 = <&qupv3_se1_uart_sleep>;

    bluetooth {
        compatible = "qcom,wcn3990-bt";
        max-speed = <3000000>;
        cts-gpios = <&tlmm 12 GPIO_ACTIVE_HIGH>;
        rts-gpios = <&tlmm 13 GPIO_ACTIVE_HIGH>;
    };
};

max-speed设成3Mbps,这是蓝牙模块的典型速率。硬件流控的GPIO必须和原理图一一对应,接反了蓝牙就没办法正常通信。

2.5 内核编译与模块加载

搞完设备树和驱动,接下来就是编译和部署。8155平台用Yocto构建系统,但内核编译可以单独进行。

2.5.1 内核编译流程

我一般这样编译内核:

# 设置交叉编译环境
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

# 配置内核
make defconfig  # 使用默认配置
make menuconfig # 按需调整

# 编译内核和设备树
make -j8 Image
make -j8 dtbs

# 编译内核模块
make -j8 modules
make modules_install INSTALL_MOD_PATH=./modules

这里有个关键点:defconfig。8155有专门的defconfig文件,通常在arch/arm64/configs/下,名字类似sa8155_defconfig。直接用make defconfig会选错,你得指定:

make sa8155_defconfig

2.5.2 模块加载与调试

驱动可以编译进内核,也可以编译成模块。我建议你在开发阶段都用模块,这样修改后不用重启整个系统:

# 加载模块
insmod my_driver.ko

# 查看模块信息
modinfo my_driver.ko

# 卸载模块
rmmod my_driver

# 查看已加载模块
lsmod | grep my_driver
调试技巧: 模块加载失败时,用 dmesg | tail 查看内核日志。如果提示"Unknown symbol",说明你的模块依赖了其他模块,需要先加载依赖。用 modprobe 可以自动处理依赖关系,比 insmod 省心。

2.6 知识体系总览

说了这么多,咱们用一张图把整个知识体系串起来。这张图涵盖了从硬件接口到驱动加载的完整链路:

8155 硬件接口与 BSP 知识体系 硬件接口层 引脚复用(Pin Mux) 电压域(1.8V/3.3V) GPIO I2C SPI UART I3C ...更多 Linux 驱动框架:pinctrl / GPIO子系统 / I2C核心 / SPI核心 / TTY层 设备树配置:引脚复用 / 中断 / 时钟 / 电源域 / 时序参数 内核编译:Image + dtbs + modules 模块加载:insmod / modprobe / lsmod 开发流程:硬件原理图 → 设备树配置 → 驱动开发 → 内核编译 → 模块加载验证

这张图从左到右、从上到下展示了完整的开发链路。你每次拿到一块新板子,都可以按这个流程走一遍,基本不会漏掉什么。

核心要点:
  • GPIO配置要关注电压域和引脚复用,用sysfs先验证硬件
  • I2C地址容易搞错,务必确认是7位还是8位地址
  • SPI模式由CPOL/CPHA决定,和外设手册必须一致
  • UART调试串口是救命稻草,波特率别搞错
  • 开发阶段用模块加载,省去反复重启的时间

嗯,这一章的内容就到这儿。硬件接口和BSP是座舱开发的基石,你把这些搞透了,后面写应用层驱动就会顺手很多。记住一句话:设备树配不对,驱动写得再好也白费。