
简介OV5648 MIPI RAW 驱动代码是一套面向 OV5648 五百万像素 CMOS 图像传感器的底层驱动适用于基于 MIPI 接口的嵌入式或桌面系统尤其针对 Deepin 等 Linux 环境。压缩包内共包含 11 个文件以 9 个头文件和 2 个 C 源文件为主头文件涵盖传感器初始化、ISP、自动曝光、自动白平衡等关键配置源文件则负责参数表与调优数据的具体实现整体压缩包仅 271KB结构紧凑便于学习和二次开发。目前已有 640 人学习浏览适合摄像头驱动开发初学者或需要移植、定制 OV5648 方案的工程师。通过研读这些代码读者可掌握 MIPI RAW 数据从时序配置到图像输出的完整链路理解驱动与操作系统层的交互方式并可基于其中 AE/AWB 调优参数和寄存器配置快速调整出适配自身摄像头模组的版本从而缩短调试周期并降低底层开发的入门门槛。1. MIPI 接口 OV5648 RAW 驱动这份代码包到底能帮你省多少事做摄像头驱动的人看到ov5648_mipi_raw驱动代码.rar这种命名第一反应基本都是同一个问题又是网上流传的“半成品”还是能直接落地的完整工程我把这包拆完后的结论是它不像某些只丢一个.c文件让你自己猜的散装代码而是把 MIPI 接口 OV5648 sensor 从寄存器配置、上电时序到驱动框架的完整链路都收进来了特别适合正在做深高清deepin或 Linux 平台 camera 适配的工程师。如果你是拿着 datasheet 对着寄存器一个个翻、还在为 image sensor 不出图发愁的状态这份资源能把你的调试起点从零直接拉到“能出 raw 图”的阶段。本文按“协议基础 → 驱动代码结构 → 配置参数 → 调试链路 → 避坑实录 → 验证方法”的顺序拆给你看每个环节我会直接给出可抄作业的做法。2. 先看懂 OV5648 的 MIPI RAW 数据链路像素排列与传输时序2.1 MIPI CSI-2 协议下的 RAW 数据传输结构OV5648 是一颗 500 万像素级别的 sensor输出的 RAW 数据在 MIPI CSI-2 链路上是按DTSData Type来标识的。我们常说的 “RAW8、RAW10、RAW12” 对应 CSI-2 规范里的0x2A、0x2B、0x2C三种数据类型。ov5648 驱动里默认走的多是 RAW10也就是每个像素用 10 bit 表示在 MIPI 链路上通过 4 Lane 传输每 Lane 的速率根据主控端 PLL 配置落在 400 Mbps 到 800 Mbps 之间。驱动初始化时如果 DTS 没配对主控端 csi 控制器会把 raw 数据误解析成打包格式出来的图像就是花屏或者完全偏色这是第一层最容易踩的坑。MIPI 链路上还有一层 LPRXLow Power Receive和 HSHigh Speed模式的切换逻辑。sensor 端通过MIPI_CTRL寄存器组控制 PHY 层的状态切换驱动里的mipi_sw_ctrl配置项就是干这个的。如果上电时序和 MIPI 初始化顺序不对最常见的结果是 CSI 接口报Unsupported data type错误或者 dmesg 里看到csi_phy1: no signal之类的日志。2.2 OV5648 的像素排列如何影响 ISP 处理OV5648 的 sensor 输出是 Bayer 排列驱动代码里通常会暴露一组pattern配置默认是bggr。为什么这个参数重要因为 ISP 在做去 mosaic 时必须知道每个像素点的颜色通道顺序。你要是把 BGGR 的 sensor 配成了 GRBG图像上会出现明显的网状伪彩且这种错误在 raw 图转出来的瞬间就能肉眼判断。驱动代码里ov5648_otp_read会从 OTP 区读 sensor 本身的 color info但主控端 ISP 的color_mode和pattern配置是在驱动侧写死的所以排查偏色问题时不要只盯 sensor 寄存器主控端 ISP 的输入格式同样要核对。这类 RAW sensor 驱动里还有一个隐藏知识点sensor 的active array和effective output size不是同一个概念。OV5648 的 active array 有 2624×1956但实际输出可能是 2592×1944500 万或 2048×1536300 万裁切。驱动里ov5648_set_mode会根据上层下发的分辨率对寄存器做二次配置。// ov5648_mode_change 里典型的 crop 配置 static void ov5648_set_crop(struct ov5648_dev *sensor, u16 width, u16 height) { u16 start_x (2624 - width) / 2; u16 start_y (1956 - height) / 2; // 直接写入 sensor 的 crop start 寄存器组 sensor_write(sensor, 0x3800, start_y 8); // Y 起始高位 sensor_write(sensor, 0x3801, start_y 0xFF); sensor_write(sensor, 0x3802, start_x 8); // X 起始高位 sensor_write(sensor, 0x3803, start_x 0xFF); sensor_write(sensor, 0x3804, (start_x width - 1) 8); // X 结束 sensor_write(sensor, 0x3805, (start_x width - 1) 0xFF); sensor_write(sensor, 0x3806, (start_y height - 1) 8); // Y 结束 sensor_write(sensor, 0x3807, (start_y height - 1) 0xFF); }这段代码做的事是把 sensor 的感光区中心对齐后裁出目标分辨率。0x3800~0x3807这 8 个寄存器是 Omnivision 系 sensor 通用的 crop 配置区width和height由上层通过 V4L2S_FMTioctl 传下来。需要注意部分主控的 ISP 要求输入尺寸必须是偶数对齐你下发奇数宽度时驱动不会拦你但出图后图像边缘会出现一条绿色或黑色的细线我 2024 年在 rk3588 平台上排过一次就是这种问题。3. 驱动代码核心结构拆解从 i2c 读写到 v4l2-subdev 注册3.1 驱动的分层框架sensor 端与主控端怎么协作这份驱动代码整体是标准的 V4L2 subdev 架构分为三层i2c 通信层负责通过 SCCB 协议读写寄存器sensor 控制层负责上电时序、时钟配置、分辨率切换v4l2 抽象层负责向主控端注册 subdev、media controller 接口和 pad format。你下载后最先要看的是ov5648_driver.c里的probe函数整个初始化的秘密都在这。需要留个心眼的地方是sensor 的 i2c 地址是 7 位地址0x368 位读写地址是0x6C。很多第一次调 OV5648 的人会在这里翻车因为有部分旧型号 OV5640 是 0x21地址改了但初始化代码没对应更新。static int ov5648_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct ov5648_dev *sensor; int ret; sensor devm_kzalloc(client-dev, sizeof(*sensor), GFP_KERNEL); if (!sensor) return -ENOMEM; sensor-client client; sensor-pwr_gpio of_get_named_gpio(client-dev.of_node, pwdn-gpios, 0); sensor-reset_gpio of_get_named_gpio(client-dev.of_node, reset-gpios, 0); // 初始化 i2c 读写锁与 subdev mutex_init(sensor-lock); v4l2_i2c_subdev_init(sensor-subdev, client, ov5648_subdev_ops); // 读取 sensor ID 确认硬件连接正常 ret ov5648_read_reg(sensor, 0x300A, val); if (ret 0 || val ! 0x56) { dev_err(client-dev, sensor ID error: 0x%02x\n, val); return -ENODEV; } return 0; }probe里最有价值的不是注册逻辑而是最后那段 ID 校验。0x300A是 OV5648 的高位 ID 寄存器正常读到0x56。如果i2c地址配错或者复位引脚没拉对这里会直接返回-ENODEVsensor 根本不会进入 normal 模式。调试的时候我先看这里如果 ID 读不到就不浪费时间查后续寄存器配置直接回头量 pwdn 和 reset 引脚电平。3.2 上电时序与 GPIO 操作的隐藏细节OV5648 的电源域比 OV5640 更讲究AVDD、DOVDD、DVDD三路供电必须要按先后顺序上电而且间隔不能太小。驱动代码里一般通过pwdn-gpios和reset-gpios来控制但硬件的 RC 延时电路或 PMIC 的 power sequence 配置决定了 GPIO 拉高拉低的实际效果。# 典型的上电时序配置在 dts 里体现 i2c3 { ov5648: ov564836 { compatible ovti,ov5648; reg 0x36; pinctrl-names default; pinctrl-0 ov5648_pins; reset-gpios gpio4 RK_PB0 GPIO_ACTIVE_LOW; pwdn-gpios gpio4 RK_PB1 GPIO_ACTIVE_HIGH; clocks cru CLK_MIPICAM_OUT; clock-names xvclk; clock-frequency 24000000; avdd-supply vcc2v8_dvp; dovdd-supply vcc1v8_dvp; dvdd-supply vcc1v2_dvp; rockchip,camera-module-index 0; rockchip,camera-module-facing back; rockchip,camera-module-name default; rockchip,camera-module-lens-name default; port { ov5648_out: endpoint { remote-endpoint mipi_in_ucam0; >static const struct ov5648_mode ov5648_modes[] { { .width 1920, .height 1080, .hts 2200, // 行总长包括 blanking .vts 1125, // 帧总长 .pclk 72000000, // 像素时钟 72 MHz .reg_list ov5648_1080p_regs, .reg_list_size ARRAY_SIZE(ov5648_1080p_regs), }, { .width 1280, .height 720, .hts 1716, .vts 750, .pclk 72000000, .reg_list ov5648_720p_regs, .reg_list_size ARRAY_SIZE(ov5648_720p_regs), }, };hts和vts决定了帧率公式frame_rate pclk / (hts * vts)。所以你会发现 1080p 和 720p 的 pclk 相同但 hts/vts 不同帧率也不同。调帧率时如果你只改 VTS 不改 HTS会导致行同步异常图像偏上半部分出现横条纹。这类问题在驱动里不会报错只能通过看 raw 图来判断。4. 把 RAW 图正确调出来曝光、增益与时钟配置三个关键参数组4.1 曝光与增益AE 算法的底层寄存器支持OV5648 的曝光控制是通过串行曝光方式实现的寄存器0x3500~0x3503控制曝光时间0x350A~0x350B控制模拟增益。值得注意的是OV5648 的曝光单位为行line实际曝光时间就是exposure / frame_rate。驱动代码里 AE 算法算出的 exposure 值要除以2再写入寄存器因为 sensor 内部是双行同时曝光。static int ov5648_set_exposure(struct ov5648_dev *sensor, u32 exposure) { u32 tmp; // 寄存器单位与 AE 算法单位换算 // AE 内部以 1/16 行精度计算写寄存器时右移 4 位 tmp exposure 4; sensor_write(sensor, 0x3500, (tmp 12) 0x0F); sensor_write(sensor, 0x3501, (tmp 4) 0xFF); sensor_write(sensor, 0x3502, (tmp 0x0F) 4); return 0; }0x3500高 4 位、0x3501中间 8 位、0x3502低 4 位组合成一个 16 位的曝光值。你写代码时如果只操作中低位曝光时间超过 4096 行时高位没写进去画面上就会每隔一阵出现一条亮的横带这就是经典的“曝光 roll over”问题。4.2 主时钟频率24MHz 还是 27MHz代码里该写哪个OV5648 的 XVCLK外部输入时钟支持 6~27 MHz但最推荐的频率是 24 MHz。驱动代码里clock-frequency 24000000这个参数决定主控端给 sensor 送钟的 PLL 配置。如果你实际板子是 27 MHz 晶振而 dts 没改sensor 内部 PLL 会算出错误的 MIPI bit clock出来的帧率比预期低 12% 左右。验证方法很简单示波器量 MIPI clock lane 的翻转频率理论值应该在pclk / lanes * bits_per_pixel附近。量出来偏差超过 5%基本可以断定是 XVCLK 或 PLL 配置有问题。4.3 白平衡与色彩矩阵RAW 图偏色时先查哪里驱动代码里有独立的ov5648_awb配置块包含0x5180~0x51FF一组寄存器。这是 sensor 内部的 AWB 引擎但代码里通常默认关闭——因为 RAW 数据要交给主控端 ISP 处理sensor 端做 AWB 会把数据改坏ISP 那边就没法再按 Bayer 排列正确插值了。如果你在 mipi 调试工具里看到的 raw 图偏绿不要先动 AWB。绿偏大多来自Bayer pattern 配错或白平衡增益失配。把 ISP 的 gain 全部设成 1.0 看输出如果还是绿就回头查 pattern。如果绿变成了正常那就是 ISP 的 AWB 配置问题和 sensor 驱动无关。5. 避坑实录OV5648 MIPI RAW 调试中的五个高频翻车点5.1 现象I2C 通信正常但 sensor ID 读出来是 0xFF原因复位引脚时序不满足。OV5648 上电后 reset 引脚如果拉低时间不够规格书要求最小 5mssensor 内部状态机没完成初始化i2c 寄存器读取会失败。解决在 probe 函数里对 reset 引脚做两次翻转——先拉低延时 10ms再拉高延时 10ms。这个动作必须在读取 ID 之前完成。5.2 现象MIPI 信号有波形但主控端锁不住 lane原因>v4l2-ctl -d /dev/video0 --set-fmt-videowidth1920,height1080,pixelformatRG10P --stream-mmap --stream-count1 --stream-toframe.rawRG10P是 V4L2 里 RAW10 packed 格式的代号如果你的驱动注册的是Y10P或别的四字符码这里不匹配的话抓出来的文件会是错的字节序。抓完后用下面的脚本快速转图验证import numpy as np from PIL import Image # 1920x1080 RAW10 packed每 4 个像素打包成 5 字节 width, height 1920, 1080 raw np.fromfile(frame.raw, dtypenp.uint8) # 按 MIPI RAW10 unpack 规则拆解 pixels np.zeros(width * height, dtypenp.uint16) c 0 for i in range(0, len(raw), 5): for j in range(4): pixels[c] (raw[i j] 2) | ((raw[i 4] (j * 2)) 0x03) c 1 # 转成 BGGR 排列的 RGB 图像 image np.zeros((height, width, 3), dtypenp.uint8) image[:, :, 0] pixels.reshape(height, width) 2 image[:, :, 1] (pixels.reshape(height, width) 1) image[:, :, 2] pixels.reshape(height, width) # 保存成 PNG 供 ISP 工具链继续处理 Image.fromarray(image).save(preview.png)这段脚本做的事是把 5 字节包里的 4 个 10-bit 像素拆开填充到 16-bit 数组里。 2和 1是简单的通道映射实际你在 ISP 里还要做 gamma 和去 mosaic所以这一步只是验证数据流是否完整。如果这步出来的图像还有横纹、花屏或者行错位就回到驱动配置继续查如果图像是“正常的花屏”能看出轮廓说明 raw 数据流通了问题在 ISP 处理。从那以后我每次适配新 sensor都强制自己走一遍“raw 落盘验证”再进 ISP 联调。这套习惯帮我滤掉了至少一半的无效 debug 时间希望也能帮你少掉点头发。本文还有配套的精品资源点击获取