
简介面向安卓与 MTK 平台的 MIPI 摄像头驱动开发需求这份资源提供 OV5647 传感器驱动的精简实现与配置文件。内容覆盖驱动注册、I2C 寄存器初始化、MIPI 摄像头框架集成以及数据流处理等关键环节适合嵌入式驱动工程师和平台底层开发者参考。资源共 4 个文件含 3 个头文件与 1 个 C 源文件压缩包仅有 16KB便于直接查看传感器寄存器配置、参数结构体与上层调用接口。目前已有 773 人学习下载。通过阅读源码可理解 OV5647 在 MTK 平台上的完整驱动框架包括传感器参数定义、MIPI 配置、CameraCustomized 接口对接等模块同时也能学习到 MTK 硬件抽象层与内核驱动如何协作为适配其他分辨率、调试成像效果或排查常见异常提供实际参考。资源体量虽小但涵盖“配置—注册—数据通路”的核心实现链路对快速掌握 MIPI 摄像头驱动原理和移植思路具有较好的辅助价值。1. 一个摄像头驱动包为什么要把 RAW 数据当回事拿到一个叫 ov5647_mipi_raw.rar 的压缩包里面不外乎驱动源码、寄存器配置表、raw 抓图和一份可能有用的文档。做 MTK Android 平台调试的人看到这个名字应该立刻明白这是把 OV5647 这颗 MIPI 摄像头接到 Android 系统里最关键的半只脚。能出图不算本事raw 数据验证通过才算 MIPI 通路真实存在。这篇文章不讲树莓派上的现成方案讲的是在 MTK 平台从驱动移植、dts 参数到 raw 图像验证的一整套路子适合刚接手 sensor bring-up 的驱动工程师也适合被 HAL 逼着交 raw 图的集成工程师。2. 拆开 ov5647_mipi_raw.rarRAW、MIPI 与寄存器配置分别是干什么的2.1 先分清三样东西sensor raw 输出、MIPI 协议、驱动代码很多人把这三个概念搅在一起导致 test 模式下出了图就以为驱动没问题实际上一换到 realwo 就翻车。OV5647 是一颗 1/4 英寸、500 万像素的 CMOS sensor输出的是 Bayer 排列的 raw 数据不是 YUV也不是 RGB。raw 数据的特点是每个像素点只保留一种颜色分量RGGB 或者 BGGR 排列10-bit 或 8-bit 位深。sensor 内部只负责采样、增益和曝光不做 ISP 处理所以 raw 数据本身就是最原始的传感器输出。MIPI 是这套原始数据在物理链路上的运输协议。OV5647 的 MIPI CSI-2 接口可以配置成 1/2/4 lane每 lane 的数据速率由 PLL 决定。sensor 出来的 raw 数据先打包成 MIPI 的 short packet 和 long packet再经过 D-PHY 的差分线送到 SoC。MTK 平台的 ISP 端收到之后依 MIPI 协议解包再去做黑电平校正、去 mosaic 和 3A。所以驱动代码要干的活有两件一是把 sensor 内部寄存器配成正确的曝光和输出模式二是把 MIPI 时序参数告诉 SoC 侧的 MIPI 接收端让两边按一个节奏握手。驱动代码在中间负责的是桥梁作用。寄存器配置错了raw 图会发绿发红MIPI 时序参数错了raw 图会花掉或者干脆收不到数据上电时序错了sensor ID 都读不到。这个 rar 包里的内容如果是靠谱的 release一般会包含 sensor 驱动源文件、dts 配置参考和几帧 raw dump。但拿到手先别急着编译先验证数据本身。2.2 用文件头和十六进制确认 .rar 里 raw 文件的真实格式raw 文件不像 JPG 有固定文件头很多时候就是纯二进制数据扩展名可能是 .raw、.bin、.mipi甚至没有扩展名。第一步建议在 Linux 下用 file 命令看文件类型如果显示 data就要用十六进制工具看开头有没有 ASCII 字符。有的驱动源码包会故意把 raw 文件放进去做快速验证但格式不写清楚分辨率、位深、Bayer 排列全靠猜。我一般会先写一个脚本统计文件大小根据已知传感器推算分辨率。OV5647 输出的 raw 主分辨率是 2592x194410-bit 数据在 DDR 里如果按 16-bit 对齐存放一帧大小就是 2592x1944x2 字节约 10MB。如果文件大小是 10MB 附近可以按这个尺寸去解。8-bit 的话减半5MB。如果是 preview 分辨率 640x48010-bit 对齐后约 600KB。拿文件大小除以宽高就能确认位深是否对齐。下面这个 Python 脚本可以快速读取 raw 文件输出前几行像素值确认里面不是全零或全 0xFF顺便估算有效数据范围import sys import numpy as np def probe_raw(path, width2592, height1944, bpp2): data np.fromfile(path, dtypenp.uint8) print(ffile size: {len(data)} bytes) # 10bit 在 DDR 里按 2 字节对齐 frame data[:width * height * bpp] img frame.reshape(height, width, bpp) # 取低 8 位先看强度分布 low img[:, :, 0].astype(np.uint16) | (img[:, :, 1].astype(np.uint16) 8) print(min:, low.min(), max:, low.max(), mean:, low.mean()) print(first row first 16 pixels:, low[0, :16]) if __name__ __main__: probe_raw(sys.argv[1])逻辑说明先把整个文件读进来按宽乘高乘每像素字节数截取一帧。OV5647 在 MTK 端输出 raw 时10-bit 数据经常是高位在前、低位在后所以用第一个字节加第二个字节左移 8 位拼成 16 bit。参数里 width、height、bpp 都可以根据实际情况改如果你的 raw 是 8-bitbpp 传 1那第二个字节是下一个像素脚本会乱。遇到这种情况把低 8 位那行改成frame.reshape(height, width)就行。有效数据范围只要不是全 0 或全 4095就说明 MIPI 链路大概率通了后面再做精细图像验证。2.3 寄存器配置表怎么读0x300a 的 ID 与 0x103 的复位逻辑驱动调试的第一步永远是读 sensor IDOV5647 的 chip ID 放在寄存器 0x300A 和 0x300B前者读回 0x56后者读回 0x47。如果 i2c 能读到这两个值说明上电和 i2c 地址是对的。需要注意的是 OV5647 的 i2c 地址有 7-bit 和 8-bit 两种写法MTK 驱动里一般用 8-bit 地址 0x6C也就是 7-bit 地址 0x36 左移一位。在 dts 里写 i2c 地址时经常有人填成 0x36导致 probe 失败。复位逻辑上有一个容易错的地方OV5647 的软复位不是简单写一下 0x103常见做法是先写 0x103 0x01 再延时 5ms然后写 0x103 0x00。0x103 的 bit 0 是 SCCB 寄存器组复位有的驱动会再配合 0x3008 的 bit 7 做软件复位。上电时序和复位顺序错了sensor ID 会偶尔读到 0xFF 或者读不到。所以拿到驱动源码先检查 probe 函数里有没有复位和延时不要一上来就配 registers。寄存器配置表通常是{0x3000, 0x00}这种二元的数组对应 reg 地址和 value。关注几个关键项0x3808/0x3809 是水平输出尺寸0x380A/0x380B 是垂直输出尺寸0x3812 附近裁剪0x3C01 是 MIPI 输出控制。如果驱动开了 test pattern0x503D 和 0x5040 附近会有 test pattern 控制位出图是彩条不能用来验证真实场景。下一个阶段就是要关掉 test pattern切回 sensor real output。3. 在 MTK Android 平台上把 OV5647 驱动接起来源码移植与 dts 参数3.1 拿到 sensor 驱动先放到 kernel 驱动树哪个位置MTK Android 平台把 sensor 驱动统一放在 kernel 的 imgsensor 目录里常见路径是kernel-4.19/drivers/misc/mediatek/imgsensor/src/common/v1/不同 SoC 会有分支差异比如 MT6765 和 MT6785 的目录结构略有不同但大框架一致。sensor 驱动本身是一个独立目录里面包含ov5647mipiraw_Sensor.c、ov5647mipiraw_Sensor.h和ov5647mipiraw_Sensor.c之类。驱动文件名里的mipiraw是有意义的表示这个 sensor 输出 MIPI rawHAL 层要按 raw sensor 去初始化。先把整个驱动目录复制过来然后在同级的imgsensorlist.c里找到 sensor list加入 OV5647 的 id 和 init 函数。MTK 的 sensor 列表是一张静态表从 0x01 开始排序新 sensor 不能和已有 sensor 序号冲突。代码里类似// imgsensorlist.c 片段 extern struct imgsensor_struct ov5647mipiraw_imgsensor; // 有的版本是 sensor_init static struct imgsensor_list sensor_list[] { {OV5647MIPIRAW_SENSOR_ID, ov5647mipiraw_imgsensor}, };逻辑说明这个结构体里包含 sensor 的读 ID 函数、init 函数和控制函数。MTK probe 阶段会遍历这张表按 i2c 地址去尝试读 sensor ID读到那一个能匹配的就用对应的驱动。所以如果驱动已经放在目录里但没有加进 listsensor 永远不会被 probe。参数说明OV5647MIPIRAW_SENSOR_ID是一个宏定义在ov5647mipiraw_Sensor.h里必须和 0x300A/0x300B 读出来的值组成一个 16-bit ID比如 0x5647。这一步做完编译 kernel不要急着烧进去。先确认编译产物里出现了ov5647mipiraw_Sensor.o再用 grep 确认 sensor 名称被编进来了。很多时候驱动源码是加进去了但 Makefile 里的IMGSENSOR_DRVNAME宏没对上导致即使 ID 匹配也无法加载现象就是 log 里一直说 imgsensor probe fail。3.2 dts 里配置 MIPI lane、时钟频率和电源序列MTK 平台把 sensor 的硬件连接关系放在 dts 里。通常有两个节点一个在 i2c 控制器下配置 sensor 的 i2c 地址和电源另一个在mipi_tx/csi节点下配置 lane 数和时序。OV5647 支持 2-lane 和 4-lane常用的是 2-lane 跑 1080p 30fps4-lane 可以跑满 2592x1944。dts 里一个典型的节点配置长这样i2c2 { ov5647_mipi: ov564736 { compatible ovti,ov5647; reg 0x36; // 7-bit i2c 地址 pinctrl-names default, sleep; pinctrl-0 ov5647_pins_default; pinctrl-1 ov5647_pins_sleep; power-supply cam_power; reset-gpios pio 41 GPIO_ACTIVE_LOW; pwdn-gpios pio 40 GPIO_ACTIVE_HIGH; clocks clk26m; clock-frequency 24000000; status okay; }; }; mipi_csi { lane-count 2; // 根据硬件实际连接 hs-bitrate 720000000; // 每 lane 速率单位 bit/s sensor ov5647_mipi; };逻辑说明这里reg是 7-bit 地址 0x36如果你的驱动里用 8-bit 0x6Cdts 仍然填 0x36i2c core 会自动左移。hs-bitrate是 MIPI 高速传输的 bit rate必须和 sensor PLL 配置一致。如果 dts 里写 720Mbps但驱动寄存器把 PLL 配成了 480Mbps接收端会报 CRC 错误画面会出横条纹。参数说明OV5647 在 1296x972 30fps 下2-lane 大约需要 480Mbps在 1080p 30fps 下2-lane 大约需要 640-720Mbps。具体值取决于 PCLK 和 lane 数公式是bitrate pclk * bits_per_pixel / lane_count其中 raw10 按 10 bit 算。电源序列是另一个容易踩的地方。MTK 平台一般用 pmic 把 avdd、dovdd、dvdd 拉起来然后拉 reset再切时钟。dts 里如果只写了 power-supply没有控制 reset-gpios 的延时sensor 可能在上电瞬间还没 ready 就被 i2c 去读 ID导致 probe 失败。我一般会在驱动 probe 函数里用msleep(10)保证电源稳定后再去拉 resetreset 拉高后等 20ms 再读 ID。MTK 的pinctrl-0配置了 mipi 数据脚的 pinmux如果漏了MIPI 信号不会 routing 到 CSI 控制器log 里虽然 i2c 能读到 ID但始终收不到中断。3.3 驱动里上电时序和 ID 校验probe 函数的最小实现驱动移植最关键的是 probe 阶段的时序。MTK sensor 驱动一般会提供poweron函数由上层 imgsensor core 调用。你拿到 ov5647 驱动后先看 poweron 里是否完整做了 avdd/dovdd/dvdd 三路电源使能。如果原来的平台用的 PMIC 电压域和你的板子不一样延时参数要重新调。下面这段我常用的最小实现可以参考static int ov5647_poweron(struct i2c_client *client) { // 1. 电源使能 regulator_bulk_enable(3, ov5647_supply); msleep(10); // 2. reset 释放拉高表示非复位 gpiod_set_value_cansleep(reset_gpio, 1); msleep(20); // 3. MCLK 初始化24MHz clk_prepare_enable(mclk); msleep(5); // 4. 读 ID失败则返回错误 u16 id read_i2c_16(client, 0x300A); if (id ! 0x5647) { dev_err(client-dev, ov5647 id mismatch: 0x%04x\n, id); return -ENODEV; } return 0; }逻辑说明先供电再拉 reset最后开时钟这是绝大多数 raw sensor 的上电顺序。反过来先开时钟再供电sensor 内部的 LDO 可能会有毛刺导致 ID 读取不稳定。read_i2c_16是 MTK 平台的 i2c 封装读 0x300A 和 0x300B 两个字节拼成 0x5647。参数说明msleep的数值不是乱写的OV5647 的数据手册要求 reset 释放后至少等待一个 SCCB 扫描周期才能读 ID经验值 10-20ms。如果你板子供电部分有大的去耦电容上电慢就得加大到 50ms不然 log 里会出现一段时间内 i2c ack 错误。ID 读通之后probe 还没完。MTK 会调用sensor_init去写一长串寄存器把 sensor 切成某一个分辨率。建议先在 init 函数里把所有寄存器写完之后加一个read_i2c_16读 0x3808看看当前输出宽度是否符合预期。这一步能快速发现寄存器表是否被损坏或写错地址避免进到相机预览阶段才黑屏到时候排查范围就大了。4. 用 RAW 图验证驱动从 MIPI 抓到数据到能看的图像4.1 抓 raw 的两种方法内核调试节点 / camera hal dump驱动能 probe 不代表 MIPI 通路完全正常最快的验证方法是直接抓 raw。MTK 平台的 imgsensor 驱动里通常有 raw dump 支持通过 procfs 节点或者 adb 命令触发常见的是proc/mtksw/下的某个 debug 节点。具体节点名随平台变化但思路一致让 ISP 在一段时间内把收到的 raw 数据完整写进文件然后从手机里 pull 出来。另一个常见做法是在 camera HAL 层开 debug dump。MTK 的 camera HAL 有 metadata 开关可以在 support 列表里打开dump raw这样每次 preview 前会把 raw 写到/data/vendor/camera/或者/sdcard/DCIM/。两种方式各有利弊内核节点抓到的 raw 最底层能直接反映 sensor 输出是否干净HAL dump 则带着 ISP 的预处理偏色问题可能被 ISP 掩盖。我建议刚开始 bring-up 时用内核节点抓因为你要验证的是 MIPI 从 sensor 到 SoC 这一段不是验证 ISP 效果。抓完 raw 后文件大小和你在 2.2 节算的对不上第一件事不是怀疑分辨率设置而是看看是不是有 CRC 错误导致 ISP 丢弃部分包。MTK 的 log 里搜索MIPI或csi2关键字会有crc_err或csi_irq的统计信息。如果常看见 CRC 错误说明 lane 数和 bitrate 不匹配回到 dts 去改。4.2 用 Python 把 10-bit raw 转成可视 PNG抓下来的 raw 是一堆黑白像素没法直接用图片查看器打开。写个转换脚本把 raw 数据重排成 Bayer 图再插值成 RGB 预览图。这一步能马上看出 sensor 是不是在正常出图。下面这个脚本处理 2592x1944 10-bit rawimport numpy as np from PIL import Image def raw10_to_bayer(path, width2592, height1944): raw np.fromfile(path, dtypenp.uint8).astype(np.uint16) raw raw[:width * height * 2] # 每两个字节拼一个 16-bit 像素取低 10 bit img raw[0::2] | (raw[1::2] 8) img img 0x3FF return img.reshape(height, width) / 1023.0 * 255.0 def bayer_to_rgb(bayer, bayer_patternBGGR): # 最简单的双线性插值只做预览 rgb np.stack((bayer, bayer, bayer), axis-1) return rgb.astype(np.uint8) if __name__ __main__: bayer raw10_to_bayer(ov5647.raw) Image.fromarray(bayer_to_rgb(bayer)).save(ov5647.png)逻辑说明raw10 在内存里按 16-bit 存放所以每两个字节组成一个像素。 0x3FF只保留低 10 位把 0-1023 映射到 0-255。bayer_pattern参数决定了后面做 demosaic 的起始相位OV5647 通常是 BGGR但如果你拿到的是 RGGB整张图会偏红把 pattern 改成 RGGB 就行。参数说明如果 raw 是 8-bit每像素只需要一个字节那上面代码里的raw[0::2] | raw[1::2] 8就完全错了会得到一堆花点。8-bit 的话直接把字节数组 reshape 成宽高即可。这个脚本只能用来判断画面结构。真正要得到干净预览图需要做黑电平减去和阴影校正否则整张图对比度会发灰。但在 bring-up 阶段能看出物体轮廓你已经成功一大半了。4.3 从 raw 图判断 MIPI 通路是否正常花屏/横条纹/偏色得到 raw 预览图接下来是看图说话。如果图是全黑的先别骂 sensor检查 MIPI 那边的 clock lane 是不是反了。raw 全黑通常意味着 ISP 收到了 MIPI packet但所有像素值都是无效数据0x000。原因可能是 sensor 输出模式没配对寄存器里的 timing 没有生效。如果图是斜向的彩色条纹或者像撕裂一样的花屏大概率是 lane 映射问题。OV5647 的 MIPI 数据线在 PCB 上有顺序MTK 端的 CSI 控制器支持 lane swap如果硬件上 d0/d1 接反了MIPI 包还是能被解析但图像会左半部分和右半部分错位。这种问题不用改板子在 dts 里调lane-swap属性即可。我遇到的多数花屏最后都是因为 dts 里 lane-count 写的是 4但硬件只接了 2 根 data lane导致接收端时钟和数据对不齐。偏色问题则更多是 Bayer 排列设置不对。OV5647 的 raw 可以输出 BGGR、RGGB、GRBG、GBRG 四种排列寄存器 0x9A 里有控制位。MTK 驱动里有一个bayerPattern的枚举如果你驱动里写的是BAYER_BGGR但 sensor 实际输出 RGGB整张图会明显偏红或偏蓝。改驱动里的枚举比改寄存器更简单因为 HAL 是按驱动的枚举去做 ISP 的。改完重新抓 raw偏色立刻纠正。5. MTK Android 调试 OV5647四个典型翻车点和排查路径5.1 现象i2c 能读到 sensor ID但 Camera HAL 一直黑屏原因read ID 成功说明供电、时钟、i2c 没问题黑屏说明 MIPI 数据通路没建立。多数情况是 dts 里 MIPI 接收端节点没有启用或者lane-count和驱动注册表对不上。MTK 平台的 CAM 是 camsys 管理sensor 只作为 source如果 camsys 的csi-port配置和 dts 冲突MIPI 信号在 camsys 层就被丢弃了。解决先查 kernel log 里camsys关键字确认 sensor 对应的主链路有没有 register。再查clk_summary看 MIPI 参考时钟是否开启。Frame start 中断如果一次都没触发过直接看 MIPI D-PHY 配置特别是hs_trail和clk-post这类时序参数。OV5647 的 MIPI 收端对时序要求不算苛刻但 bit rate 不一致会表现出时好时坏。5.2 现象raw 图亮度不均匀两侧发暗或一边暗原因OV5647 的 lens 如果没有校准过raw 图边缘暗是正常的但两侧明显不对称的时候怀疑是 MIPI 的 desync 导致同一帧数据里存在错位。MTK 的 D-PHY 里有 deskew calibration 机制sensor 的输出需要配合 MIPI 的 dphy 校准。有的驱动在 init 前段没有做 deskew 相关寄存器配置导致收发端相位差。解决先抓两帧 raw 对比如果暗的位置固定不变是 lens shading 问题和 MIPI 无关如果暗处会游走就是 MIPI lane 的 skew。在 dts 或 sensor 驱动里打开dphy_deskewMTK 平台的 camsys 调试接口里有 deskew 触发命令。经验值是在 MIPI 600Mbps 左右时skew 超过 0.5ns 就会出现明显亮度纹。实在调不通把 hs-bitrate 从 720Mbps 降到 600Mbps足以规避大部分时序问题。5.3 现象预览帧率只有 15fps但驱动标称 30fps原因MTK Android 的 preview 帧率受 HAL 层 sync 控制不是 sensor 输出多快就显示多快。OV5647 在 2592x1944 30fps 时 raw 数据量约 300MB/s如果 dts 里配的 bitrate 不够MIPI 收端会丢帧表现为实际帧率减半。解决先查 v4l2 或 camera 的 frame arrive time确认是 sensor 没输出 30fps还是 HAL 故意丢帧。用示波器量 MIPI CLK 的 HS 模式周期换算实际 bitrate。我的经验是 OV5647 的寄存器如果配错 PLL输出帧率会稳定在 15fps 或 24fps而驱动代码里还写着 30fps。这时需要回到寄存器 0x3808 附近的 timing 设置手动计算pclk 总像素行 * 总行 * fps。5.4 现象换了一片 sensor 后 ID 变成 0x00原因OV5647 的 ID 读不出来最常见的原因不是 sensor 坏而是 i2c 地址冲突。sensor 的 SCCB 地址引脚被上下拉设成了 0x36但驱动里写 0x6C。换了一片 sensor 之后如果 PCB 板上地址引脚上拉电阻没焊sensor 默认地址可能不同。解决用万能 i2c 工具挨个扫描 0x30-0x37 的地址哪个能回 ACK再回读 0x300A。不要盲目相信驱动里的地址。另一个原因是 firemware 的 reset pin 悬空导致 sensor 内部 bootloader 没把 ID 区初始化。把 reset 拉低再拉高一次延时加长到 50msID 一般能回来。6. 把 OV5647 调到可量产状态帧率、曝光与自检脚本当 raw 图稳定、预览流畅离量产还差最后一步把驱动里的曝光、增益和帧率控制验证到位。OV5647 的曝光寄存器是 0x3500/0x3501/0x3502 是 AEC 的上中下三位增益在 0x350A/0x350B。MTK 的 3A 会通过驱动里的 set_param 回调调整这些值。我遇到过一个坑驱动里曝光寄存器写的是 16-bit但 sensor 手册要求 20-bit结果画面只要变暗就过曝闪烁后来发现是寄存器表里把 0x3502 的值当成高位还是低位的问题。建议写一个自检脚本循环读取当前帧率、曝光值和行数快速回归。用 adb 和 mtk 的 i2c 工具就能做# 读取 OV5647 当前曝光时间 adb shell i2cget -y 2 0x36 0x3500 b adb shell i2cget -y 2 0x36 0x3501 b adb shell i2cget -y 2 0x36 0x3502 b逻辑说明这三条命令把曝光寄存器的三个字节读出来拼成一个 20-bit 数。正常情况下当你用手电筒照 sensor曝光值应该明显下降。如果曝光值不变说明驱动里的 access 函数没有正常映射到 3A带出来的画面在强光下会一片白。参数说明i2cget 的-y 2是指 i2c 总线号为 2要和你的手机平台匹配有的平台总线路不同可以用i2cdetect -y -l先列出总线。我个人的习惯是每次调完驱动都固定抓 5 帧 raw 存底文件名带日期和分辨率。这样无论过了多久都能回头审视这次改动到底影响了什么。RGB 偏色是改了驱动还是换了灯板一翻 raw 对比就知道。希望你调 ov5647 的时候也能把 raw 验证当成第一道防线别等预览花屏了再猜是硬件还是软件。这条路走通之后再去接别的 sensor 就有底气了方法论是通用的希望帮到你。本文还有配套的精品资源点击获取