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,结果发现它们共用了一个中断线,调试了整整两天才找到原因。
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
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:电源域,必须和硬件设计一致
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 知识体系总览
说了这么多,咱们用一张图把整个知识体系串起来。这张图涵盖了从硬件接口到驱动加载的完整链路:
这张图从左到右、从上到下展示了完整的开发链路。你每次拿到一块新板子,都可以按这个流程走一遍,基本不会漏掉什么。
- GPIO配置要关注电压域和引脚复用,用sysfs先验证硬件
- I2C地址容易搞错,务必确认是7位还是8位地址
- SPI模式由CPOL/CPHA决定,和外设手册必须一致
- UART调试串口是救命稻草,波特率别搞错
- 开发阶段用模块加载,省去反复重启的时间
嗯,这一章的内容就到这儿。硬件接口和BSP是座舱开发的基石,你把这些搞透了,后面写应用层驱动就会顺手很多。记住一句话:设备树配不对,驱动写得再好也白费。