3、Sensor驱动开发环境搭建:Linux内核驱动开发环境、交叉编译工具链、设备树基础、I2C/SPI通信协议
做Sensor驱动开发,第一关就是搭环境。很多人觉得这事简单,装个虚拟机、下个内核源码就开干。但实际项目里,环境配不好,后面全是坑。我见过不少同事,代码写得挺溜,结果编译出来的模块一加载就崩,查了半天发现是交叉编译工具链版本不对。
这一章,我就把环境搭建的要点掰开揉碎讲清楚。从Linux内核驱动开发环境怎么配,到交叉编译工具链怎么选,再到设备树和I2C/SPI通信协议的核心知识点,咱们一个一个过。
3.1 Linux内核驱动开发环境
先说主机环境。我个人习惯用Ubuntu 20.04 LTS或22.04 LTS,这两个版本对嵌入式开发的支持最稳定。你不需要装桌面版,服务器版就行,省资源。
核心依赖包就这些:
sudo apt-get update
sudo apt-get install build-essential
sudo apt-get install linux-headers-$(uname -r)
sudo apt-get install git fakeroot ncurses-dev
sudo apt-get install xz-utils libssl-dev bc
sudo apt-get install flex bison libelf-dev
这里有个坑——linux-headers包必须和你的内核版本完全一致。我遇到过有人用5.4的内核,结果装了5.10的头文件,编译出来的模块根本插不进去。
内核源码获取方式有两种:
- 从kernel.org下载:稳定版,适合学习
- 从芯片厂商获取:比如NXP、Rockchip、Allwinner提供的BSP包,里面带了补丁和配置文件
我建议初学者先从标准内核开始。厂商BSP虽然方便,但里面改的东西太多,有时候你都不知道某个配置是干嘛的。
3.2 交叉编译工具链
交叉编译,说白了就是在你的x86电脑上编译出ARM架构能跑的二进制文件。Sensor驱动通常跑在ARM Cortex-A系列处理器上,所以你得用ARM交叉编译工具链。
常用的工具链有几种:
| 工具链 | 适用场景 | 安装方式 |
|---|---|---|
| gcc-arm-linux-gnueabihf | ARM 32位,硬件浮点 | apt-get install |
| gcc-aarch64-linux-gnu | ARM 64位 | apt-get install |
| Linaro GCC | 性能优化更好 | 官网下载 |
| 芯片厂商定制版 | 特定SoC优化 | BSP包自带 |
安装命令很简单:
sudo apt-get install gcc-arm-linux-gnueabihf
sudo apt-get install gcc-aarch64-linux-gnu
装完之后验证一下:
arm-linux-gnueabihf-gcc --version
如果能看到版本号,说明工具链装好了。
编译内核模块时,需要指定交叉编译器:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
这里有个细节——CROSS_COMPILE后面的横线不能丢。它表示工具链的前缀,系统会自动补全gcc、ld、objcopy等命令。
3.3 设备树基础
设备树(Device Tree)是Linux内核用来描述硬件信息的一种数据结构。以前的内核用板级文件(board-xxx.c)来定义硬件,但那种方式太死板,换一个板子就得改内核代码。
设备树解决了这个问题。它把硬件描述和内核代码分离开,你只需要写一个.dts文件,告诉内核“这个Sensor挂在I2C总线上,地址是0x48”,内核就能自动识别。
一个典型的Sensor设备树节点长这样:
&i2c1 {
status = "okay";
clock-frequency = <400000>;
imx219: camera@10 {
compatible = "sony,imx219";
reg = <0x10>;
clocks = <&clks IMX219_CLK>;
clock-names = "xvclk";
DOVDD-supply = <&vreg_dovdd>;
AVDD-supply = <&vreg_avdd>;
DVDD-supply = <&vreg_dvdd>;
reset-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
};
};
我来解释一下关键字段:
- compatible:驱动匹配的关键字,驱动里通过
of_match_table来匹配 - reg:I2C设备地址,0x10就是IMX219的默认地址
- clocks:时钟源,Sensor需要外部时钟才能工作
- supply:电源域,Sensor通常需要多路电压
- reset-gpios:复位引脚,低电平有效
编译设备树的方法:
dtc -I dts -O dtb -o imx219.dtb imx219.dts
生成的.dtb文件就是内核启动时加载的二进制设备树。
reg字段写的是7位I2C地址左移1位后的值,不是原始地址。
3.4 I2C通信协议
I2C是Sensor驱动里最常用的通信协议。它只有两根线——SCL(时钟)和SDA(数据),支持多主多从。
Sensor驱动里,I2C主要用来做两件事:
- 配置寄存器:写Sensor的初始化参数,比如曝光时间、增益、帧率
- 读取状态:读Sensor的ID、温度、错误标志等
Linux内核里操作I2C的标准接口:
// 写寄存器
int i2c_smbus_write_byte_data(struct i2c_client *client,
u8 reg, u8 value);
// 读寄存器
int i2c_smbus_read_byte_data(struct i2c_client *client,
u8 reg);
// 批量写
int i2c_master_send(struct i2c_client *client,
const char *buf, int count);
// 批量读
int i2c_master_recv(struct i2c_client *client,
char *buf, int count);
实际项目中,我更喜欢用i2c_transfer来做批量操作。比如初始化Sensor时,要写几十个寄存器,用i2c_smbus_write_byte_data一个一个写太慢了。
static int sensor_write_array(struct i2c_client *client,
struct regval_list *vals, int len)
{
int i, ret;
for (i = 0; i < len; i++) {
ret = i2c_smbus_write_byte_data(client,
vals[i].reg,
vals[i].val);
if (ret < 0)
return ret;
}
return 0;
}
I2C通信的时序要求很严格。特别是Sensor上电后,需要等待一段时间才能开始I2C通信。我见过有人刚上电就去读Sensor ID,结果读回来全是0xFF。
3.5 SPI通信协议
SPI比I2C快,但线多。它需要4根线:SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。
Sensor驱动里,SPI通常用于高速数据传输,比如高帧率的图像传感器或者需要快速配置的Sensor。
Linux内核的SPI驱动接口:
// 定义SPI传输消息
struct spi_transfer t = {
.tx_buf = tx_buf,
.rx_buf = rx_buf,
.len = len,
.speed_hz = 10000000, // 10MHz
.cs_change = 0,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
// 同步传输
ret = spi_sync(spi, &m);
SPI的时钟极性(CPOL)和相位(CPHA)必须和Sensor匹配。这个在设备树里配:
spi_master: spi@ff110000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "snps,dw-apb-ssi";
reg = <0xff110000 0x1000>;
interrupts = <0 44 4>;
clocks = <&spi_clk>;
num-cs = <1>;
cs-gpios = <&gpio0 5 GPIO_ACTIVE_LOW>;
sensor@0 {
compatible = "adi,adxl345";
reg = <0>;
spi-max-frequency = <5000000>;
spi-cpol; // 时钟极性为1
spi-cpha; // 时钟相位为1
};
};
3.6 知识体系总览
这一章的内容比较多,我画了一张图帮你理清思路:
这张图把环境搭建的四个核心模块串起来了。从上到下,先配好Linux内核环境,再搞定交叉编译工具链,然后通过设备树描述硬件,最后用I2C或SPI和Sensor通信。每一步都环环相扣。
- 环境搭建是基础,工具链版本必须和内核匹配
- 设备树是硬件和驱动的桥梁,compatible字段是匹配关键
- I2C适合配置和低速通信,SPI适合高速数据传输
- 时序和电气特性必须严格遵循数据手册