
简介OV5647 MIPI Camera 驱动实现源码包面向 Android 与 MediaTek 平台下的嵌入式驱动开发者及 BSP 工程师内容围绕五百万像素传感器 OV5647 的驱动框架涉及 MIPI 接口配置、I2C 寄存器初始化、ISP 图像数据通路等关键环节还兼顾与 CameraService 及 HAL 层的对接方式可直接用于 MTK 平台摄像头驱动的移植与调试参考。包体仅 16KB共 4 个文件其中 3 个 h 头文件分别负责摄像头定制参数、传感器信息配置等接口声明1 个 c 源文件承担驱动主逻辑可快速理解驱动注册、上下电流程、分辨率切换、数据流处理等实现细节。已有 772 人学习下载。通过研读它可以掌握在 MTK Android 平台上将 OV5647 传感器与 MIPI 框架、ISP 和系统服务的协同方法减少重复排查并可在理解整体结构后基于自身平台与传感器参数进行二次开发对相机驱动开发或平台移植人员有实际参考价值。1. 一块 ov5647_mipi_raw 补丁包先搞清它是给谁吃的第一次把ov5647_mipi_raw.rar解压的人很多是从树莓派那边转过来的树莓派摄像头模块用的就是 OV5647接上就能出图。但把同样一颗 sensor 挂到 MTK Android 主板上情况完全不一样——MIPI lane 分配、RAW Bayer 排列、Camera HAL 认不认这个 sensor任何一个环节不匹配屏幕就是黑的或者一片紫。ov5647_mipi_raw这个名字已经把链路交代完了OV5647 是那颗 500 万像素 CMOS 传感器mipi是它传数据的 MIPI CSI-2 接口raw是传感器直接输出的 Bayer 原始数据没有经过 ISP 的 YUV 处理而mtk android说明这套驱动要落进 MTK 的 imgsensor 框架里。这篇按“链路搞清楚 → 驱动移植 → 点亮出图 → 排查翻车 → 验证 RAW”的顺序写适合做智能硬件、工业平板、扫码头、边缘盒子的工程师照着走能少踩两礼拜坑。2. MTK 平台上 OV5647 的 RAW 链路从传感器输出到 ISP 的每个环节调 OV5647 之前我习惯先不碰代码把一条数据链路在脑子里过一遍。OV5647 这颗 sensor 的底子跟树莓派摄像头模块同源但你拿到的ov5647_mipi_raw并不是树莓派那套 V4L2 subdev 驱动而是移植到 MTK imgsensor 私有框架里的一份驱动。两条路的抽象完全不同如果你带着树莓派的调试经验来搜 MTK 平台很容易被“同样是 ov5647 为什么驱动文件长得完全不一样”卡住。2.1 OV5647 的 RAW 输出与 MIPI CSI-2 包络OV5647 是一颗 1/4 英寸、500 万像素2592×1944的 CMOS 图像传感器支持 RAW8 / RAW10 两种 RAW 输出也支持 YUV422但接到 MTK Android 相机时基本走的是 RAW10。整条链路的工作方式是sensor 内部 PLL 生成像素时钟把感光区读出的 Bayer 数据按 MIPI CSI-2 协议打包通过 D0/D1/D2/D3 数据 lane 和一路 clock lane 差分送出MTK 这边的 CSI-2 host 再解包把 RAW 数据喂给 ISP 做黑电平、去噪和 demosaic。这里要先记住一个概念MIPI CSI-2 协议分成物理层 D-PHY 和协议层两层。OV5647 属于 D-PHY 时代的老传感器走的是差分对不是那种三条线一组的新 C-PHY。所以如果你在网上搜 “mipi c-phy s 参数” 来套这颗 sensor方向就错了——C-PHY 是手机屏和高阶 sensor 上的三线制物理层OV5647 根本不支持。我见过有人用 C-PHY 的眼图标准去量 OV5647 的 D-PHY 波形量出来的压摆率永远“不正常”白折腾一晚上。再说 RAW Bayer。sensor 感光区每个像素只能看到一种颜色按 2×2 排列成 RGGB、BGGR、GRBG、GBRG 四种之一。OV5647 的模组出厂时每家的 Bayer 顺序不一定一样这个信息最终要写进驱动里的bayer_pattern字段。如果这里和模组实际排列对不上出来的图像就是全绿或红蓝对调。这个字段不是“玄学”是 ISP 做 demosaic 时决定每个像素该插值成什么颜色的依据错一个位整幅图就废。RAW10 的数据量也可以提前算一下2592×1944×10bit 约等于 50Mbit按 15 帧算大概是 756Mbps。OV5647 的 MIPI 模组通常做 2 lane 或 4 lane2 lane 每 lane 跑 400Mbps 左右4 lane 可以降到每 lane 250Mbps 附近。这也是为什么多数模组愿意走 2 lane——布线少、干扰小带宽刚好够。2.2 MTK 的 imgsensor 框架与 HAL 层的边界MTK 平台对摄像头的抽象方式和高通不一样。高通走标准的 V4L2 subdevMTK 是从老 Turnkey 时代一路演进过来的私有 imgsensor 框架。每个新 sensor 被封装成一个驱动文件挂到全局的kdSensorList[]链表里。链路大致是HAL 通过/dev/video0下发 open/close/getinfo 等命令内核的imgsensor.c再把命令路由到具体的 sensor 驱动回调。一个 OV5647 MIPI RAW 驱动文件里最核心的结构是imgsensor_info_struct里面塞了 sensor 的身份、增益范围、曝光上限、MIPI lane 数、Bayer 排列这些关键参数。我把最常改的字段拉成一张表字段作用典型值sensor_id与模组 PID 比对用于识别0x5647 或 0x47max_gain / min_gain增益上下限决定暗光表现常见 min 0x40、max 0x200min_exposure / max_exposure曝光行数范围具体按帧率算mipi_data_lane实际使用几条 data lane2 或 4bayer_patternBayer 排列方式按模组规格填mirror_flip镜像和翻转开关0 / 1 组合注意mipi_data_lane必须跟模组硬件一致。模组只引出 2 lane驱动写成 4 laneMTK 的 CSI-2 host 会一直等不到足够的数据出来的图像就是花屏。这条我在第 4 章会再展开。HAL 侧的关系也要捋清MTK 的 Camera HAL 拿到 sensor 驱动的sensorGetInfo返回值之后才会决定怎么配置 ISP、怎么定预览尺寸。如果你的 sensor 驱动里imgsensor_info_struct填的分辨率列表和实际模组输出不一致HAL 照样能起来但预览和拍照的分辨率会错位拍出来的照片尺寸跟设置对不上。所以驱动里的分辨率列表、曝光增益范围、MIPI lane 数这三个核心字段不能从别的模组驱动里复制粘贴必须按这份 OV5647 模组的实际规格来填。3. 动手移植DTS 登记、sensor 驱动注册与初始化序列链路清楚了接下来就是动手。我一般拿到ov5647_mipi_raw这类包之后会先看三样东西有没有 DTS 节点、有没有 imgsensor 驱动文件、有没有 init 寄存器序列。这三样齐了点亮只是时间问题。但 MTK 平台新旧版本的差异很大老 mt6735/mt6750 时代摄像头电源和 GPIO 控制写死在kd_camera_hw.c里Android 11 之后不少平台把电源域和 pinctrl 挪进了 DTS。下面我按“新旧都适用”的写法给一版参考具体路径以你手上的 SDK 模板为准。3.1 DTSi2c 设备节点与 pinctrl 配置OV5647 的控制接口是 SCCB兼容 I2C。MTK 平台通常把它挂到某个空闲的 I2C 总线上地址常见为 0x367 位地址也有模组把地址做成 0x6c8 位地址本质是同一个设备。DTS 里我一般这样写i2c3 { status okay; clock-frequency 400000; pinctrl-names default, mipi_clk_on, mipi_clk_off; pinctrl-0 pinctrl_i2c3_default; pinctrl-1 pins_cam0_mclk_en; pinctrl-2 pins_cam0_mclk_dis; ov5647_mipi_raw: ov5647_mipi_raw36 { compatible ovti,ov5647_mipi; reg 0x36; reset-gpios pio 26 GPIO_ACTIVE_LOW; pwdn-gpios pio 27 GPIO_ACTIVE_HIGH; clocks clk26m; clock-names xcxi; }; };节点里的reg 0x36要和驱动里i2c_client的地址对上。MTK imgsensor 驱动通常不直接依赖 DTS 里的这个 I2C 节点去 probe而是自己按sensor_id挨个探测挂载因此 DTS 更多是给 GPIO、时钟、pinctrl 用的。reset-gpios和pwdn-gpios是模组的复位和掉电脚注意两个引脚的极性有的模组 pwdn 是高有效有的是低有效接反了 sensor 永远起不来。pinctrl里的mipi_clk_on/mipi_clk_off控制的是 MCLK 主时钟引脚。OV5647 的 MCLK 一般用 24MHz由 MTK 平台clk26m供给。MCLK 没配或者频率不对sensor 内部 PLL 就锁不住I2C 能通但 image 就是不出来——这个现象特别容易让人误判成 MIPI 链路问题。3.2 驱动文件里的注册入口与 sensor_id 探测OV5647 驱动文件里有一个 sensor 注册表MTK 的 imgsensor 框架启动时遍历这张表逐个调check_version去探测传感器 ID。OV5647 的 PID 寄存器在0x3000和0x3001正常读回来的值应该是 0x56 和 0x47。驱动里常见的写法是这个样子/* ov5647_mipiraw_Sensor.c 里的注册入口 */ static struct IMGSENSOR_INIT_FUNC_OBJ sensor_init_list[] { { .check_version OV5647MIPIRAW_CHECK_VERSION, .sensor_id OV5647MIPIRAW_SENSOR_ID, /* 0x5647 */ .init_func ov5647mipiraw_init, }, {NULL, 0, NULL}, }; /* 探测函数读 0x3000/0x3001 比对 */ static kal_uint32 OV5647MIPIRAW_CHECK_VERSION( struct sub_sensor_info *sensor_info) { kal_uint8 pid 0, ver 0; read_reg(0x3000, pid); read_reg(0x3001, ver); if (pid 0x56 ver 0x47) { sensor_info-sensor_id 0x5647; return 1; /* 识别成功 */ } return 0; /* 框架会继续探测下一个 sensor */ }这段代码里read_reg底层走的就是前面 DTS 里那个 I2C 地址。如果你的模组地址不是 0x36而是别的比如 0x3c驱动的 I2C 写地址也要同步改。我踩过一次驱动里写死0x36但换了一家的模组实际是0x3c探测一直失败日志里反复打sensor id error还以为是线没焊好。初始化序列是另一块大头。OV5647 上电后默认输出的是 DVP 模式的 YUV要让它切成 MIPI 2lane RAW10 输出必须往寄存器里写一大串配置。这个序列就是常说的 init table通常在驱动文件里以ov5647_mipiraw_init_setting_xxx[]数组形式存在。/* 初始化序列的一部分节选格式是 reg, val */ static const struct reg_setting ov5647_mipiraw_start_setting[] { {0x3103, 0x11}, /* 使能 PLL 分频 */ {0x3008, 0x82}, /* sensor 软复位 */ {0x3017, 0x7f}, /* MIPI 模式相关 */ {0x3018, 0xfc}, /* lane 数配置这里按 2 lane */ {0x3034, 0x1a}, /* PLL 分频参数 */ {0x3503, 0x00}, /* 曝光和增益自动/手动切换 */ {0x4800, 0x04}, /* MIPI 时钟 lane 启动 */ };注意最后一行{0x4800, 0x04}这是 OV5647 的 MIPI 时钟 lane 开关。很多模组在 MIPI 模式下还必须把0x4801的 lane 数配置同步改掉驱动里这两处必须配套。init 序列一旦有一个值抄错sensor 可能仍然出图但图像时序完全错乱花屏、横条纹、左右偏移全都有可能。改寄存器表没有捷径只有拿着模组规格书逐行核对不像调 GPIO 极性那样能靠日志快速定位。4. OV5647 MIPI RAW 调试的避坑与排查先把这四个现场按顺序走一遍点亮 OV5647 的路上我至少走过四回弯路每一回都浪费了一到两天。这里按“现象 → 原因 → 解决”写出来都是可以直接抄作业的排错路径。4.1 I2C 通信失败sensor 一直不上线现象CHECK_VERSION读回来的 PID 不是 0x5647而是0xFF或0x00。日志里反复出现[SENSOR] sensor id errorHAL 那边永远报找不到后置摄像头。原因最常见的是电源时序不对。OV5647 模组一般需要 AVDD 2.8V、DOVDD 1.8V、DVDD 1.2V而且要求 DOVDD 先于 AVDD 起来。如果驱动里的power_on函数把电压顺序写反了sensor 内部上电状态机卡死I2C 从机根本不应答。其次是 MCLK 没起sensor 没有时钟SCCB 接口也响应不了。还有一种是 reset 脚被拉死比如 GPIO 默认输出低电平把 sensor 摁在复位状态。解决我现在的固定流程是先确认硬件上电。用示波器量 AVDD/DOVDD 的上电顺序再量 MCLK 有没有 24MHz 波形。这两个没有问题再回到软件层查驱动里power_on函数的 GPIO 操作顺序。注意有的模组 reset 低有效、pwdn 高有效两个脚的操作方向是相反的代码里容易写反。如果硬件和代码都看过了还是不通用i2cdetect在系统起来后手动扫一遍 I2C 总线确认 sensor 有没有挂在总线上——这一步能立刻区分是“sensor 没响应”还是“驱动没探测到”。4.2 MIPI 能通但图像花屏现象I2C 通了sensor 也识别成功了预览能出画面但整幅图像花得厉害——绿色紫色混在一起或者屏幕上是一道一道的斜纹动一下摄像头画面像雪花一样散开。原因这是典型的 MIPI lane 数不匹配。模组是 2 lane 出图驱动imgsensor_info_struct里mipi_data_lane填了 4MTK 的 CSI-2 host 按 4 lane 去收其中两路一直在等数据收到的每帧都不完整。反过来模组是 4 lane 驱动填 2 lane会丢一半数据。另一个常见原因是 MIPI 的 data lane 和 clock lane 在 PCB 上布线不等长差分对压摆率不够信号眼图闭合。解决先把mipi_data_lane写成和模组一致。然后查驱动 init 序列里0x3018的值这个寄存器控制数据 lane 的使能位必须跟mipi_data_lane配套。最后再看硬件MIPI 差分对布线要等长、同层、少打过孔。如果你做的是软板转接这条特别容易出问题——软板太长导致 MIPI 信号衰减花屏就是信号质量不够的直接表现。这种情况我会用示波器看 MIPI 波形量 clock lane 的差分幅度正常应该能清晰地看到一对反向的差分信号上升沿干净幅值不低于 200mV。如果波形一塌糊涂别调软件了改硬件。4.3 RAW 数据能取到但颜色错乱现象图像能出来画面也连贯但红色和蓝色对调或者整个画面严重偏绿怎么看都不对。这不是白平衡问题因为不管怎么调 AWB 都拉不回来。原因Bayer 排列没对上。sensor 实际的 Bayer 顺序是 BGGR驱动里bayer_pattern写的却是 RGGBISP 做 demosaic 的时候把每个像素的颜色通道都错了一位。还有一种隐藏情况模组带 mirror/flip比如 sensor 横着装、图像做了镜像翻转后Bayer 顺序也会变化。很多工程师改了mirror_flip之后忘了同步改bayer_pattern图像就是花的颜色。解决把bayer_pattern在四种排列之间轮换每次改完重新抓一张图看颜色。BGGR、RGGB、GRBG、GBRG 这四个值来回试哪个对就是哪个。试对之后再连着mirror_flip的组合一起验证同一颗 sensor不同朝向的模组最终填的可能是不同组合。所以不要问“OV5647 到底该填哪个”要看你的模组实物是哪个方向。4.4 图像边缘有彩虹色斑中间正常现象画面中心颜色正常四周或者边缘出现彩虹状色斑尤其黑白物体边缘最明显。这个现象不是花屏也不影响预览但拍文档或者拍二维码时边缘根本没法用。原因这是 ISP 的 demosaic 插值边缘处理问题也就是大家常说的 MTK 相机插值没调好。MTK ISP 的 demosaic 会参考相邻像素的颜色梯度做插值如果 sensor 输出的 Bayer 顺序在驱动里填对了但 ISP 的插值权重配置和 sensor 的噪声特性不匹配边缘就会出彩色伪像。还有一种是黑电平校准不对导致暗部噪点被放大颜色在边缘溢出。解决先确认 Bayer 顺序没问题再用 MTK 的调试工具拉一组 RAW 图对比边缘区域的 R/G/B 通道直方图。如果 G 通道在边缘明显偏低那是黑电平或镜头阴影修正没做。如果只是轻微彩虹可以用 ISP 的 demosaic 边缘去伪彩参数压一压。这个环节没有统一参数因为跟镜头、sensor 批次、环境光都有关系。我一般先把黑电平校准做掉边缘色斑通常能消掉一大半。5. 出 RAW 之后验证 Bayer 排列与初调曝光增益画质调整按我自己的习惯分两步走先验证 RAW 数据链路是不是干净的再动曝光和增益。如果你跳过第一步直接调白平衡碰到颜色问题分不清是链路错还是参数错等于在沙滩上盖楼。5.1 抓一帧 RAW 验证 Bayer 排列MTK 平台的 LCA 或调试版固件一般都能直接 dump RAW 帧生成的文件是一个裸的 Bayer 数据。拿到这个文件我习惯先不急着看图像而是用 Python 算一下四个通道的均值这样能快速判断 Bayer 顺序是不是真的对。# 用 Python 检查 RAW dump 的 Bayer 通道均值 import numpy as np # 假设 sensor 输出 2592x1944RAW10 按 16bit 存储 raw np.fromfile(ov5647_raw.raw, dtypenp.uint16).reshape(1944, 2592) # 按 RGGB 拆成四个通道 ch_r raw[0::2, 0::2] ch_g1 raw[0::2, 1::2] ch_g2 raw[1::2, 0::2] ch_b raw[1::2, 1::2] print(R :, ch_r.mean()) print(G1 :, ch_g1.mean()) print(G2 :, ch_g2.mean()) print(B :, ch_b.mean())一个正常的 RAW 帧四个通道均值应该比较接近都落在 10bit 灰度中段附近。如果你发现某个通道均值明显偏低比如 B 通道只有 30说明 Bayer 排列匹配有偏差画面大概率偏色。这个脚本还能验证文件大小2592×1944×2 字节一帧 RAW 大约 10.1MB如果 dump 出来的文件大小对不上说明分辨率配置或者 MIPI 收包有问题后面的分析都别做了。R 和 G1/G2 的均值差异稍微有一点是正常的因为 sensor 的量子效率对不同波长本来就不一样但 Be 通道如果差出一个量级一定是排列问题。5.2 曝光与增益的初始值设置以及 MTK 相机插值初调曝光和增益的寄存器换算是 OV5647 驱动里最容易被抄错的地方。OV5647 的曝光值是一个 20bit 的数拆到三个寄存器里/* 曝光20bit 拆到 0x3500/0x3501/0x3502 */ void ov5647_set_exposure(kal_uint32 exposure) { sensor_write_reg(0x3500, (exposure 12) 0x0F); sensor_write_reg(0x3501, (exposure 4) 0xFF); sensor_write_reg(0x3502, (exposure 0x0F) 4); } /* 增益整数部分在 0x350A小数部分在 0x350B */ void ov5647_set_gain(kal_uint32 gain) { sensor_write_reg(0x350A, (gain 8) 0xFF); sensor_write_reg(0x350B, gain 0xFF); }这两个函数的换算关系要跟模组的实际寄存器手册核对不同批次 OV5647 的增益公式可能有一点点差别。我见过有人从别的 sensor 驱动里复制了一套增益换算套到 OV5647 上暗光下画面噪点爆炸还以为是 sensor 垃圾。曝光和增益的初始值我建议在调试阶段先固定住不让 3A 自动跑。把曝光设在一个中间曝光量、增益设成 1x这样每次抓图的条件一致才能比较改动前后的效果。3A 自动跑起来之后MTK 相机插值、AWB、AE 三个模块同时在动出了问题根本没法定位。等 RAW 链路验证干净了再逐步放开 AE最后放开 AWB。顺序不要反过来否则你看到的任何颜色问题都分不清是 ISP 参数还是 sensor 配置造成的。这一步做完OV5647 这趟基本就稳了。6. 最后一招用一条命令读出 sensor_id链路通了一半如果你已经按照前面的步骤走到了出图阶段我还有一个验证习惯在板子刚拿到手、驱动还没开始移植的时候先用 adb 把 OV5647 的 PID 寄存器读出来。这招能在一分钟之内确认硬件链路到底通不通省去后面一大半盲调。前提是这台 MTK 设备已经开了 adb root而且内核里编了 i2c-dev 的支持。然后在 PC 上执行# 假设 OV5647 挂在 i2c bus 37 位地址 0x36 # 第一步验证 I2C 总线是否通 adb shell i2cdetect -y -r 3 # 第二步读 PID 寄存器 0x3000/0x3001返回 0x56 0x47 adb shell i2ctransfer -f -y 3 w20x36 0x30 0x00 r2i2cdetect的结果如果能在 0x36 的位置看到一个地址号说明 sensor 的 SCCB 接口已经回应了总线的寻址。i2ctransfer的输出如果是0x56 0x47硬件链路基本确认没问题后面再出问题就是驱动配置的事。如果读出来全是0xff别急着调驱动先查电源和 MCLK——这跟我前面第 4.1 节说的一样上电顺序不对时 sensor 根本不应答。我还习惯顺手抓一下 kernel 日志adb shell dmesg | grep -i -E ov5647|imgsensor|sensor_id看有没有ov5647的登录信息以及 MTK imgsensor 框架是不是已经把它挂到了kdSensorList上。注意在 MTK 新平台上sensor 探测可能发生在内核启动早期dmesg缓冲区可能被盖掉那就在驱动里加一条pr_info([ov5647] sensor_id 0x%x\n, id)配合adb shell dmesg一起看。这个习惯我保持了几年了每次接一块新的 MTK 板子第一件事永远是先读 sensor_id而不是急着把驱动编译进去。读到了说明硬件、电源、I2C、时钟都活着可以安心做软件适配读不到直接锁定硬件问题不碰驱动。把这招用熟调 mipi 摄像头的效率能提升一大截希望帮到你。本文还有配套的精品资源点击获取