拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MTK平台OV5647 MIPI RAW摄像头驱动移植与调试实战

MTK平台OV5647 MIPI RAW摄像头驱动移植与调试实战

简介: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 lane,2 lane 每 lane 跑 400Mbps 左右,4 lane 可以降到每 lane 250Mbps 附近。这也是为什么多数模组愿意走 2 lane——布线少、干扰小,带宽刚好够。

2.2 MTK 的 imgsensor 框架与 HAL 层的边界

MTK 平台对摄像头的抽象方式和高通不一样。高通走标准的 V4L2 subdev,MTK 是从老 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 或 0x47
max_gain / min_gain增益上下限,决定暗光表现常见 min 0x40、max 0x200
min_exposure / max_exposure曝光行数范围具体按帧率算
mipi_data_lane实际使用几条 data lane2 或 4
bayer_patternBayer 排列方式按模组规格填
mirror_flip镜像和翻转开关0 / 1 组合

注意mipi_data_lane必须跟模组硬件一致。模组只引出 2 lane,驱动写成 4 lane,MTK 的 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 DTS:i2c 设备节点与 pinctrl 配置

OV5647 的控制接口是 SCCB,兼容 I2C。MTK 平台通常把它挂到某个空闲的 I2C 总线上,地址常见为 0x36(7 位地址),也有模组把地址做成 0x6c(8 位地址),本质是同一个设备。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_raw@36 { 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 error,HAL 那边永远报找不到后置摄像头。

原因:最常见的是电源时序不对。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填了 4,MTK 的 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写的却是 RGGB,ISP 做 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 输出 2592x1944,RAW10 按 16bit 存储 raw = np.fromfile("ov5647_raw.raw", dtype=np.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 3,7 位地址 0x36 # 第一步:验证 I2C 总线是否通 adb shell "i2cdetect -y -r 3" # 第二步:读 PID 寄存器 0x3000/0x3001,返回 0x56 0x47 adb shell "i2ctransfer -f -y 3 w2@0x36 0x30 0x00 r2"

i2cdetect的结果如果能在 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 摄像头的效率能提升一大截,希望帮到你。

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

返回列表