14、CSI摄像头驱动:摄像头模组选型、设备树配置、v4l2-ctl调试、多摄像头同步采集
CSI摄像头,说白了就是Jetson平台的“眼睛”。
我刚开始接触Jetson时,觉得接个摄像头能有多难?结果第一次点亮IMX219就折腾了一整天。嗯,后来才发现,摄像头驱动这件事,硬件选型、设备树、调试工具、同步采集,每一步都有坑。今天我就把这些经验掰开揉碎了讲给你听。
14.1 摄像头模组选型:别只看像素
选摄像头模组,很多人第一反应是“像素越高越好”。其实在嵌入式视觉里,像素只是其中一个维度。
Jetson平台常见CSI摄像头模组对比
| 模组型号 | 分辨率 | 帧率 | 接口 | 适用场景 |
|---|---|---|---|---|
| IMX219 | 800万 | 30fps | 2-lane CSI | 入门、原型验证 |
| IMX477 | 1230万 | 60fps | 4-lane CSI | 高分辨率、HDR |
| OV9281 | 100万 | 120fps | 2-lane CSI | 高速、全局快门 |
| AR0234 | 200万 | 90fps | 4-lane CSI | 工业检测、运动场景 |
我个人习惯,选型时先问三个问题:
- 你的场景需要全局快门还是卷帘快门? 拍运动物体,全局快门是刚需。我有个项目做流水线检测,用了卷帘快门的IMX219,结果运动模糊严重,后来换了OV9281才搞定。
- CSI lane数够不够? Jetson Nano只有2-lane CSI,Xavier NX有4-lane。你选4-lane的模组,接在Nano上只能跑2-lane模式,分辨率帧率都会受限。
- 驱动支持成熟吗? 我建议优先选NVIDIA官方支持的模组。IMX219、IMX477这些,JetPack自带驱动,省心很多。冷门模组你得自己写驱动,那工作量就大了。
我的经验: 如果你刚开始做Jetson视觉项目,直接买官方的IMX219或IMX477模组。别折腾那些便宜的山寨模组,省下的钱不够你调试的时间成本。
14.2 设备树配置:让内核认识你的摄像头
摄像头接上Jetson,系统不会自动识别。你得通过设备树告诉内核:这里有个摄像头,它用哪个I2C地址,挂在哪个CSI端口上。
设备树文件在Jetson上通常放在 /boot/dtb/ 目录下。以Jetson Nano为例,默认的设备树是 tegra210-p3448-0000-p3449-0000-b00.dtb。
我自己习惯的做法是,先反编译现有的dtb,看看摄像头节点长什么样:
# 反编译设备树
dtc -I dtb -O dts -o tegra210.dts /boot/dtb/tegra210-p3448-0000-p3449-0000-b00.dtb
# 搜索摄像头相关节点
grep -n "cam_i2c\|imx219\|sensor" tegra210.dts
一个典型的IMX219设备树节点长这样:
/* IMX219摄像头节点示例 */
i2c@3180000 {
status = "okay";
imx219_a: imx219@10 {
compatible = "sony,imx219";
reg = <0x10>;
/* CSI端口配置 */
port {
imx219_ep: endpoint {
remote-endpoint = <&csi_ep>;
data-lanes = <1 2>;
link-frequencies = /bits/ 64 <456000000>;
};
};
};
};
/* CSI主机端配置 */
nvcsi@150c0000 {
status = "okay";
port {
csi_ep: endpoint {
remote-endpoint = <&imx219_ep>;
};
};
};
这里有几个关键点:
- reg = <0x10> 是摄像头的I2C地址。IMX219默认是0x10,但有些模组可能是0x20。我遇到过一块板子,I2C地址被改成了0x1a,查了半天才发现。
- data-lanes 指定使用哪几对数据线。2-lane模组用 <1 2>,4-lane模组用 <1 2 3 4>。
- link-frequencies 是MIPI时钟频率。这个值得跟模组的数据手册对上,不对的话图像会花屏。
注意: 修改设备树后,记得重新编译并替换dtb文件。我见过有人改了dts但没编译,重启后摄像头还是没反应。命令是:dtc -I dts -O dtb -o new.dtb new.dts,然后复制到/boot/dtb/目录下。
14.3 v4l2-ctl调试:你的摄像头调试瑞士军刀
设备树配好了,摄像头能不能用?别急着写代码,先用v4l2-ctl探探路。
v4l2-ctl是V4L2框架的命令行工具,Jetson上默认就有。我每次调试摄像头,第一步就是用它:
# 列出所有视频设备
v4l2-ctl --list-devices
# 查看摄像头支持的分辨率和格式
v4l2-ctl -d /dev/video0 --list-formats-ext
# 抓一帧图像保存为raw文件
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=YUYV --stream-mmap --stream-to=frame.raw --stream-count=1
我个人习惯,拿到一个新摄像头,先做这三步:
- 检查设备节点:
v4l2-ctl --list-devices能看到摄像头对应的 /dev/videoX 是哪个。有时候CSI摄像头会映射到video0,有时候是video1,别搞混了。 - 确认格式支持:
--list-formats-ext会列出所有分辨率、帧率、像素格式。如果这里没有你需要的格式,那上层应用肯定也拿不到。 - 抓一帧看看:抓一帧raw数据,用工具转成图片。如果图像正常,说明硬件链路没问题。如果花屏或全黑,那就要检查设备树或硬件连接了。
避坑指南: 我曾经遇到一个情况,v4l2-ctl能列出设备,但一抓图就报错“VIDIOC_STREAMON: Invalid argument”。后来发现是设备树里data-lanes配错了,2-lane模组配成了4-lane。改回来就好了。
14.4 多摄像头同步采集:让两只眼睛步调一致
单摄像头好搞,多摄像头同步才是真考验。双目视觉、3D重建、多视角监控,都需要多个摄像头在同一时刻曝光。
Jetson平台的多摄像头同步,核心在于硬件触发和软件时间戳。
14.4.1 硬件同步方案
最可靠的方式是用一个外部信号同时触发所有摄像头。Jetson的GPIO可以输出PWM信号,接到所有摄像头的TRIGGER引脚上。
设备树里需要配置GPIO功能:
/* GPIO触发引脚配置 */
gpio@2200000 {
camera-trigger-gpios = <&gpio TEGRA_GPIO(S, 7) GPIO_ACTIVE_HIGH>;
};
/* 每个摄像头节点添加触发配置 */
imx219_a: imx219@10 {
...
trigger-mode = <1>; /* 1=硬件触发模式 */
trigger-polarity = <1>; /* 1=上升沿触发 */
};
然后在应用层,用Jetson的GPIO库输出触发信号:
# 使用jetson-gpio库输出触发脉冲
import Jetson.GPIO as GPIO
TRIGGER_PIN = 216 # GPIO(S,7)对应的编号
GPIO.setmode(GPIO.TEGRA_SOC)
GPIO.setup(TRIGGER_PIN, GPIO.OUT)
# 输出30fps的触发信号
import time
while True:
GPIO.output(TRIGGER_PIN, GPIO.HIGH)
time.sleep(0.0001) # 100us脉冲宽度
GPIO.output(TRIGGER_PIN, GPIO.LOW)
time.sleep(0.0333) # 30fps周期
14.4.2 软件同步方案
如果硬件触发不方便,也可以用软件同步。但精度会差一些,大概在几毫秒级别。
我的做法是,用v4l2的 VIDIOC_DQBUF 时间戳来判断帧的对齐情况:
// 多摄像头时间戳对齐示例
struct v4l2_buffer buf1, buf2;
struct timeval ts1, ts2;
// 同时启动两个摄像头的视频流
ioctl(fd1, VIDIOC_STREAMON, &type);
ioctl(fd2, VIDIOC_STREAMON, &type);
while (1) {
// 从两个摄像头各取一帧
ioctl(fd1, VIDIOC_DQBUF, &buf1);
ioctl(fd2, VIDIOC_DQBUF, &buf2);
ts1 = buf1.timestamp;
ts2 = buf2.timestamp;
// 计算时间差
long diff = (ts2.tv_sec - ts1.tv_sec) * 1000000
+ (ts2.tv_usec - ts1.tv_usec);
if (abs(diff) > 5000) { // 超过5ms偏差
printf("帧不同步!偏差: %ld us\n", diff);
// 这里可以丢弃一帧,重新对齐
}
}
核心要点: 硬件同步精度在微秒级,软件同步在毫秒级。如果你的应用需要亚毫秒级同步(比如双目深度估计),必须用硬件触发。
14.5 本章知识体系
下面这张图,把CSI摄像头驱动的整个流程串起来了:
从选型到应用,每一步都环环相扣。选错了模组,后面设备树怎么配都白搭。设备树配错了,v4l2-ctl怎么调都出不来图。同步搞不定,多摄像头应用就是废的。
嗯,CSI摄像头驱动这块,说白了就是“硬件选对、设备树配准、工具用好、同步搞定”。把这四点吃透了,Jetson上的视觉开发你就入门了。