
简介这是一份面向嵌入式驱动开发者的OV5645 MIPI YUV摄像头驱动源码基于MIPI接口与YUV色彩空间适用于手机、平板等移动设备的相机模块适配与调试也可作为学习MIPI摄像头驱动的入门参考。该驱动负责传感器识别、初始化、数据传输、缓冲管理及同步信号处理是摄像头正常成像的关键软件层。资源包共4个文件由3个头文件与1个C源文件组成头文件分别对应传感器寄存器参数、平台采集参数及定制化接口配置C文件则实现了传感器初始化、MIPI数据接收、图像格式解析与电源管理逻辑整体约40KB代码量小巧精炼便于阅读和移植到不同平台。已有583人学习下载。通过学习该驱动开发者可以快速理解OV5645在MIPI YUV模式下的工作流程掌握分辨率、帧率、曝光时间等关键参数的配置方法并参考其缓冲管理与同步信号处理思路用于相机驱动开发、项目调试或作为自有代码的移植蓝本同时可借鉴其中的错误处理与电源管理设计优化系统运行时的稳定性与功耗表现。1. ov5645_mipi_yu驱动为什么先查 MIPI 信号而不是先查驱动代码调试 ov5645 这颗 sensor 的 mipi_yu 驱动时我见过太多人把时间耗在翻驱动源码上最后发现是设备树里一个 clock-frequency 配错、MIPI 时钟 lane 没拉起来导致 camera 根本不出图。ov5645 是 OmniVision 的 500 万像素 sensormipi_yu 指的是通过 MIPI CSI-2 接口输出 YUV422 格式数据这类驱动在内核里通常对应ov5645.c配合 V4L2 框架和 media controller 使用常见于 i.MX6ULL、RK3288、全志 V3s 这类带 ISP 或直接采集 YUV 的嵌入式平台。这篇笔记直接解决三个问题驱动是怎么把 ov5645 的 MIPI YUV 数据送进内核的、设备树该怎么配才能让ov5645节点被正确 probe、以及出图失败时排查的先后顺序。内容面向正在调 Linux camera 驱动、手里有板子和 sensor 模块、需要尽快出图的工程师不聊 RGB 和 BT656 那些旧接口。先记住一个结论MIPI 物理层不通驱动写得再对也是黑匣子。2. 从 ov5645 到 mipi_yu先理解 sensor 为什么默认走 YUV 输出2.1 YUV422 输出路径sensor 内部 ISP 与 MIPI 打包ov5645 的 datasheet 里有一个关键信息它把片上 ISP 处理后的数据直接通过 MIPI CSI-2 发出去不需要 SoC 端做复杂的 ISP 处理。常见的输出格式是 YUV422 8-bit也就是UYVY或YUYV对应内核里的MEDIA_BUS_FMT_UYVY8_2X8。这个格式在 MIPI 链路上是按「Y0 U0 Y1 V0」的顺序逐像素打包的每个像素 2 字节一条 lane 上跑 8-bit 数据。选 YUV 输出而不是 RAW Bayer核心原因是 SoC 端省事。RAW 数据需要 SoC 的 ISP 做去马赛克、白平衡、降噪这要求驱动里额外实现 media pipeline 的实体绑定和 format 协商。而 YUV 输出直接把 sensor 当成一个「数据源」V4L2 子设备注册后get_fmt返回的就是可直接显示的格式采集端拿到的 buffer 不用再做格式转换就能喂给显示或编码器。所以很多工业视觉项目里用 ov5645 而不是 ov5640就是看重这颗 sensor 的 YUV 输出稳定性和 500 万像素的清晰度。2.2 驱动里最重要的三个回调s_power、s_stream、set_fmtov5645.c这个驱动属于 V4L2 subdev 驱动核心就是实现v4l2_subdev_ops里的三个回调s_power控制 sensor 的上电和断电包括 reset 引脚、powerdown 引脚、MCLK 时钟的开关。如果这个回调里没把 reset 拉高再拉低sensor 可能一直处于复位状态MIPI 无输出。s_stream启动或停止 MIPI 数据流。这个函数里会调用ov5645_s_stream往 sensor 寄存器写入 0x4202 等值来开始输出。注意很多平台的 ISP 要在s_stream之前完成初始化否则会看到「no signal」。set_fmt配置输出格式和分辨率。ov5645 支持的 YUV422 分辨率一般是 1280x960 和 1920x1080还有 640x480 预览模式。驱动里有一段初始化寄存器序列按分辨率区分数组比如ov5645_1920x1080_regs。我调 rk3288 平台时习惯先在s_power里加一段 10ms 的延时再拉高 reset防止 sensor 上电后立刻被复位。这个延时看起来是玄学但实际是 MCLK 稳定需要时间写短了真的会偶发无图。2.3 media controller 与 V4L2 pipeline 的绑定关系驱动 probe 成功后会注册一个 v4l2_subdev并且通过media_entity_pads注册一个 sink pad。这个 pad 要和 SoC 端的 CSI 控制器实体用media_create_pad_link连接起来。这一步不写在 sensor 驱动里而是写在 SoC 的 camera 驱动里比如rkisp或mxc_mipi。常见做法是先加载 sensor 驱动生成/dev/v4l-subdev0再加载 CSI 控制器驱动把 sensor 的 subdev 挂到它所管理的 pipeline 上。检查这一步是否完成看media-ctl -p的输出确认 sensor entity 是否出现在拓扑里。如果 sensor 驱动加载了却看不到 entity多半是设备树里port的 endpoint 连接写错了或者remote-endpoint的 phandle 指向了一个不存在的节点。3. 把 ov5645_mipi_yu 驱动跑起来设备树、内核配置与加载顺序3.1 设备树节点clock、reset、powerdown 三个引脚一个不能少一个能正常 probe 的 ov5645 设备树节点最少要有 MCLK 时钟、reset-gpio、powerdown-gpio、I2C 地址和 MIPI endpoint。下面是一个基于 i.MX6ULL 平台的可复现写法i2c2 { ov5645: ov56453c { compatible ovti,ov5645; reg 0x3c; pinctrl-names default; pinctrl-0 pinctrl_csi_0; clocks clks IMX6UL_CLK_CSI; clock-names xclk; assigned-clocks clks IMX6UL_CLK_CSI; assigned-clock-rates 24000000; reset-gpios gpio1 18 GPIO_ACTIVE_LOW; powerdown-gpios gpio1 19 GPIO_ACTIVE_HIGH; csi_id 0; mclk 24000000; mclk_source 0; port { ov5645_ep: endpoint { remote-endpoint csi_ep; clock-lanes 0; >CONFIG_VIDEO_OV5645y CONFIG_MEDIA_CONTROLLERy CONFIG_VIDEO_V4L2_SUBDEV_APIy CONFIG_VIDEO_MXCSIy # 取决于平台如果是 rk 平台就是 CONFIG_VIDEO_RKISP驱动源码在drivers/media/i2c/ov5645.c。如果你的内核版本较老源码里可能没有 compatible 字符串ovti,ov5645需要检查ov5645_of_match表。我看到过有人从新内核把 ov5645.c 整个拷到老内核结果编译报错因为结构体v4l2_subdev_ops的成员名在新旧内核里不一样。不要直接跨大版本拷贝驱动文件用 backport 的方式逐函数移植更稳。配置保存后重新编译内核烧录前用menuconfig搜一下OV5645确认驱动确实编进去了。烧录后启动插上摄像头模组用dmesg | grep ov5645看 probe 日志ov5645 2-003c: probing... ov5645 2-003c: detected OV5645 sensor ov5645 2-003c: supply vdddo not found看到supply not found或者reset GPIO not available直接修设备树别往下查。前面这些 regmap 和 GPIO 都没起来MIPI 更不可能有信号。3.3 加载顺序sensor 先 probe还是 CSI 先 probe在多数 Linux BSP 里sensor 驱动和 CSI 控制器驱动是独立 probe 的不存在硬性的先后顺序因为 media link 的建立发生在 CSI 控制器的complete回调也就是所有 subdev 都 probe 完才执行。但也见过一些平台偷懒把 link 写在 CSI 驱动的 probe 里此时 sensor 还没 probemedia link 建立失败运行时不报错也不出图。我一般验证加载顺序的方法是开机后先看/sys/bus/i2c/devices/下有没有2-003c这个目录再看media-ctl -p里 sensor entity 在不在。如果 sensor 已经 probe 但 entity 没出现在拓扑里这个 CSI 驱动多半是有问题的要么改驱动的complete回调要么用v4l2-compliance去强制触发 subdev 绑定。还有一种是 sensor 和 CSI 都在驱动里指定了async寄存器流程例如用v4l2_async_register_subdev和v4l2_async_notifier。这种情况下通过CONFIG_VIDEO_V4L2_ASYNC调试信息能看到绑定是否成功。如果async绑定失败日志里会出现failed to bind subdev先检查 device tree 里 endpoint 的remote-endpoint是否双向都写了漏一侧就会触发异步框架超时。4. 运行时验证确认 MIPI 链路真的在传 YUV 数据4.1 用 media-ctl 和 v4l2-ctl 抓一帧图验证链路设备树和驱动都正常后出图验证的命令序列是固定的。以 i.MX6ULL ov5645 为例media-ctl -d /dev/media0 -r media-ctl -d /dev/media0 -l ov5645 2-003c:0 - mx6s-csi:0[1] media-ctl -d /dev/media0 -V ov5645 2-003c:0 [fmt:UYVY8_2X8/1280x960] v4l2-ctl -d /dev/video0 --set-fmt-videowidth1280,height960,pixelformatUYVY v4l2-ctl -d /dev/video0 --stream-mmap --stream-count1 --stream-to/tmp/frame.rawmedia-ctl -l那一条是建 link-V是设置 pad format。这两条不执行v4l2-ctl即使打开/dev/video0拿到 buffer 也是全黑或全灰。原因是 V4L2 pipeline 里每个实体之间有独立的 format必须逐级协商一致。UYVY8_2X8这个格式名称来自media-bus-format.h在命令行里写成字符串时注意别把2X8写成2x8大小写敏感。抓到的/tmp/frame.raw是纯 YUV 数据没有文件头。用 Python 转成 PNG 验证内容import numpy as np from PIL import Image width, height 1280, 960 raw np.fromfile(/tmp/frame.raw, dtypenp.uint8) y raw[0::2].reshape(height, width) u raw[1::2].reshape(height, width // 2) uv np.stack([u, u], axis-1).reshape(height, width) img np.stack([y, uv, uv], axis-1).astype(np.uint8) Image.fromarray(img, YCbCr).convert(RGB).save(/tmp/frame.png)说明一点raw[0::2]取的是偶数位字节对应 YUV422 里的 Yraw[1::2]是奇数位包含 U/V 交替这里简单地重复 U 分量颜色并不严格正确但用来确认图像内容足够。重点观察两点图像是否全黑或全绿以及边缘是否有多彩噪点。全黑说明数据没进来全绿说明 Y 分量全是一个值大概率是 sensor 没正确初始化。4.2 看 MIPI 错误计数和 CSI 中断比看图像更早暴露问题图像有干扰或不出图时先别急着改驱动代码。我通常在 CSI 控制器的中断状态和错误计数里找信息cat /proc/interrupts | grep csi cat /sys/kernel/debug/mx6s_csi/err_count不同平台不一样但思路是通用的。如果 CSI 中断次数为 0说明 MIPI 物理层根本没收到数据问题在 sensor 侧的时钟或 lane 配置。如果中断次数增加但err_count里有 CRC 错误或 ECC 错误说明物理链路不稳定优先查模组的 MIPI 排线是否过长或弯折过MIPI 信号对阻抗敏感飞线超过 10cm 就容易出 CRC 错误>v4l2-ctl -d /dev/v4l-subdev0 --get-fmt看输出是不是UYVY8_2X8。如果 sensor 支持多种格式比如Y8和UYVYs_stream时驱动会根据set_fmt选择的 mbus code 写不同的寄存器序列所以media-ctl -V设置的格式必须和 sensor 内部寄存器初始化的格式一致。YUV 字节序颠倒UYVY 和 YUYV不影响亮度只影响色度最容易出现在不同版本的 ov5645 模组上。同一颗 sensor 不同批次寄存器默认值可能有差异厂商会在初始化序列里写死输出格式因此不要只信驱动代码里写的要实际抓一帧验证。5. ov5645_mipi_yu 驱动调试避坑5 条实战踩坑记录5.1 现象s_stream 之后 no signaldmesg 里没有错误原因排查过程先看 CSI 的 interrupt 状态发现中断计数为 0。再看 MCLK示波器抓xclk引脚发现没有波形。查驱动s_power回调代码发现clk_prepare_enable执行了但没检查返回值进一步查设备树发现assigned-clock-rates和实际 PLL 输出的频率不匹配clk 框架自动把频率设置成了最近似值 22.5MHz但 sensor 需要 24MHzPLL 锁定失败。解决在s_power里加延时并且用clk_get_rate打印实际时钟频率确认是 24MHz 再继续。设备树里assigned-clock-parents也要指定正确的父时钟否则 clk 框架可能选了一个误差超过 5% 的源。5.2 现象图像有横向撕裂帧率只有预期的一半原因分析ov5645 默认输出帧率可能不是我们想要的。在驱动s_stream里写入的初始化序列里0x4808和0x4809控制 PLL 分频这两个值决定了像素时钟。如果 SoC 端的 CSI 接收带宽不足比如只支持 2 lane 500Mbps而 sensor 按 4 lane 800Mbps 输出图像就会出现撕裂或丢帧但单帧图像内容是完整的。解决方法是把分辨率降到 640x480或修改寄存器数组里的0x4808/0x4809让输出降低到匹配带宽。5.3 现象probe 失败报failed to find reset-gpios设备树写的是reset-gpios gpio1 18 GPIO_ACTIVE_LOW;但内核报找不到。原因是该 GPIO 已经被别的驱动申请了或者 pinctrl 配置里没有把gpio1_io18配成 GPIO 功能。解决先用gpioinfo gpiochip0查这个 pin 是否空闲再用cat /sys/kernel/debug/gpio看有没有被占用。如果被别的驱动用了可以给ov5645节点加status okay且确保pinctrl里没有冲突定义。5.4 现象画面全绿但 interrupt 正常增长这个现象最迷惑人。MIPI 数据在中断层面是通的代表数据确实从 MIPI 进了 CSI。全绿的原因我在 ov5645 模组上查到的是s_stream里寄存器写顺序问题ov5645 在输出视频数据前需要先写0x4202 0x00让 sensor 退出 standby再等若干帧时间。如果初始化序列里把 standby 位写成了 1sensor 只会输出同步信号不输出有效像素采集端看到的数据都是填充值表现为绿色或灰色。解决修改ov5645_1920x1080_regs数组里0x4202的值并且在s_stream启动后加msleep(100)等 sensor 稳定输出。5.5 现象热拔插后程序崩溃或 ioctl 报EPIPEov5645 这类模组不支持热拔插但调试时经常带电插拔导致 I2C 总线锁死或 MIPI 时钟被拉死。发生之后最有效的恢复办法是断电重启而不是在驱动里加恢复逻辑。有些平台可以在驱动里对s_stream的失败路径做优化比如超时后usb_reset_device的思路迁移过来关掉 MIPI 时钟释放 CSI 的 DMA再重新初始化。注意不要在中断上下文里做这些配合 workqueue 延迟执行。6. 进阶用 GPIO 触发来验证每一路 MIPI lane定位物理层问题当你已经把驱动、设备树、寄存器都查过一遍图像依然白屏或花屏最后的手段是逐 lane 验证。这个方法我在现场帮人排查过多次比反复改代码有效把模组从板子上拆下来用示波器逐根测 MIPI lane 的差分信号确认每个 lane 的摆幅是否在 200mV 到 400mV 上下时钟 lane 的频率是否正确。很多模组厂商的排线焊接问题导致单根 lane 数据断掉CSI 控制器不会报 fatal error只会持续收到错误数据表现为花屏或偶发条纹。软件侧也可以做一件事把style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />