☰
OV13850 Linux驱动移植指南:从V4L2 subdev到出帧避坑
2026/9/28 1:35:00 网站建设 项目流程

简介:这是一套面向Linux/Android摄像头驱动开发者的OV13850传感器驱动源码,基于瑞芯微RK3288硬件平台。压缩包共5个文件,整体104KB,包含两个C源文件、一个头文件、一个Android.mk构建脚本和一个XML校准数据文件。驱动源码涵盖了传感器上电、时钟配置、I2C寄存器写入、MIPI链路建立以及图像数据输出等完整流程,并给出了寄存器配置表与私有数据结构定义。已有655人浏览学习,适合正在做瑞芯微平台Camera驱动移植或OV13850方案调试的工程师参考。整包结构精简,目录区分校准与源文件模块,读者既能快速集成到现有Android系统,也可对照源码理解驱动初始化、上电时序与图像采集链路;其中涉及I2C寄存器操作、MIPI收发配置、图像格式转换等关键实现,并配有构建脚本与校准文件,适合作为驱动入门和二次开发的基础材料。

1. OV13850.tar.gz 是什么:一颗 13MP sensor 的 Linux 驱动要怎么落地

拿到一个名为 OV13850.tar.gz 的 Linux 驱动包,往往意味着板子上那颗 1380 万像素的 MIPI 摄像头模组还没被内核认出来。OV13850 在 RK、全志、NXP 这类带 ISP 的嵌入式平台上很常见,驱动要做的不是把 sensor 点亮,而是把它接进 V4L2 框架,让应用层能打开节点、配置格式、持续拿到帧。这篇笔记按接手这类驱动包的实际顺序展开:先弄清 subdev ops 和设备树怎么配,再编译出 ko、用 media-ctl 让 sensor 出流,最后把常见的上电时序、时钟和 I2C 地址坑提前避开。正在做 linux 驱动开发,被这颗 sensor 卡在不出图、花屏或者 probe 失败上的人,按这个路径走能少翻几次车。

2. OV13850 驱动接入 V4L2:subdev 回调、设备树属性和注册顺序

很多第一次接触 sensor 驱动的人,会下意识想找 file_operations 里的 read/write——那是字符设备驱动框架的思维。OV13850 这类 MIPI sensor 在 Linux 里走的是 V4L2 subdev 模型,它不直接暴露 /dev/videoX,而是作为一个 media entity 挂在 /dev/media0 上,由 ISP 的 video 节点把帧收走。驱动包能不能用,先看它有没有把这三件事做对:I2C 总线枚举、subdev ops 注册、设备树 GPIO 和时钟绑定。

2.1 驱动核心就三部分:i2c_driver、v4l2_subdev 与 ops 表

翻开解压后的 ov13850.c,去掉平台附带的那堆寄存器数组,真正干活的骨架一般长这样:

/* ops 表是上层调用的入口,stream on/off 和电源控制都在这里 */ static const struct v4l2_subdev_core_ops ov13850_core_ops = { .s_power = ov13850_s_power, }; static const struct v4l2_subdev_video_ops ov13850_video_ops = { .s_stream = ov13850_s_stream, .g_frame_interval = ov13850_g_frame_interval, }; static const struct v4l2_subdev_ops ov13850_subdev_ops = { .core = &ov13850_core_ops, .video = &ov13850_video_ops, };

这段代码要连着看懂三层。最外层是一个 i2c_driver,probe 时拿到 struct i2c_client,调用 v4l2_i2c_subdev_init 或 v4l2_i2c_subdev_reg 把 subdev 挂到 I2C 总线上。s_power 是上电回调,平台在打开 sensor 时先调它,里面一般做 GPIO 拉高、延时、写一串初始化寄存器;s_stream 才是出流的开关,video 节点 stream on 时,V4L2 会按媒体拓扑从 sensor 到 ISP 依次调用各 subdev 的 s_stream(true)。所以排查不出图时,不要在应用层瞎猜,先在 s_stream 里打一条 dev_info 看它到底有没有被调到。

ops 表之外,驱动里还会有一个 v4l2_ctrl_handler,负责曝光、增益、白平衡这些控制项。OV13850 的曝光和增益是写在寄存器里的,ctrl 回调的 set 函数最后都会落到 i2c 写寄存器。判断一个驱动包是不是完整,就看它是不是同时给了 i2c_driver、subdev ops 和 ctrl handler——很多网上下载的“驱动”只有寄存器表,没有 ops 注册,那种东西上板子是跑不起来的。

2.2 设备树里给 sensor 开的卡片:clocks、pwdn、reset 与 lane 数

驱动能不能 probe 成功,一半决定在设备树上。OV13850 挂在某个 I2C 控制器下面,同时需要一路 MCLK、一路 PWDN、一路 RESET。一个最小 dts 节点通常长这样:

&i2c0 { status = "okay"; ov13850: ov13850@10 { compatible = "ovti,ov13850"; reg = <0x10>; clocks = <&cru SCLK_CAM0>; clock-names = "xvclk"; assigned-clocks = <&cru SCLK_CAM0>; assigned-clock-rates = <24000000>; pwdn-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>; reset-gpios = <&gpio1 6 GPIO_ACTIVE_LOW>; pinctrl-names = "default"; pinctrl-0 = <&cif_clk_pin>; port { ov13850_out: endpoint { remote-endpoint = <&mipi_csi2_in>; >tar xzvf OV13850.tar.gz find . -type f | grep -iE 'ov13850|dts|readme|README|patch' | sort

一个能用的包,通常包含 drivers/media/i2c/ov13850.c、头文件(里面是寄存器表)、平台 dts 的 diff,以及一份说明文档。先别急着把 .c 拷过去,打开文件头看两件东西:兼容字符串,比如 “ovti,ov13850”;以及 include 了哪些内核头文件。v4l2_subdev 的 ops 结构在 4.x 内核改过几次,驱动若按较老内核写成 s_power 直接挂在 core ops 上,在新内核上可能编译报错或者运行时不回调,这种情况要先找平台 SDK 内核版本对应。

3.2 内核配置:把 ov13850 编成模块还是内建

OV13850 在 kernel Kconfig 的主菜单路径一般是:

Device Drivers → Multimedia support → Media driver types → Camera sensor devices → OV13850 sensor support

不少平台把 sensor 菜单放在 I2C camera sensors 下,入口名字可能叫 OmniVision OV13850 support。选成模块会生成 ov13850.ko,选内建则进 zImage。我一般选模块,原因很实际:调 dts 时不用反复烧整个镜像,改完 dts 后重新编 dtb 即可,ko 和 dtb 分开更新,翻车半径小。

对应 Kconfig 和 Makefile 的写法一般是这样的:

# drivers/media/i2c/Makefile obj-$(CONFIG_VIDEO_OV13850) += ov13850.o

编之前确认内核源码里已经加了这段,否则 menuconfig 里看不到选项。平台 SDK 目录下执行:

export ARCH=arm64 export CROSS_COMPILE=aarch64-linux-gnu- make menuconfig make Image dtbs modules -j$(nproc)

ARCH 决定内核和 ko 的体系结构,CROSS_COMPILE 是交叉编译工具链前缀,这两行错一个,编译出的模块加载时就会报 unknown symbol 或者 Invalid module format。make Image 编内核、dtbs 编设备树、modules 编全部 ko,-j 并行数按机器内存给。

提示:如果 ARCH 写成了 arm 而板子是 64 位,ko 能编出来但 insmod 会直接拒绝,不用怀疑工具链坏了。

3.3 把 dts 节点挂到具体板子上:一份最小补丁的写法

板级 dts 通常在 arch/arm64/boot/dts/rockchip/ 或 vendor 目录里。找到 sensor 要挂的那条 I2C 总线,比如 i2c0,按第 2.2 节的模板把 ov13850@10 节点加进去,还要在同一文件的某个 pinctrl 节点确认 MCLK 引脚的复用。平台 SDK 里常给一个 camera 的 dtsi,把通用 sensor 配置放那里,board dts 里只引用。

补完 dts 后重新编 dtb:

make dtbs

把新 dtb 烧到板的 boot 分区。如果整个流程是 SDK 的编译脚本在管理,就找到它里面对应 dtb 的输出目录,把 dtb 单独拷贝出来,用板子自带工具更新。不烧 dtb 只换 ko,多半会发现 dts 里的节点根本没生效。

3.4 把 ko 送进 rootfs:scp、modinfo 与 dmesg 验证

dtb 更新完成后,把驱动模块装进板子:

scp drivers/media/i2c/ov13850.ko root@<board-ip>:/lib/modules/$(uname -r)/extra/ ssh root@<board-ip> "depmod -a && modprobe ov13850"

scp 是板子和主机之间拷文件最简单的方式;depmod 重建模块依赖,modprobe 按名字加载。加载后立刻看:

ssh root@<board-ip> "dmesg | grep ov13850"

正常会看到“ov13850 0-0010: probing v4l2 subdev”这类日志,后面跟着 v4l2_ctrl 注册信息。如果 dmesg 没有任何输出,先看 modprobe 有没有找到模块、I2C 地址对不对,不要直接进应用层。到这一步,驱动已经在内核里活过来了,接下来要解决的是让它出帧。

4. 让 OV13850 真正出帧:media-ctl 拓扑、v4l2-ctl 与格式核对

probe 成功不代表能出图,很多驱动包就是卡在“注册了但没帧”这一步。原因大多是媒体链路没拉起来,或者 subdev 的 pad 格式没有设置。出帧这件事要按顺序做:确认节点、建立 link、设置格式、拉流。

4.1 先确认设备节点和拓扑:dmesg 与 media-ctl -p

加载驱动后,先确认内核里出现了哪些新设备:

dmesg | grep -E 'ov13850|media|csi' ls -l /dev/v4l-subdev* /dev/video* /dev/media* media-ctl -d /dev/media0 -p

media-ctl -p 的输出里会列出每个 entity,sensor 一般叫 ov13850 0-0010,后面跟着 pad0。这一行要能看到,说明 subdev 注册进了拓扑。看不到的话,优先级最高的问题是 dts 里节点没贴到对应的 i2c 控制器上,或者驱动里 entity name 初始化失败。

注意板子上可能不止一个 media device:sensor 走 ISP 的 /dev/media0,USB 摄像头是 /dev/media1。用 -d 参数指定,别拿默认节点拉错链路。

注意:如果 ls /dev/media* 没有任何节点,先回内核配置开 CONFIG_MEDIA_CONTROLLER,子设备 API 没开,后面所有命令都白搭。

4.2 在 media 拓扑里把 OV13850 接到 ISP:link 与 pad 格式

拓扑是静态的,还需要把 sensor pad 和 ISP 入口用 link 连起来。我一般用这三条命令:

media-ctl -d /dev/media0 -r media-ctl -d /dev/media0 -l "'ov13850 0-0010':0 -> 'mipi csi2':0[1]" media-ctl -d /dev/media0 -V "'ov13850 0-0010':0[fmt:SRGGB10_1X10/1920x1080]"

第一条 -r 是清除所有 link 设置,回到出厂拓扑;第二条 -l 建立 sensor pad0 到 CSI 接收端 pad0 的链路,[1] 表示 enable。第三条 -V 设置 sensor pad0 的输出格式和分辨率,SRGGB10_1X10 对应的就是 OV13850 的 RAW10 Bayer 输出,Bayer 顺序是 RGGB。实体名字里的 0-0010 是 i2c 总线号和地址,以自己的 media-ctl -p 输出为准,平台不同名字略有差异。如果平台 ISP 支持,部分 SoC 会把 sensor 配成 UYVY,但那是 ISP 后处理的结果,对 OV13850 这类 raw sensor,别跳级配成 YUYV 去拉流。

4.3 用 v4l2-ctl 拉流并核对帧尺寸

拓扑和格式设置完,在 video 节点上抓帧:

v4l2-ctl -d /dev/video0 \ --set-fmt-video=width=1920,height=1080,pixelformat=RG10 v4l2-ctl -d /dev/video0 \ --stream-mmap --stream-count=30 \ --stream-to=/tmp/ov13850_1080p.raw

--set-fmt-video 里的 pixelformat 要写 RG10,这是 V4L2 里对 RAW10 四字符码的约定写法,在应用层它代表每个像素占两个字节、高位填充。--stream-count=30 是抓 30 帧,不设置的话会一直跑,调试时容易把 emmc 写满。跑完后检查文件大小:

ls -l /tmp/ov13850_1080p.raw

1920x1080 的 RAW10 每帧是 192010802 字节,因为没有压缩,这是一条很好用的自检公式——文件大小不是这个值,说明像素格式或者分辨率没真正生效,不要急着怪驱动。我见过有人把 pixelformat 写成 RGGB 或者 GREY,命令能过但抓下来的全是空帧,多半就是这行写错。

4.4 把 RAW 转成人能看的图:验证用的最省事路径

v4l2-ctl 抓下来的是 sensor 直接吐的 Bayer 数据,在 PC 上最省事的方式是用支持 Bayer 的查看器直接打开,或者交给 ffmpeg 做 demosaic:

ffmpeg -f rawvideo -pix_fmt bayer_bggr8 \ -s 1920x1080 -i /tmp/ov13850_1080p.raw \ -vf "format=rgb24" /tmp/ov13850_1080p.png

这里 bayer_bggr8 是按常见 Bayer 顺序写的,实际顺序以 OV13850 的配置为准,SRGGB 对应的是 bayer_rggb8,不同平台 ISP 的输出顺序可能被调整过。如果转出来的图颜色不对但物体轮廓清晰,说明 sensor 链路是通的;颜色全乱,通常是 bayer 顺序或者格式位宽标错;图里全是横条纹,就要回到 PCLK 和 MIPI lane 的问题上。这节的目的是验证链路,不是做 ISP,跑通后把中间验证命令固化成一个脚本,后面每次改完 dts 都重跑一遍。

5. OV13850 驱动移植避坑:电源时序、时钟频率、I2C 地址与 MIPI 参数

这一章是最容易让整块板子看起来像报废的地方。OV13850 这类 raw sensor 对模拟电源和时钟的敏感度远高于 USB 摄像头,probe 失败、花屏、黑屏在多数情况下不是代码逻辑错,而是时序和电参数没满足。以下 4 条是这些年反复遇到的坑,按现象、原因、解决办法写。

5.1 probe 时好时坏,首轮开机认到、重启后丢失

现象:刚烧完系统第一次启动 dmesg 里能看到 ov13850 probing v4l2 subdev,reboot 之后同样的 dts 和 ko 却报 read reg error,再断电重新上电又好了。

原因:上电时序没满足。OV13850 对电源的预期顺序大体是 AVDD、DOVDD、DVDD 先建立,MCLK 稳定后 PWDN 拉低释放,RESET 再拉高完成复位,每两步之间要求毫秒级延时。板级 dts 里如果用了 regulator 的 boot-on,但 GPIO 控制的 PWDN 提前被驱动 s_power 释放,sensor 内部上电复位还没完成,I2C 第一次访问自然超时。重启时内核时钟和 regulator 的初始化顺序变了,问题就暴露出来。

解决:把 PWDN 和 RESET 的 GPIO 控制收进驱动,而不是在 dts 里用默认状态硬拉。驱动里 s_power(true) 的顺序固定为:先把 pwdn 置为有效,开启 clocks,再延时 10ms,然后置无效 pwdn、释放 reset,再延时 20ms,最后才去写 sensor 寄存器。我用过的一种写法是:

static int ov13850_s_power(struct v4l2_subdev *sd, int on) { gpiod_set_value_cansleep(ov13850->pwdn, on ? 0 : 1); if (on) { usleep_range(10000, 12000); gpiod_set_value_cansleep(ov13850->reset, on ? 1 : 0); usleep_range(20000, 22000); } return 0; }

注意 dts 里 pwdn-gpios 是 GPIO_ACTIVE_LOW,所以“置为有效”在逻辑层是 0,gpiod_set_value_cansleep 会帮你做硬件电平翻转,不要在驱动里再写一次 !。改完后再用 reboot 和 cold boot 两种方式各验证三轮,别再只看一次开机日志。

5.2 MCLK 给成 27M,花屏和条纹来回换花样

现象:链路能出帧,但画面有斜向条纹,或者分辨率高了直接没有 PCLK;把分辨率降到 VGA 又正常,但帧率也不对。

原因:OV13850 内部的 PLL 配置是按某个输入频率算好的,常见是 24MHz 或 27MHz。寄存器表里的 PLL 倍频和分频只对其中一个频率成立,如果 dts 的 assigned-clock-rates 给了 27MHz,而驱动寄存器表按 24MHz 设计,MIPI 的 data rate 就会整体偏移,表现为花屏、条纹或带宽不够。VGA 正常是因为低分辨率下时序余量大,把问题掩盖了。

解决:先用 linux 常用命令确认实际时钟到底是多少:

cat /sys/kernel/debug/clk/clk_summary | grep -i xvclk

然后对照寄存器表确认 PLL 是按哪个频率计算的。多数发布包里 README 会写 24M crystal required 之类的话,没有 README 就看 s_stream 里写的 pll 寄存器值,反推频率。改 dts 里的 assigned-clock-rates 比改寄存器表安全,因为时钟源在 SoC 侧改起来最快,但要注意确认 I2C 读到的是不是同一个时钟树,有些平台 sensor 时钟和 MCLK 引脚不复用,改了 timeout 反而起不来。

5.3 I2C 地址 0x10 和 0x20 来回打架

现象:dmesg 一直是 ov13850: i2c read error,但 i2cdetect 在总线上明明能看到 0x10 有一个设备,且驱动反复 probe 都失败。

原因:OV13850 的 7 位 I2C 地址是 0x10,8 位写地址是 0x20,读地址是 0x21。设备树 reg 属性按内核规则填 7 位,也就是 0x10;但有些 SDK 的驱动在 probe 里又手动算了一次地址:读到 client->addr 后再 <<1,于是实际发出的事务落在 0x20 或者 0x21,I2C 控制器里又不做修正,总线事务自然没有 ACK。还有的板级 dts 直接把 reg 写成 0x20,内核后面访问时再移位,发的地址就变成 0x40,在总线上根本不存在。

解决:一律在 dts 写 reg = <0x10>,驱动里不要再对 client->addr 做算术。I2C core 会在收发时自动完成 7 位到 8 位的转换。写完以后用 i2cdetect -y -r 0 确认总线上 0x10 有设备;再在驱动 probe 里打印一条 client->addr,看到的值必须是 0x10。如果客户改了模组地址,比如 SA0 上拉把地址变成 0x12,记得 dts 和驱动两边一起改,别只在一边动手。这里还有个迷惑点:i2cdetect 显示的是 7 位地址,v4l2-subdev 的 entity name 里 0-0010 的 0010 也是 7 位,两边对不起来时先统一语境。

5.4 MIPI lane 数与 data rate 不匹配:高分辨率黑屏,低分辨率正常

现象:1920x1080 还能偶尔出图,切到 2560x1440 或者更高就彻底黑屏,media-ctl 把格式设完后 dmesg 报 CSI 超时;换一块只有 2-lane 的板子,同样的 dts 在 4-lane 配置下全黑。

原因:OV13850 模组在硬件上定义了 MIPI lane 连接,常见 4-lane 和 2-lane 两种;同时驱动里 link-frequencies 要和 SoC CSI 接收能力匹配。dts 里>for r in 640x480 1280x720 1920x1080 2560x1440; do w=${r%x*}; h=${r#*x} v4l2-ctl -d /dev/video0 --set-fmt-video=width=$w,height=$h,pixelformat=RG10 timeout 2 v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=5 >/dev/null || echo "fail at $r" done

这条脚本要在 rootfs 里放好,它比任何测速工具都能更快暴露 lane 和速率问题。如果某个分辨率失败但低一档正常,先怀疑 vblank/hblank 配置,驱动里不同分辨率往往对应不同的 vts 和 hts,这两个值决定 PCLK 和 MIPI 吞吐,不能只改 width 和 height。这是我在 lane 问题上最疼的一条血泪经验:硬件排线到模组之间经常只接了两对差分,光改 dts 没用。

6. 验收前调好这 3 个点:曝光增益、分辨率切换与稳定性

驱动能出帧只算及格,能交付还要再过三关。

6.1 先验证曝光和增益的链路通不通

用 v4l2-ctl 在 subdev 节点上直接打控制命令:

v4l2-ctl -d /dev/v4l-subdev0 --set-ctrl=exposure=1000 v4l2-ctl -d /dev/v4l-subdev0 --get-ctrl=exposure

如果驱动里的 v4l2_ctrl_handler 没有注册 V4L2_CID_EXPOSURE,命令会直接报错。参数能设置成功,再把镜头对向亮处和暗处,观察帧平均亮度是否变化。只出图不能调曝光,这个驱动离验收还差一大截。

6.2 分辨率切换前,先把流停干净

切分辨率最常见的失败方式是黑屏。我的习惯是先 stream off,sleep 200ms 让 sensor 把残留的帧排空,再 set format,再 stream on。放在脚本里就是:

v4l2-ctl --stream-off sleep 0.2 v4l2-ctl --set-fmt-video=width=640,height=480,pixelformat=RG10 v4l2-ctl --stream-mmap --stream-count=5

多次切换后仍然黑屏,查 s_stream(false) 的实现是不是把寄存器表恢复到了初始状态,以及 CSI 接收端有没有在 stream off 时清掉 buffer。

6.3 压测 30 分钟,用 dmesg 抓瞬时错误

交付之前我会让板子跑一轮长时间抓帧,同时开 dmesg -w 盯着看:

dmesg -w | grep ov13850 & timeout 1800 v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=3000

帧计数和文件大小都要连续,中途出现 timeout、lost frames 就要回到时钟和 lane 上查,而不是在 app 层打补丁。这三个点是我现在拿到任何 sensor 包都先做的验证,OV13850 也不例外。链路通了、参数能控、长时间不掉帧,再交给应用层做预览或抓拍,心里才有底。希望帮到你。

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

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

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

立即咨询