☰
Jetson CSI相机驱动从内核模块到设备树:GMLS2避坑指南
2026/10/20 3:38:01 网站建设 项目流程

简介:在嵌入式视觉应用中,GMSL2接口以高带宽、低延迟特性成为Jetson平台车载相机接入的主流方案。该驱动资源包面向Jetson开发者与ROS使用者,解决GMSL2相机在JetPack环境下的驱动编译、参数配置与图像话题获取问题。资源共281个文件,包体仅104KB,包含cmake构建配置、make脚本、launch启动文件、Python测试节点、bash环境设置及catkin工作空间元文件,可支撑从环境准备到节点集成的完整流程。目前已有1584人学习下载。包内my_publisher与my_subscriber示例展示了图像话题的发布订阅写法,便于快速验证驱动;目录结构保留catkin_workspace、built_by等元信息,适合对照理解ROS工作空间组织方式。对于正在搭建GMSL2相机驱动环境、需要参考源码结构或排查编译问题的开发者,这套资源提供了紧凑而可用的参考样板。

1. 拿到 Jetson 的 GMLS2 相机驱动包,我第一反应是装完就能出图像,结果翻车了

拿到 GMLS2 相机驱动包时,我以为和普通摄像头一样,把驱动文件放进系统、重启、打开 /dev/video0 就能采图。结果按说明走到第三步就卡住:内核模块编译报错、设备树 overlay 没生效、传感器一直枚举不到。折腾两天才摸清,GMLS2 在 Jetson 平台上不是一款用户态驱动,而是需要围绕 JetPack 的 L4T 内核做模块编译、设备树配置和 I2C 参数联调的 CSI 相机驱动。它适合正要把工业相机集成到 Jetson 视觉设备、做产线视觉、或者维护机器人视觉模组的开发者。这篇笔记会告诉你驱动包为什么不是装完即用、哪些参数必须改、以及排错时先查哪条链路。

2. 先看懂驱动构成:GMLS2 驱动不是一个 .so,是一整套内核模块链

2.1 驱动包里到底有什么

解压 GMLS2 驱动包后,目录结构大致是这几块:

目录 / 文件作用
module/内核模块源码,对应 sensor 驱动主体
dts/设备树 overlay 源文件示例,用于声明外接 sensor
firmware/相机初始化寄存器序列与固件描述
scripts/一键编译与安装脚本,但版本匹配做得不足
README.md版本配对表与接线说明

很多人拿到包就直接跑scripts/install.sh,以为脚本会帮你处理一切,实际脚本只做了「编译模块 + 拷贝 .ko 到系统目录」两件事,设备树 overlay 和 I2C 地址都没法自动适配,必须根据你的 Jetson 型号和相机接口手动改。

2.2 为什么不能把它当普通摄像头驱动用

GMLS2 相机走的是 CSI 接口,Jetson 上 CSI 相机驱动要注册进 Linux 的 media 子系统,由 v4l2 subdevice 节点暴露给用户态。这不是apt install能解决的,必须是内核模块。内核模块有个硬性约束:编译时的内核版本和运行时的内核版本必须一致。Jetson 的内核由 JetPack 决定,同一个相机驱动包,在 JetPack 5.0 上编译出的 .ko,放到 JetPack 5.1 上直接 refuse to load,因为 vermagic 对不上。

我一般会先确认目标板当前内核版本:

uname -r # 输出类似 5.15.0-tegra cat /proc/device-tree/model # 输出类似 Jetson Xavier NX Developer Kit

第一个命令告诉你内核版本,第二个命令告诉你板卡型号。实际项目里,很多开发者把驱动包在 PC 上交叉编译完直接扔到 Jetson 上,insmod 报Invalid module format或version magic mismatch,问题就出在没核对这两项。正确做法是:在 Jetson 板端查看版本,下载对应版本的驱动源码,再编译。

2.3 整条工作链路:从开机到出图是谁在起作用

GMLS2 相机在 Jetson 上从上电到出图像,要经过一条完整的链路,任何一环断了都会导致无图。

  1. bootloader 加载设备树,内核读compatible字段匹配驱动
  2. I2C 控制器探测 sensor 地址,命中后触发驱动probe
  3. 驱动执行初始化寄存器序列,配置 CSI lane 数和时钟
  4. 驱动注册 v4l2 subdevice,建立 media controller 拓扑
  5. 用户态程序打开/dev/video0,申请 buffer,开始采图

第 1 步和第 2 步是失败重灾区。设备树 overlay 没打进去,内核根本不知道有这颗 sensor;I2C 地址对不上,probe 直接失败,dmesg里看不到probe success。

我排查这类问题有个固定动作,先看 dmesg 里有没有 sensor 字样:

dmesg | grep -i gmls2 dmesg | grep -i "i2c.*probe\|csi.*probe"

如果输出为空,说明设备树 overlay 没生效,或者 I2C 探测根本没走到。如果看到probe failed,一般是地址冲突或者 reset GPIO 配置不对。这一条链路上,设备树和 I2C 是优先排查的对象,模块源码本身比较少出问题,因为出厂前厂商已经调过一遍。

3. 不要急着编译:版本配对矩阵决定你能不能省下两天时间

3.1 先从三张表里找出你的组合

GMLS2 相机驱动在不同 JetPack 版本上的编译结果不通用,其原因不在驱动源码本身,而在 Linux 内核接口。比如struct v4l2_subdev_ops在不同内核版本里字段有变化,device_tree的reset-gpios解析接口也不同。

版本配对的基本逻辑是:

Jetson 平台JetPack 版本L4T 内核版本GMLS2 驱动包版本
较新模组5.1+5.10/5.15随包标注为 v2.x 及以上
较旧模组4.64.9v1.x

注意,驱动包版本不是越高越好。我见过有人拿着新版本驱动包去配旧 JetPack,结果驱动里用了一个v4l2_async_nf_add_i2c新接口,旧内核根本不存在这个符号,编译直接失败。反向操作更常见:旧驱动包配新内核,probe时报unknown field错误。

建议拿到驱动包先做一件事:打开 README 或module/Makefile,看它声明的内核版本 range,再用uname -r比对当前系统。范围以外,不要抱侥幸心理,老老实实换 JetPack 版本,或者找厂商要适配版本。

3.2 完整编译安装过程(板载编译版)

我通常用板载编译而不是 PC 交叉编译,原因很简单:交叉编译器版本与内核工具链不一致时,会出现ftsafe等诡异问题,玄学难查。板载编译只要把内核源码放到位就能跑。

首先准备内核源码,JetPack 自带源码包,一般位于/usr/src/目录。

ls /usr/src/ # 期望看到类似 linux-tegra 或 kernel-5.15 的目录 cd /usr/src/linux-tegra

然后导出编译环境变量:

export ARCH=arm64 export LOCALVERSION=-tegra export CROSS_COMPILE= make tegra_defconfig make modules_prepare

这里有几个关键参数要说明:

  • ARCH=arm64:Jetson 是 64 位 ARM 架构,不设会默认 x86,编译出的模块直接报废。
  • LOCALVERSION=-tegra:这个必须和内核 release 版本后缀一致,否则编译后的模块 vermagic 对不上。怎么看?uname -r里的-tegra后缀就是它。
  • CROSS_COMPILE=:板载编译时留空,如果你是 PC 交叉编译,要填成aarch64-linux-gnu-前缀。

modules_prepare的作用是生成模块编译所需的头文件和符号表,不跑这一步直接编译驱动,会报一堆missing include。

接下来进入驱动源码目录编译:

cd /path/to/gmls2_driver/module make -j4

如果一切顺利,会生成*.ko文件。完成后先不要急着安装,直接加载测试:

sudo insmod gmls2.ko dmesg | tail -50

看到gmls2 probe success或类似日志,说明驱动本身是活的。如果insmod报Exec format error,九成是 ARCH 或 LOCALVERSION 没配对。

3.3 安装驱动并确认设备节点

确认 probe 成功后,再进入正式安装环节:

sudo cp gmls2.ko /lib/modules/$(uname -r)/kernel/drivers/media/i2c/ sudo depmod -a sudo modprobe gmls2

三步的含义分别是:把模块放进标准模块目录、刷新模块依赖、按名加载。注意modprobe比insmod好用的地方在于它会自动处理依赖,如果 gmls2 依赖别的符号,modprobe 会帮你先加载依赖模块。

此时检查 v4l2 设备:

v4l2-ctl --list-devices

如果看到类似gmls2 (platform:tegra-capture)这样的条目,说明 v4l2 已经识别到。没有看到,问题大概率不在驱动,而在设备树 overlay 或者 I2C 配置,进入下一章处理。

4. 设备树与 I2C 参数配置:决定相机能否被枚举到的关键一步

4.1 为什么 Jetson 非要 overlay 不可

Jetson 的外设配置不是传统 ARM 那种直接把 device tree 编死到 dtb,而是用configfs动态叠加 overlay。GMLS2 相机作为 sensor,必须有一个 overlay 声明它的存在,包括它挂在哪个 I2C 总线上、使用哪些 GPIO、占哪几个 CSI lane。

没有 overlay,Jetson 内核的 tegra-capture 驱动根本不知道有 sensor 存在,/dev/video0大概率不存在。很多开发者编译完驱动发现找不到设备,问题就出在这一步。

overlay 源文件一般在驱动包的dts/目录下,文件名形如tegra234-gmls2-overlay.dts。拿到后需要针对自己的模组做修改。

4.2 一个可复现的 overlay 修改示例

以下是我在项目里用过的最小可用 overlay,关键位置都有注释:

/dts-v1/; /plugin/; / { overlay-name = "GMLS2 Sensor"; compatible = "nvidia,p3768"; fragment@0 { target-path = "/i2c@3180000"; __overlay__ { #address-cells = <1>; #size-cells = <0>; status = "okay"; gmls2: gmls2@10 { compatible = "vendor,gmls2"; reg = <0x10>; reset-gpio = <&gpio 41 GPIO_ACTIVE_HIGH>; pwdn-gpio = <&gpio 42 GPIO_ACTIVE_HIGH>; clock-frequency = <24000000>; port { gmls2_out: endpoint { remote-endpoint = <&csi_csi_port0>; >dtc -@ -I dts -O dtb -o gmls2.dtbo gmls2.dts sudo cp gmls2.dtbo /boot/

部分新版本 Jetson 还支持在模块目录里合并,具体看你的 JetPack 的 bootloader 版本。

4.3 I2C 地址冲突与时钟频率的坑

overlay 里写的 I2C 地址是不是能正常工作,要实际扫总线。Jetson 上常用i2cdetect工具确认设备地址:

i2cdetect -y -r 7

这里的 7 是 I2C 总线号,不同模组不一样,可以通过ls /dev/i2c-*确认。如果扫描结果里在多处出现地址,说明总线配置有问题;如果扫描地址和你写在 dts 里的reg不一致,以扫描结果为准,但要排除板子上其他器件的地址冲突。

地址冲突的表现是:probe 不报错,但寄存器读写返回的数据完全不对。原因往往是有两个器件共用了同一个 I2C 地址,一个的应答干扰了另一个的访问。排查办法是断开其他器件,只保留 GMLS2 再扫一次。

时钟频率也要注意。Jetson 的 I2C 时钟默认可能是 100kHz 或 400kHz,GMLS2 这类 sensor 一般要求时钟频率不能超过额定值。改错会导致 sensor 寄存器偶发读写失败,表现为有时出图有时黑屏。稳妥的做法是保持 schema 里的clock-frequency与相机规格书一致,不要想当然地「超频」。

5. 避坑与排查:五条实测踩坑记录,按现象、原因、解决排序

5.1 编译模块报错:missing include、unknown field满天飞

现象:编译 gmls2.ko 时,报一堆头文件缺失,或者指向struct里不存在的字段。

原因:有坑坐实了,内核源码没有先执行modules_prepare,编译驱动时缺少生成好的头文件与符号表。更常见的是内核源码版本和当前运行内核不一致——源码包是 5.10,系统跑的是 5.15。

解决:回到/usr/src/linux-tegra(或对应源码目录),先确认当前目录的源码分支版本和uname -r一致,再执行make tegra_defconfig && make modules_prepare。每次切换内核源码版本后,都要重跑这两步,不能缓。

5.2 overlay 编译成功并拷贝到 /boot,但 dmesg 里完全没有 gmls2 字样

现象:重启后dmesg | grep gmls2为空,/dev/video0不出现。

原因:新版本 Jetson 的 bootloader 只从kernel-dtb目录加载 overlay,或者需要指定 overlay 名称列表,光把 dtbo 拷到 /boot 下并不会自动加载。

解决:先确认你的载板对应的 overlay 加载方式。常见做法是修改/boot/extlinux/extlinux.conf,在FDT或OVERLAYS字段加上 gmls2。修改后重启再检查。注意,extlinux.conf 里FDT指定的基础 dtb 文件也要对,基础 dtb 对应错,overlay 打不进去。

5.3 i2cdetect 能扫到地址,但 probe 就是失败

现象:I2C 扫描能看到地址,dmesg 出现probe failed或chip ID mismatch。

原因:最常见的是reg地址写对了,但 reset GPIO 和 pwdn GPIO 顺序颠倒。GMLS2 在 probe 时会先拉高 reset 释放复位,再读寄存器验证 ID。GPIO 接反,读到的寄存器全为零,自然验证不通过。

解决:用逻辑分析仪或者连续打印 GPIO 电平的方式,确认 reset 引脚在 probe 时确实出现过电平翻转。我一般会先在 dts 里把 reset-gpio 严格对照载板原理图走一遍,名字都写上注释,不靠猜。

5.4 图像能出但花屏、条纹、颜色偏绿

现象:v4l2-ctl 能看到设备,但采集到的图像是花的,或者条纹状。

原因:CSI>v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=GRBG10 v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=1 --stream-to=/tmp/gmls2.raw

pixelformat按 GMLS2 规格书填,常见的是 raw Bayer 格式。这里GRBG10表示 10 位 Bayer 排列,如果格式不对,采出来的图会错位严重,哪怕是真数据也像花屏。

然后用 Python 把这个 raw 文件转成可视图像:

import numpy as np import cv2 raw = np.fromfile('/tmp/gmls2.raw', dtype=np.uint8) img = raw.reshape(1080, 1920) img16 = (img.astype(np.uint16) * 4).astype(np.uint16) color = cv2.cvtColor(img16, cv2.COLOR_BayerGRBG2BGR) cv2.imwrite('/tmp/gmls2.png', color)

这段代码做的事很简单:把裸数据按宽高重排成二维数组,做一个 10 位到 16 位的左移,再用 OpenCV 做 Bayer 去马赛克。如果你是RGGB排列,把cv2.COLOR_BayerGRBG2BGR换成cv2.COLOR_BayerRGGB2BGR。

正常出图后,再验证一下 media 拓扑是否完整:

media-ctl -p /dev/media0

看输出里有没有gmls2节点以及它到tegra-capture的 link 状态。链路不生效的典型现象是 v4l2-ctl 能打开设备但streamon超时,这时问题多数在 CSI 虚通道配置。

从那以后,我每次接 Jetson 相机驱动项目,都强制先走一遍版本配对矩阵、再动设备树 overlay、最后才编译模块,顺序反了就是给自己挖坑。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询