简介:这份资源面向在英伟达Jetson平台上开发计算机视觉与自动驾驶应用的工程师,提供GMSL2相机驱动的完整工作空间源码,用于解决高带宽、低延迟图像传输场景下的驱动适配与编译问题。GMSL2相比传统MIPI CSI-2具备更高数据速率和更长线缆支持,适合远程部署的车载摄像头与ADAS系统。资源包共281个文件,以cmake与make构建脚本为主,辅以sh、bash、zsh等环境配置脚本,以及py、cpp、c源码和launch、xml、pc等ROS相关配置,压缩包约104KB,目录结构保留了catkin工作空间的典型组织方式。已有1583人学习下载。读者可借助其中的ROS驱动源码、编译产物与参数配置,快速完成驱动编译、话题数据流验证与图像调试,并将相机数据接入目标检测、SLAM等上层节点,节省从零搭建驱动环境的时间。
1. 拿到一块 Jetson 和 GMLS2 相机,第一件事该干什么
如果你手头有一块 Jetson 开发板,又刚好拿到一台 GMLS2 接口的工业相机,大概率会经历这样一个晚上:插上线,ls /dev/video*什么都没有,dmesg里也看不到任何跟相机相关的枚举日志。这不是板子坏了,也不是相机坏了,而是 GMLS2 这类工业相机走的是 MIPI CSI-2 或专有串行协议,跟普通 USB 摄像头完全不是一套东西。它需要设备树里预先声明通道、I2C 地址、时钟频率,内核启动时才会去 probe 对应的驱动。这篇笔记就是围绕「Jetson 上把 GMLS2 相机跑起来」这件事,把驱动加载、设备树配置、取流验证这条链路拆开讲清楚,适合正在做边缘视觉项目、需要把工业相机接进 Jetson 的开发者。
2. 先搞清楚 GMLS2 在 Jetson 上的驱动模型
2.1 为什么 GMLS2 不是插上就能用
普通 USB 摄像头遵循 UVC 协议,内核里uvcvideo驱动是通用的,插上就枚举成/dev/video0。GMLS2 这类相机不一样,它通常通过 MIPI CSI-2 接口接入 Jetson 的 ISP 或 VI(Video Input)模块,走的是平台相关的媒体控制器框架。Jetson 的相机栈大致分三层:最底下是传感器驱动,负责通过 I2C 配置相机寄存器;中间是 VI/ISP 驱动,负责接收 CSI 数据流;最上面是 V4L2 子设备,把整条链路暴露给用户空间。
这意味着两件事。第一,内核里必须已经编译了对应传感器的驱动模块,或者你能自己编译一个。第二,设备树里必须有一段描述这个相机怎么连、挂在哪个 I2C 总线、用哪几路 CSI lane、复位脚和时钟怎么接的节点。缺任何一样,相机都不会出现在/dev下。
常见做法是先去确认相机模组的规格书里写的 I2C 从地址和 lane 配置,然后对照 Jetson 官方开发套件的设备树源文件,找到最接近的传感器节点做修改。我一般会先把原厂提供的驱动源码和设备树 patch 拿到手,再决定是直接编译进内核还是做成模块。
2.2 驱动加载的两种路径:内置与模块
在 Jetson 上,传感器驱动可以编译进内核镜像,也可以编译成.ko模块在启动后加载。两种方式各有适用场景。
内置的好处是启动即就绪,不需要额外操作,适合产品化固件。缺点是每次改驱动都要重新烧录或替换内核镜像,调试周期长。模块化的好处是改完insmod就能验证,配合dmesg看 probe 日志非常快,适合开发阶段。
判断当前驱动是哪种状态,可以看/lib/modules/$(uname -r)/下有没有对应的.ko文件,或者直接看内核配置里CONFIG_VIDEO_xxx是=y还是=m。如果规格书里给的驱动源码只有一个.c文件,通常需要自己写一个Makefile把它编成模块。
# 查看当前内核版本和已加载的相机相关模块 uname -r lsmod | grep -i -E "camera|sensor|gmls|imx|ov" # 查看内核配置中相机驱动是内置还是模块 zcat /proc/config.gz | grep -i "CONFIG_VIDEO"这段命令的作用是先摸清底细。uname -r拿到内核版本,后面编译模块时必须用同一版本的头文件。lsmod看有没有已经加载的传感器模块。/proc/config.gz在 Jetson 上通常可用,能直接看到内核编译时的配置项,比去翻源码目录里的.config更可靠。
参数上要注意,如果CONFIG_VIDEO_GMLS2显示为=m,说明模块已经编好了,直接modprobe即可;如果是is not set,那就得自己编译。编译时需要指定KERNEL_DIR为 Jetson 上内核源码的路径,通常是/usr/src/linux-headers-$(uname -r)或者官方 BSP 包解压后的kernel/kernel-5.x目录。
2.3 设备树节点的关键字段
设备树是 GMLS2 能不能被 probe 的核心。一个典型的 CSI 相机节点会包含这些字段:compatible用来匹配驱动,reg是 I2C 从地址,reset-gpios和powerdown-gpios控制上下电时序,clocks指定外部时钟源,port下面的endpoint描述 CSI lane 数量和极性。
我见过最常见的翻车是compatible字符串跟驱动里的of_device_id表对不上,内核直接跳过这个节点,连 probe 都不会调用。另一个高频问题是reg地址写成了 7 位地址但设备树要求 8 位左移格式,或者反过来。还有lane数量跟实际接线不一致,probe 能过但取流时报CSI2 error。
修改设备树后需要重新编译 dtb 并替换/boot/dtb/下的文件,然后重启。验证是否生效可以看/proc/device-tree/下有没有对应节点,或者用i2cdetect确认 I2C 总线上能看到相机的从地址。
# 确认设备树节点是否被内核识别 ls /proc/device-tree/ | grep -i gmls # 查看 I2C 总线上是否枚举到相机 i2cdetect -y -r 1 # 查看内核 probe 日志 dmesg | grep -i -E "gmls|sensor|csi"i2cdetect的-y跳过交互确认,-r使用 SMBus 读方式,某些 Jetson 型号上不加-r会误报。如果i2cdetect能看到地址但dmesg里没有 probe 成功日志,问题多半在设备树字段或驱动匹配上,而不是硬件连接。
3. 从零编译并加载 GMLS2 驱动模块
3.1 准备内核头文件和交叉编译环境
在 Jetson 上直接编译模块是最省事的路径,前提是板子上有内核头文件。如果用的是官方 BSP,头文件通常在/usr/src/linux-headers-$(uname -r)下。如果没有,需要从 BSP 包里把kernel目录拷过来,或者用apt安装对应的linux-headers包。
交叉编译的场景是在 x86 主机上编译好.ko再拷到 Jetson 上。这时需要设置ARCH=arm64和CROSS_COMPILE=aarch64-linux-gnu-,并且KERNEL_DIR指向主机上解压好的 Jetson 内核源码目录。注意源码目录必须已经执行过make prepare和make modules_prepare,否则编译模块时会报缺少Module.symvers或autoconf.h。
# 在 Jetson 本机编译模块 cd gmls2_driver make -C /lib/modules/$(uname -r)/build M=$(pwd) modules # 在 x86 主机上交叉编译 export ARCH=arm64 export CROSS_COMPILE=aarch64-linux-gnu- make -C /path/to/jetson-kernel M=$(pwd) modulesM=$(pwd)告诉内核构建系统只编译当前目录下的外部模块,不会去动整个内核树。-C指定内核源码路径,这个路径下必须有Makefile和Kconfig。如果报ERROR: Kernel configuration is invalid,说明内核源码没有配置好,需要先跑make defconfig或使用 Jetson 的默认配置。
3.2 编写和修改驱动源码的关键点
GMLS2 驱动源码通常由相机模组厂商提供,但往往只适配了某个特定 JetPack 版本。移植到新版本时,重点检查这几个地方:V4L2 子设备注册接口是否有变化,v4l2_i2c_subdev_init的参数在新内核里是否调整过,s_stream回调里的 CSI 开关顺序是否跟新版本 VI 驱动兼容。
另一个容易忽略的是电源管理。Jetson 的相机供电通常由regulator框架管理,驱动里需要通过devm_regulator_get拿到电源并控制使能。如果设备树里没写vdd-supply或者驱动里没调用regulator_enable,相机会因为没电而 probe 失败,但dmesg里可能只报一个模糊的-EPROBE_DEFER。
// 驱动 probe 函数里获取 regulator 并上电的典型写法 static int gmls2_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct gmls2_priv *priv; int ret; priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->vdd = devm_regulator_get(&client->dev, "vdd"); if (IS_ERR(priv->vdd)) { dev_err(&client->dev, "failed to get vdd regulator\n"); return PTR_ERR(priv->vdd); } ret = regulator_enable(priv->vdd); if (ret) { dev_err(&client->dev, "failed to enable vdd\n"); return ret; } /* 后续初始化 I2C 寄存器、注册 V4L2 subdev */ return 0; }这段代码的关键是devm_regulator_get的第二个参数"vdd",它必须跟设备树里vdd-supply = <&cam_vdd>;的属性名一致。regulator_enable之后通常还需要延时几毫秒等电源稳定,再去做 I2C 通信。如果 probe 返回-EPROBE_DEFER,说明 regulator 还没准备好,内核会稍后重试,这时候不要急着判定驱动有问题,先确认供电节点是否被其他驱动先初始化。
3.3 加载模块并确认 probe 成功
编译出.ko之后,用insmod或modprobe加载。modprobe会处理依赖关系,推荐优先用。加载后立刻看dmesg最后几十行,确认有没有probe success或者类似的成功日志。如果只看到probe failed,根据错误码判断方向:-ENODEV多半是 I2C 通信失败,-EINVAL可能是设备树参数不对,-EPROBE_DEFER是依赖没就绪。
# 加载模块 sudo modprobe gmls2 # 查看 probe 结果 dmesg | tail -30 # 确认 V4L2 子设备是否注册 ls /dev/v4l-subdev* media-ctl -pmedia-ctl -p会打印出整个媒体控制器的拓扑结构,能看到 GMLS2 传感器节点、CSI 接收节点以及它们之间的 link 是否 enable。如果拓扑里根本没有传感器节点,说明驱动没注册成功;如果有节点但 link 是 disabled,需要在用户空间用media-ctl --link手动使能,或者检查设备树里endpoint的remote-endpoint是否配对正确。
4. 取流验证与常见问题排查
4.1 用 v4l2-ctl 抓一帧确认链路通
驱动 probe 成功、媒体链路也 enable 之后,下一步是实际抓一帧图像。Jetson 上常用v4l2-ctl来操作 V4L2 设备。先列出所有 video 节点,找到 GMLS2 对应的那个,通常可以通过v4l2-ctl --list-devices看到设备名。
抓帧命令需要指定格式、分辨率和保存文件。GMLS2 常见的输出格式有YUYV、UYVY或者压缩格式。如果格式设错,命令会报VIDIOC_S_FMT: failed。分辨率也要跟相机支持的模式匹配,不能随便写。
# 列出所有视频设备 v4l2-ctl --list-devices # 查看指定设备支持的格式 v4l2-ctl -d /dev/video0 --list-formats-ext # 抓取一帧保存为文件 v4l2-ctl -d /dev/video0 \ --set-fmt-video=width=1920,height=1080,pixelformat=YUYV \ --stream-mmap \ --stream-count=1 \ --stream-to=frame.raw--stream-mmap使用内存映射方式取流,比 read 方式效率高。--stream-count=1只抓一帧就退出,适合快速验证。--stream-to把原始数据写到文件,后续可以用ffmpeg或python转成图片查看。如果命令卡住不返回,多半是 CSI lane 配置不对或者时钟频率不匹配,数据根本没进来。
4.2 避坑:GMLS2 驱动调试中最容易翻车的五个点
现象一:i2cdetect能看到地址,但驱动 probe 一直返回-ENODEV。原因通常是设备树里compatible字符串跟驱动of_device_id表不匹配,内核根本没把i2c_client和驱动关联起来。解决方法是把驱动源码里of_device_id的compatible值抄下来,逐字对照设备树节点,注意大小写和连字符。
现象二:probe 成功,media-ctl -p也能看到节点,但取流时报CSI2 error或timeout。这种情况多半是 lane 数量或极性配置错了。设备树endpoint里的># 用 GStreamer 预览 GMLS2 相机画面 gst-launch-1.0 v4l2src device=/dev/video0 ! \ video/x-raw,width=1920,height=1080,format=YUY2,framerate=30/1 ! \ videoconvert ! xvimagesink # 录制 10 秒视频到文件 gst-launch-1.0 v4l2src device=/dev/video0 num-buffers=300 ! \ video/x-raw,width=1920,height=1080,format=YUY2 ! \ videoconvert ! nvvidconv ! nvv4l2h264enc ! \ h264parse ! qtmux ! filesink location=test.mp4
num-buffers=300在 30fps 下正好是 10 秒。nvvidconv把格式转成 NVMM 内存,nvv4l2h264enc是 Jetson 上的硬件编码器,比软件编码省 CPU。如果v4l2src报not negotiated,说明 caps 跟相机实际输出不匹配,用v4l2-ctl --list-formats-ext确认支持的格式和帧率再改。
5. 把 GMLS2 驱动集成进自己的应用:一个可复用的初始化脚本
5.1 启动时自动加载和配置链路
产品化的时候不可能每次手动modprobe和media-ctl。我一般会写一个 systemd service,在启动时按顺序做这几件事:加载驱动模块、等待/dev/video0出现、用media-ctl配置链路、设置默认格式。这样应用层直接打开设备就行,不用关心底层初始化。
#!/bin/bash # /usr/local/bin/gmls2-init.sh set -e # 加载驱动 modprobe gmls2 # 等待设备节点出现,最多等 5 秒 for i in $(seq 1 50); do if [ -e /dev/video0 ]; then break fi sleep 0.1 done # 配置媒体链路 media-ctl -d /dev/media0 --link "gmls2 1-0036":0->"vi-output":0[1] # 设置默认格式 v4l2-ctl -d /dev/video0 \ --set-fmt-video=width=1920,height=1080,pixelformat=YUYV echo "gmls2 camera initialized"set -e让脚本在任何一步失败时立即退出,避免带着半初始化状态继续跑。media-ctl --link的格式是"实体名":端口->"实体名":端口[使能],实体名可以用media-ctl -p查到。这个脚本可以做成 systemd 的ExecStartPre,或者直接写成一个 oneshot service。
5.2 在 Python 应用里打开相机并读帧
应用层用 OpenCV 是最快的路径,但要注意 OpenCV 的VideoCapture在 Jetson 上默认走 V4L2,需要指定CAP_V4L2后端。如果相机输出的是非标准格式,OpenCV 可能无法直接解析,这时候用 GStreamer pipeline 更稳。
import cv2 # 方式一:直接用 V4L2 后端 cap = cv2.VideoCapture(0, cv2.CAP_V4L2) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080) cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'YUYV')) # 方式二:通过 GStreamer pipeline pipeline = ( "v4l2src device=/dev/video0 ! " "video/x-raw,width=1920,height=1080,format=YUY2 ! " "videoconvert ! appsink" ) # cap = cv2.VideoCapture(pipeline, cv2.CAP_GSTREAMER) ret, frame = cap.read() if ret: cv2.imwrite("snapshot.jpg", frame) cap.release()cv2.CAP_V4L2强制使用 V4L2 后端,避免 OpenCV 自动选到 GStreamer 但 pipeline 没配好。CAP_PROP_FOURCC设置像素格式时,FourCC 码要跟v4l2-ctl里看到的一致。如果cap.read()一直返回False,先用v4l2-ctl确认命令行能抓到帧,排除应用层问题。
5.3 验证驱动稳定性的一个笨办法但有效
驱动调通之后,我习惯做一个 24 小时连续取流测试,看会不会中途断流或者内存泄漏。方法很简单,用 GStreamer 跑一个无限循环,同时用tegrastats监控内存和 CPU。如果连续跑一天不掉帧、内存不涨,基本可以认为驱动稳定。
# 连续取流 24 小时,每 1000 帧打印一次状态 gst-launch-1.0 v4l2src device=/dev/video0 ! \ video/x-raw,width=1920,height=1080,format=YUY2,framerate=30/1 ! \ fpsdisplaysink video-sink=fakesink text-overlay=false # 另开终端监控内存 tegrastats --interval 5000fakesink不显示画面,只消耗数据,适合压力测试。fpsdisplaysink会定期打印实际帧率,如果帧率从 30 慢慢掉到 20 以下,说明有 buffer 泄漏或者 CSI 带宽不够。tegrastats看RAM那一项,如果持续增长不回落,大概率是驱动里某个vb2_buffer没正确归还。
从那以后我每次调新相机驱动,都会先跑一遍 24 小时压力测试再交付,因为很多时序和带宽问题在短时间测试里根本暴露不出来。希望帮到你。
本文还有配套的精品资源,点击获取