尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
MIPI/DVP CSI摄像头驱动移植
1、原理图1.1 dcmiport代表硬件模块上的视频接口DCMI 的 portDCMI 控制器的接收口SINKOV5640 的 port摄像头 sensor 的发送口SOURCEendpointport 上面的连接端子用来标记这根连线。remote-endpoint互相指向对方告诉内核 media graph这两个端点连在一起。1.2 i2cov56401.3 framefmt‌‌framefmt‌ 是 Linux V4L2 框架里的核心结构体 struct v4l2_mbus_framefmt用于描述媒体总线上的图像格式比如像素格式、分辨率、色彩空间这些。‌‌1.4 DCMIDCMI 属于 Linux Media 子系统v4l2 框架最终应用通过 /dev/videoX 捕获图像stm32-dcmi 驱动只负责并行数据接收OV5640 的寄存器配置是另一个 i2c sensor 驱动ov5640.c两个节点通过 remote-endpoint 完成链路绑定内核会构建 media graph1.5为什么两边 endpoint 都要写总线参数看这段代码ov5640_probe 读取sensor 端 endpoint参数用来配置 sensor 输出时序dcmi_probe 读取dcmi 端 endpoint参数用来配置 DCMI 采样时序ov5640_core_ops核心操作函数作用谁调用何时调用.s_powerov5640_s_power打开/关闭传感器电源上下电、复位主控驱动DCMI或应用层通过VIDIOC_S_POWERioctl应用 open/close 设备时或 host 驱动 probe 阶段需要上电做初始化时.log_status打印传感器当前状态用于 debugV4L2 core通过VIDIOC_LOG_STATUS用户手动触发ioctl(fd, VIDIOC_LOG_STATUS).subscribe_event/.unsubscribe_event订阅/退订事件如帧率变化、控制值变化通知V4L2 控件子系统 (v4l2-ctrls)应用调用VIDIOC_SUBSCRIBE_EVENT时最常用的是 s_power它的典型调用链应用 open(/dev/videoX) → dcmi_open() → v4l2_fh_open() → pm_runtime_get() → 如果之前是关闭的host 驱动会调用: v4l2_subdev_call(subdev, core, s_power, 1) ← 上电 → ov5640_s_power(1) → regulator_enable(DOVDD) → gpiod_set_value(PWDN, 0) // 退出 powerdown → gpiod_set_value(RESET, 1) // 拉高复位 → clk_prepare_enable(xclk) // 给 MCLK → ov5640_identify_chip() // 读 chip_id 确认在位ov5640_video_ops视频流操作函数作用谁调用何时调用.s_streamov5640_s_stream启动/停止传感器输出像素流主控驱动DCMI在 STREAMON/OFF 时调用整个驱动里最重要的函数——应用VIDIOC_STREAMON时触发.g_frame_interval读取当前帧率如 30fps →{numerator1, denominator30}V4L2 core / 应用通过VIDIOC_G_PARM应用查询帧率时.s_frame_interval设置帧率应用通过VIDIOC_S_PARM应用修改帧率时如切换到 15fpss_stream 的调用链是最核心的一条线应用: ioctl(fd, VIDIOC_STREAMON, type) → V4L2 core: video_ioctl2() → vb2_core_streamon() → vb2_ops.start_streaming (即 dcmi_start_streaming) → dcmi_pipeline_s_stream(1) // 遍历 media 管线 → v4l2_subdev_call(subdev, video, s_stream, 1) ← 关键一跳 → ov5640_s_stream(enable1) → if (pending_mode_change) ov5640_set_mode() // 写分辨率/时序寄存器 → if (pending_fmt_change) ov5640_set_framefmt() // 写格式寄存器 → ov5640_set_stream_dvp(sensor, 1) // 打开 DVP 输出使能位 → ov5640_write_reg(sensor, OV5640_REG_SYS_CTRL1, 0x01) // startov5640_pad_ops媒体管线/格式操作这是格式协商阶段用到的表所有函数都在应用调用 VIDIOC_S_FMT 等 ioctl 时被触发函数作用谁调用何时调用.enum_mbus_code枚举传感器支持的像素格式编码如 YUYV、JPEG、RGB565V4L2 core通过VIDIOC_ENUM_FMT应用查询支持哪些格式时.get_fmtov5640_get_fmt读取当前配置的格式mbus_code、宽×高V4L2 core / host 驱动通过VIDIOC_G_FMT或管线遍历时应用查询当前格式host 在dcmi_graph_notify_complete初始化默认格式时.set_fmtov5640_set_fmt设置目标格式只记录真正写寄存器留到 STREAMONV4L2 core / host 驱动通过VIDIOC_S_FMT应用ioctl(fd, VIDIOC_S_FMT, fmt)时.enum_frame_size枚举支持的分辨率如 640×480、1080P应用通过VIDIOC_ENUM_FRAMESIZES应用查询支持哪些分辨率时.enum_frame_interval枚举某分辨率下支持的帧率应用通过VIDIOC_ENUM_FRAMEINTERVALS应用查询这个分辨率下有哪些帧率可选时v4l2_async_register_subdev_sensor_common(sensor-sd)把这个 subdev 交给异步框架。整体链路逻辑1. DCMI probe 执行解析DTS endpoint → 读取data-shift/bus-widthmedia_entity_pads_init 创建SINK padvb2_queue_init 初始化buffer队列dcmi_graph_init 创建async notifier等待OV56402. OV5640 i2c驱动 probe执行media_entity_pads_init 创建SOURCE padv4l2_async_register_subdev_sensor_common 注册notifier等待DCMI3. 两端全部probe成功 → V4L2异步框架匹配成功 → media_create_linkgraph连通4. 应用层 open /dev/video0 → VIDIOC_S_FMT 设置分辨率格式 → VIDIOC_REQBUFS申请buffer → VIDIOC_QBUF入队buffer → VIDIOC_STREAMON5. STREAMON调用链路dcmi的streamon → 调用subdev s_stream → ov5640_s_stream就是第一张截图开启sensor输出DVP信号6. DCMI硬件接收PCLK/VSYNC/HSYNCDMA搬运图像到vb2 buffer中断通知上层2.流程逻辑分析2.1 DCMI 控制器端a.先判断设备是否匹配检查设备树中是否有与此驱动匹配的兼容性字符串创建DCMI私有数据结构体b.获取复位控制器用于后续硬复位解析设备树中的 endpoint端点读取摄像头与 DCMI 之间的物理连接属性。关键点代码明确检查了 ep.bus_type如果发现是 MIPI CSI-2 接口直接报错退出因为此 DCMI 驱动只支持并口Parallel。如果你的 OV5640 是 MIPI 接口这个驱动不会生效。保存并口的配置总线宽度如 8/10/12 位、数据偏移等。c.interruptsGIC_SPI 78 → platform_get_irq request_threaded_irq (L1910,1926)reg0x4c006000 → platform_get_resource ioremap (L1914,1920)clocks/clock-namesmclk → devm_clk_get(mclk) (L1934)dmas/dma-namestx → dma_request_chan(tx) (L1941)获取资源d.初始化并发控制与链表irqlock自旋锁中断上下文使用lock/dma_lock互斥锁保护 V4L2 ioctl、DMA 操作complete等待 DMA 采集完成的同步对象buffers维护正在使用的 vb2 buffer 链表e.media_device_init初始化 media_device整个 media graph 的顶层管理对象OV5640 subdev 也注册到这个 mdevv4l2_device_register注册 V4L2 顶层设备一个 media 设备对应一个 v4l2_device可以包含多个 subdev video 节点框架骨架v4l2_device_register → video_device_alloc → vb2_queue 初始化 → media_entity_pads_initf.dcmi_graph_init内部初始化v4l2_async_notifier异步通知器解析 DTS endpoint 的remote-endpoint等待远端 OV5640 sensor subdev 就绪和 OV5640 那边的v4l2_async_register_subdev_sensor_common配对不管是 OV5640 先 probe 还是 DCMI 先 probe等两端都 probe 成功V4L2 异步框架自动调用匹配回调执行media_create_link把 OV5640 SOURCE pad ↔ DCMI SINK pad 建立 linkmedia graph 连通。dcmi硬件复位platform_set_drvdata把 dcmi 私有结构体挂载到 platform_device后面 ioctl、中断里可以通过dev_get_drvdata拿到 dcmi 指针pm_runtime_enable开启电源管理【物理世界】 ┌──────────────────┐ I2C控制 ┌──────────────┐ │ SoC 内 DCMI 控制器 │ ──────────────────────→ │ OV5640 传感器 │ │ (并行采集硬件) │ (发寄存器配置命令) │ (数据源) │ └────────┬─────────┘ └──────┬───────┘ │ DVP 并行总线(DATA/PCLK/HSYNC/VSYNC) │ └───────────────────────────────────────────┘ 【V4L2 框架层】 ┌─────────────────────────────────────────────────────────┐ │ v4l2_device (注册中心/根节点) │ ← 不属于任何物理设备纯粹是框架的管理层 │ ├── video_device (→ /dev/video0) │ ← 对应 DCMI 的用户接口面 │ │ └── vb2_queue (DMA 缓冲队列管理) │ │ └── v4l2_subdev (OV5640) │ ← 对应 OV5640 传感器 │ └── ov5640_subdev_ops (.core/.video/.pad) │ └──────────────────────────────────────────────────────────┘v4l2_device 不是物理设备它是 V4L2 框架的管理层/注册中心video_device 是 DCMI 的用户接口面生成 /dev/videoXv4l2_subdev 是 OV5640 的标准契约外壳让 DCMI 能用统一接口操作任意传感器。物理上只有两块芯片但 V4L2 把管理、接口、子设备拆成三个结构体目的是让 host 驱动和 sensor 驱动彻底解耦。struct ini dcmi_graph_init相关函数解析// dcmi_graph_parse() 里 struct device_node *ep, *remote; struct v4l2_async_subdev *asd; ep of_graph_get_next_endpoint(np, NULL); // 拿到 dcmi 自己的 endpoint remote of_graph_get_remote_port_parent(ep); // 顺着 remote-endpoint 拿到 OV5640 的节点 asd kzalloc(sizeof(*asd), GFP_KERNEL); asd-match_type V4L2_ASYNC_MATCH_FWNODE; // ← 告诉框架用 fwnode 匹配 asd-match.fwnode of_fwnode_handle(remote); // ← 填入 OV5640 节点的 fwnode 指针 v4l2_async_notifier_add_subdev(dcmi-notifier, asd);此时 DCMI 并没有主动去找 OV5640它只是向异步框架交了一张寻人启事——我需要的子设备它的 fwnode 是这个指针出现了通知我时序图时间轴 ─────────────────────────────────────────────► [DCMI 驱动加载] [OV5640 驱动加载] [结果] │ │ │ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ dcmi_probe │ │ ov5640_probe│ │ 绑定完成 │ │ │ │ │ │ │ │ │ │ ▼ │ │ ▼ │ │ │ │ dcmi_graph_ │ │ 初始化 sd │ │ │ │ _init() │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ 解析设备树 │ │ v4l2_async_ │ │ │ │ 找到 ov5640 │ │ register_ │ │ │ │ 节点引用 │ │ subdev_ │ │ │ │ │ │ │ sensor_ │ │ │ │ ▼ │ │ common() │ │ │ │ 注册 notifier│◄──────│ │ │ │ │ │ 到全局列表 │ │ ▼ │ │ │ │ │ │ │ 框架检查 │ │ │ │ │ │ │ 匹配条件 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ └───────┴────────┤ 匹配成功 │ │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ │ 调用 DCMI │ │ │ │ │ 的 bound() │ │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ │ 建立 media │ │ │ │ │ pad link │ │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ │ 调用 │ │ │ │ │ complete() │ │ │ │ │ 注册 │ │ │ │ │ /dev/video0 │ │ │ └─────────────┘ └─────────────┘ └─────────────┘2.2 ov5640传感器端a.MEDIA_BUS_FMT_UYVY8_2X8就是 8bit 并行 DVP 总线UYVY每个像素拆成 2 个 8bit 样本在数据线传输这是 subdev 的 pad 输出格式是 sensor 往 DCMI 发的数据格式。v4l2_mbus_framefmt 描述的是 media pad 上传输的数据格式不是用户态 buffer 格式是 media graph 链路协商的核心结构体。b.读 DTS 根节点的rotation 180属性属于sensor 本体属性不是 endpoint 总线属性。c.fwnode_graph_get_next_endpoint从 ov5640 这个设备节点找到它 port 下的 endpointov5640_0v4l2_fwnode_endpoint_parse解析 endpoint填充 sensor-epstruct v4l2_fwnode_endpoint里面就包含 ep.bus.parallel拿到 bus-width/data-shift/pclk-sample/hsync/vsync-active/pclk-max-frequency。(sensor 驱动拿到这组总线参数用来配置 OV5640 内部输出时序让 sensor 硬件输出符合约定的 DVP 信号。DCMI 驱动拿到它自己 endpoint 的参数用来配置 DCMI 采样时序。)d.ov5640_subdev_opssubdev 的回调函数集合里面包含 get_fmt/set_fmt、stream_on/stream_off 等子设备接口subdev 就是 media_entity对应 media graph 里的 camera-sensor 实体。MEDIA_PAD_FL_SOURCE这个 pad 是输出源 pad数据从 sensor 往外发media_entity_pads_init初始化 media entity 的 padDCMI 那边的 pad 是 MEDIA_PAD_FL_SINK接收。media graph link 就是把 SOURCE padov5640 ↔ SINK paddcmi连接起来。2C 读取 OV5640 芯片 ID确认硬件在线这是 sensor 驱动经典第一步硬件校验。初始化 v4l2_ctrl曝光、增益、白平衡等控制项用户态可以通过 subdev 节点去调整 sensor 寄存器。异步注册 subdev这是 media graph 摄像头非常关键的机制v4l2_async_register_subdev_sensor_common 属于异步子设备注册sensori2c 子设备先注册但此时DCMI 主 video 设备可能还没 probe 完成。内核 media 框架会等待两边全部就绪自动匹配 remote-endpoint创建 media link。这就是为什么 DTS 里只需要互相写 remote-endpoint不用手动在代码里创建 link。老式非 async 驱动必须保证 probe 顺序很麻烦async 框架解决多实体 probe 先后顺序问题。行号函数调用干了什么1138v4l2_async_notifier_init(notifier)初始化一个 notifier 对象1140v4l2_async_notifier_parse_fwnode_sensor_common(...)解析设备树根据port/endpoint自动生成 asd 匹配规则相当于传感器的反向寻人启事1145v4l2_async_subdev_notifier_register(sd, notifier)把这个 notifier 挂到全局链表和 DCMI 的 notifier 一样1149v4l2_async_register_subdev(sd)← 就是这一步把 subdev 挂到全局链表 遍历所有 notifier 逐个比对3.重要函数3.1获取端点建立连接of_graph_get_next_endpoint3.2匹配类型选择enum 保证了代码的可读性和类型安全通过 switch-case 可以防止访问错误的联合体成员。 union 保证了所有可能的匹配参数共享一块内存不会因为定义了一个庞大的结构体而浪费内核内存。3.3notifier.ops 三个回调bound/complete/unbind的注册bound回调时机3.4核心匹配机制代码体现a.// dcmi_graph_parse() 里 struct device_node *ep, *remote; struct v4l2_async_subdev *asd; ep of_graph_get_next_endpoint(np, NULL); // 拿到 dcmi 自己的 endpoint remote of_graph_get_remote_port_parent(ep); // 顺着 remote-endpoint 拿到 OV5640 的节点 asd kzalloc(sizeof(*asd), GFP_KERNEL); asd-match_type V4L2_ASYNC_MATCH_FWNODE; // ← 告诉框架用 fwnode 匹配 asd-match.fwnode of_fwnode_handle(remote); // ← 填入 OV5640 节点的 fwnode 指针 v4l2_async_notifier_add_subdev(dcmi-notifier, asd);关键理解此时 DCMI 并没有主动去找 OV5640它只是向异步框架交了一张寻人启事——我需要的子设备它的 fwnode 是这个指针出现了通知我b.V4L2 异步核心把寻人启事登记到全局链表// v4l2_async_notifier_register() 内部 list_add_tail(notifier-list, notifier_list); // 把 dcmi 的 notifier 挂到全局链表上框架会在每次有新 subdev 注册时遍历这个链表c.OV5640 probe 完成向框架报到// ov5640_probe() 末尾 v4l2_async_register_subdev_sensor_common(sensor-sd); // → 内部调用 v4l2_async_register_subdev(sensor-sd) // → 把 sensor-sd 挂到全局 subdev 链表 // → 遍历所有已注册的 notifier逐个比对v4l2_async_register_subdev一旦匹配成功立即调用v4l2_async_match_notify。这个函数是真正建立连接的地方它会创建 V4L2 子设备节点、在 Media Controller 中建立 pad 与 pad 的链接Link让你的 OV5640 和 DCMI 真正连通。接着调用 v4l2_async_notifier_try_complete。因为一个主设备可能等待多个子设备比如等完 OV5640 还要等一个 MIPI 接收器这个函数检查主设备等待的所有子设备是不是都到齐了。如果到齐了就触发主设备的 complete 回调完成整个管线的初始化。极度重要的一点绑定成功后直接 goto out_unlock 跳出循环。一个子设备只能绑定一个主设备。既然已经配对了就不能再去遍历后面的主设备了。d.异步核心执行比对匹配核心这段 switch-case 完美地用到了3.2提到的 enum 配合 union 的设计通过 asd-match_type枚举标签代码决定去哪里、拿什么数据以及用哪个函数去比对。 如果是V4L2_ASYNC_MATCH_FWNODE就选择 match_fwnode。这个函数会去比较 sd 的节点和 asd 里存的那个节点是否一致。如果返回 true说明找到了直接返回当前的 asd代表配对成功主设备知道这个子设备就是自己要找的。 如果返回 false说明当前 asd 条件不满足继续 for 循环看下一个。 如果循环结束都没找到返回 NULL表示这个子设备不是主设备在等的或者还没准备好。sd-dev 是 OV5640 的 struct device由 I2C core 在 probe 前根据 camera3c 节点创建的。 dev_fwnode(sd-dev) 取出这个 device 关联的 fwnode 指针——它就是设备树里 camera3c 那个节点的 fwnode。asd-match.fwnode 是 DCMI 之前存的——它是顺着 remote-endpoint 找到的 camera3c 节点的 fwnode。两者指向同一个设备树节点所以指针相等 → 匹配成功3.5 数据流生命周期苏醒 - 启动引擎 - 搬运数据 - 刹车回收3.6用户层时序应用发 STREAMONV4L2 走 ioctl_ops 找到 DCMIDCMI 通过 v4l2_subdev_call 远程摇醒 OV5640 的 s_stream 寄存器开流硬件像素经 DVP 进 DCMIDMA 自动写内存应用通过 DQBUF 从内存零拷贝取帧。4.1 sensor端的函数ov5640_set_power是总指挥入口负责统筹上下电逻辑并处理 MIPI 接口的专属初始化。 ov5640_set_power_on / off是执行者专门负责底层硬件时钟、供电、复位引脚的开与关。4.2总时序
RELATED

相关推荐

Spring Boot 整合 FISCO BCOS 联盟链实战:从设计到落地

Spring Boot 整合 FISCO BCOS 联盟链实战:从设计到落地

1. 为什么要在 Spring Boot 里整合 FISCO BCOS把联盟链节点接进一个常规的 Java 后端服务,这件事我在两三个项目里都做过。最早的一次是给一个供应链金融的存证模块做技术验证,当时团队的后端清一色是 Spring Boot 技术栈,谁也不想为了调链再…

📅 2026/10/21 5:55:59
差分数组详解:从区间修改到前缀和的高效算法

差分数组详解:从区间修改到前缀和的高效算法

派大星最近在啃算法题,翻来覆去就一句话:差分数组真是个又萌又猛的家伙。别看这名字唬人,背后的数学连小学一年级都算得过来——被减数减减数等于差;可一旦把它放到数组上,它就能把一段区间里一堆重复的加法操作&#…

📅 2026/10/21 5:55:59
哥斯拉Webshell攻防演练复盘:加密通信原理与检测排查

哥斯拉Webshell攻防演练复盘:加密通信原理与检测排查

哥斯拉(Godzilla)这个名字,常年在攻防演练场子里混的人应该都不陌生。它是一款Webshell管理工具,和同类工具相比,最大的特点就是流量加密、动态Payload生成,基本把明文特征这条路彻底堵死了。去年我们在某公…

📅 2026/10/21 5:55:59
MORE NEWS

更多资讯

📰

知识工作插件化:构建可编程、可组合的智能工作流底座

1. 项目概述:这不是一个插件,而是一套知识工作者的“操作系统增强层”“knowledge-work-plugins”这个标题乍看像某个开源仓库的命名,但拆开来看——knowledge work(知识工作)、plugins(插件)—…

📰

SQL题目面试避坑指南:从读题拆解到查询优化全链路

写SQL题目刷了几年,也帮不少同学备过面试、带过新人,我最大的感受是:多数人不是不会写SQL,而是根本不知道一道SQL题目到底在考察什么。同一个查询,有人三行搞定,有人磨叽半小时还漏了边界;同一张…

📰

知识工作插件:面向知识工作者的模块化增强协议

1. 项目概述:这不是一个插件,而是一套知识工作者的“操作系统增强层”“knowledge-work-plugins”——光看这个名字,很多人第一反应是“又一个浏览器插件合集”或者“某款笔记软件的扩展包”。但在我过去三年深度参与多个知识管理系统的搭建与…

📰

SchoolDB四表DDL全解析:数据库设计从字段选择到约束实践

最近给团队做内部培训,我又把一套用了很多年的教学示例库脚本翻了出来——SchoolDB。场景很简单:一个学校管理系统,只有4张表:学生、教师、课程、选课。很多人觉得写DDL语句没什么技术含量,敲几行CREATE TABLE谁不会&a…

📰

订单表慢SQL优化实战:联合索引、覆盖索引与深分页方案

1. 场景回顾:慢SQL是怎么暴露出来的做订单系统的同学应该都有体会:订单表往往是整个业务库里面数据量增长最快、查询模式最多样、索引也最容易“被用坏”的一张表。这个实战项目来自某电商平台的中台订单模块,核心表trade_order存放全渠道的订…

📰

Python实战英雄联盟胜率预测:从数据清洗到SHAP模型解释

简介:LeaguePredictor 是一套面向电子竞技数据分析初学者与机器学习实践者的 Python 项目源码,聚焦英雄联盟比赛胜率预测这一具体场景,帮助读者理解如何把分类模型落地到真实赛事数据上。资源包共 24 个文件,以 22 个 py 脚本为主…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬