
简介这份资源是面向嵌入式Linux开发者的OV9650摄像头驱动移植与测试工程针对S3C2410、S3C2440、S5PV210A8等ARM平台解决摄像头驱动适配与图像采集验证的问题。驱动完全兼容V4L2架构基本不涉及ARM摄像头控制寄存器的直接操作在tiny210开发板上可完美运行其他厂商的210板卡只要硬件连接方式相同也能直接使用对于2410、6410等平台或内核版本不同的环境稍作修改即可迁移适合有一定Linux驱动基础、正在做项目或课程设计的开发者参考。压缩包共288个文件约2.29MB核心为ov965x.c、mach-mini210.c等C源码与头文件、Kconfig和Makefile构建脚本另含测试程序所需的工程文件及若干说明文档测试程序依赖OpenCV与Qt4.7。目前已有490人学习可帮助读者快速获得可运行的驱动框架、板级适配思路与采集测试方案省去从零调试的时间。1. 一块老摄像头模组为什么值得单独写一份驱动手上有一块 OV9650 摄像头模组接上 S3C2410、S3C2440 或者 A8 这类老平台第一反应往往是去找现成驱动结果翻遍内核源码树只找到零星几个 sensor 的注册代码寄存器初始化表还得自己补。这份资源就是干这件事的一份自己编写的 OV9650 摄像头驱动配一个能直接跑起来的测试程序覆盖 2410、2440、A8 三类处理器平台。它适合两类人——手里有老开发板、想把摄像头链路打通的人以及想搞清楚 V4L2 驱动从 sensor 上电到出图完整链路的人。OV9650 摄像头驱动这类代码网上零散片段多、能编译能出图的少这份资源的价值在于驱动和测试程序是配套的不用自己猜上层怎么调。2. 驱动架构拆解从 I2C 配置到 V4L2 出图的完整链路OV9650 是一颗 130 万像素的 CMOS sensor输出 RGB565/YUV 格式通过 SCCB本质是 I2C 时序配置寄存器通过并行接口把像素数据送到主控的摄像头控制器。这份驱动的结构基本遵循 Linux 摄像头驱动的经典分层sensor 层负责上电时序和寄存器配置host 层负责 DMA 搬运和中断V4L2 层负责把 buffer 暴露给用户态。下面按这个链路拆。2.1 目录结构与模块划分拿到源码先看目录这类驱动的文件组织通常是这样ov9650_driver/ ├── ov9650.c # sensor 驱动主体probe、寄存器表、上电时序 ├── ov9650.h # 寄存器地址宏、分辨率与格式定义 ├── ov9650_cfg.c # 各分辨率下的寄存器初始化数组 ├── s3c_camif.c # host 控制器DMA、中断、buffer 管理 ├── s3c_camif.h ├── Makefile └── test/ ├── capture.c # V4L2 测试程序open/ioctl/mmap/取帧 └── Makefile这个划分的关键点是 sensor 和 host 解耦。ov9650.c 里不出现任何 S3C 的寄存器地址s3c_camif.c 里也不关心 OV9650 具体某个寄存器写什么值两边通过 platform_data 和 v4l2_subdev 的 ops 对接。换平台时理论上只改 host 层sensor 层能复用——这也是为什么这份代码敢标 2410、2440、A8 三个平台它们的摄像头控制器寄存器布局不同但 OV9650 的配置序列是一样的。Makefile 里一般会区分内核模块编译和测试程序编译两条路径前者用内核的 kbuild 体系后者用交叉编译器直接编。看 Makefile 时重点确认ARCH和CROSS_COMPILE两个变量老平台交叉工具链前缀差异很大arm-linux- 和 arm-none-linux-gnueabi- 混用是最常见的编译失败原因。2.2 sensor 层上电时序与寄存器初始化OV9650 对时序敏感上电顺序错了 probe 阶段读到的 chip id 就是 0xFF 或者 0x00。驱动里这段通常写成这样static int ov9650_power_on(struct ov9650_priv *priv) { int ret; /* 1. 先给模拟电源再给 IO 电源间隔至少 1ms */ regulator_enable(priv-avdd); mdelay(2); regulator_enable(priv-dovdd); mdelay(2); /* 2. 拉低复位保持至少 1ms 后释放 */ gpio_set_value(priv-rst_gpio, 0); mdelay(2); gpio_set_value(priv-rst_gpio, 1); mdelay(5); /* 3. 提供 MCLKOV9650 需要 12MHz~27MHz */ clk_prepare_enable(priv-mclk); mdelay(10); /* 4. 读 chip id 验证通信OV9650 的 PID 寄存器是 0x0A */ ret ov9650_read_reg(priv, 0x0A); if (ret ! 0x96) { dev_err(priv-dev, chip id mismatch: 0x%02x\n, ret); return -ENODEV; } return 0; }这段代码的逻辑顺序不能调换电源稳定之前拉复位没有意义MCLK 没起来之前 I2C 读写会失败。参数上mdelay的毫秒数是保守值实际 datasheet 要求的最小间隔更短但老平台上电源爬升慢多等几毫秒比偶发 probe 失败划算。chip id 读 0x0A 寄存器返回 0x96 是 OV9650 的标识读不到就说明要么 I2C 地址错了OV9650 是 0x30/0x60 两种写法取决于驱动里是否左移一位要么上电时序没走对。寄存器初始化表在 ov9650_cfg.c 里按分辨率分数组比如ov9650_vga[]、ov9650_svga[]。每个数组是一串{reg, val}对最后以{0xFF, 0xFF}之类的哨兵结尾。写的时候用循环下发static int ov9650_write_array(struct ov9650_priv *priv, const struct regval *regs) { int i, ret; for (i 0; regs[i].reg ! 0xFF; i) { ret ov9650_write_reg(priv, regs[i].reg, regs[i].val); if (ret 0) return ret; } return 0; }这里有个容易忽略的点OV9650 部分寄存器写入后需要延时才能生效尤其是 PLL 和时钟分频相关的寄存器。如果初始化表里没有内嵌延时出图会花屏或者偏色。常见做法是在表里插入特殊标记比如{0xFE, 0x00}表示延时写函数里判断到就mdelay。这份代码如果没做这个处理调试时就得手动在关键寄存器后加延时试。2.3 host 层DMA 搬运与中断处理host 层是平台相关的部分2410/2440 的摄像头控制器和 A8 的差异主要在寄存器偏移和 FIFO 深度。核心逻辑是配置 DMA 目标地址、设置帧大小、使能中断然后在中断里切换 bufferstatic irqreturn_t s3c_camif_irq(int irq, void *dev_id) { struct s3c_camif *camif dev_id; struct camif_buffer *buf; /* 帧结束中断当前 buffer 填满换下一个 */ if (camif-irq_status CAMIF_FRAME_END) { buf list_first_entry(camif-active, struct camif_buffer, list); list_move_tail(buf-list, camif-done); buf list_first_entry_or_null(camif-pending, struct camif_buffer, list); if (buf) { list_move_tail(buf-list, camif-active); camif_set_dma_addr(camif, buf-dma_addr); } else { /* 没有新 buffer停掉采集避免覆盖 */ camif_stop_dma(camif); } wake_up(camif-wait); } return IRQ_HANDLED; }这段是驱动能不能稳定出图的关键。三个链表 active/pending/done 分别表示正在写的、排队等写的、已经写满待用户态取走的。中断里只做链表搬移和地址切换不做耗时操作。参数上buf-dma_addr必须是物理地址用户态 mmap 拿到的虚拟地址在驱动里要通过dma_map_single或者平台特定的转换拿到物理地址老平台上没有 IOMMU这一步错了就是 DMA 写到野地址表现是系统直接挂死或者花屏。DMA 地址切换的时机也有讲究必须在帧结束中断里切不能在帧中间切否则一帧图像上半部分是旧 buffer、下半部分是新 buffer出图撕裂。如果调试时看到画面上下错位先查这里。2.4 V4L2 接口用户态看到的那几个 ioctl驱动对上层暴露的是标准 V4L2 接口测试程序里用到的 ioctl 序列基本固定ioctl作用关键参数VIDIOC_QUERYCAP查询设备能力确认有 VIDEO_CAPTURE 和 STREAMINGVIDIOC_S_FMT设置分辨率与像素格式width/height/pixelformatVIDIOC_REQBUFS申请 buffer 数量count 一般 3~4VIDIOC_QUERYBUF查 buffer 长度与偏移用于 mmapVIDIOC_QBUFbuffer 入队indexVIDIOC_STREAMON启动采集类型选 VIDEO_CAPTUREVIDIOC_DQBUF取一帧阻塞等待驱动侧对应实现v4l2_ioctl_ops其中vidioc_reqbufs里分配 DMA 内存vidioc_qbuf把 buffer 挂到 pending 链表vidioc_dqbuf从 done 链表取。这里常见的实现差异是 buffer 内存用dma_alloc_coherent还是videobuf2框架老平台内核版本低很多是自己管理那就得自己处理 cache 一致性——DMA 写完之后要dma_sync_single_for_cpu否则用户态读到的是 cache 里的旧数据表现是画面静止不动或者隔几帧才更新一次。3. 跑起来交叉编译、加载模块与测试程序验证驱动能不能跑通一半看代码一半看环境。老平台的坑集中在工具链、内核版本匹配和设备节点权限上下面按实际操作顺序走一遍。3.1 编译驱动模块先确认内核源码树路径和配置文件。驱动编译依赖内核头文件里的include/media、include/linux/videodev2.h等内核版本不对会直接报结构体成员不存在。# 进入驱动目录指定内核源码路径和交叉编译器 make -C /path/to/kernel-source \ M$PWD \ ARCHarm \ CROSS_COMPILEarm-linux-gnueabi- \ modules参数说明-C切到内核源码目录执行内核的顶层 MakefileM$PWD告诉 kbuild 外部模块在当前目录ARCHarm和CROSS_COMPILE必须和编译内核时用的一致否则生成的目标文件 ABI 不匹配insmod 时报 invalid module format。编译产物是ov9650.ko和s3c_camif.ko如果 host 层也编成模块。如果报No such file or directory: asm/arch/regs-camif.h这类错说明内核源码树没配好或者头文件路径变了老内核里asm/arch是符号链接新一点的版本改成了mach/需要手动改 include 路径或者建软链接。3.2 加载模块与确认设备节点模块加载顺序是先 host 后 sensor因为 sensor 的 probe 依赖 host 注册的 subdev 适配器# 先加载 host 控制器驱动 insmod s3c_camif.ko # 再加载 sensor 驱动i2c 地址通过模块参数传 insmod ov9650.ko i2c_addr0x30 # 确认 video 设备节点生成 ls -l /dev/video*加载后用dmesg | tail -30看 probe 日志正常应该看到 chip id 匹配、分辨率表加载、video 设备注册三条信息。如果只看到 host 注册没有 sensor检查 i2c_addr 参数——有些板子上 OV9650 的地址是 0x30有些是 0x60取决于硬件上 SIO_C/SIO_D 的上拉和地址引脚接法。设备节点没生成的话确认内核配置里CONFIG_VIDEO_DEV和CONFIG_VIDEO_V4L2是打开的。3.3 编译并运行测试程序测试程序是纯用户态代码只依赖 V4L2 头文件cd test arm-linux-gnueabi-gcc capture.c -o capture -lpthread # 拷到板子上运行参数是设备节点、分辨率、输出文件名 ./capture /dev/video0 640 480 frame.rawcapture.c 的主循环逻辑是打开设备、设置格式、申请并 mmap 四个 buffer、全部入队、STREAMON、循环 DQBUF 取帧写文件再 QBUF 放回。取够帧数后 STREAMOFF 并 munmap。输出的是 raw 数据RGB565 的话可以用 ffmpeg 转成图片验证# RGB565 raw 转 png宽高按实际设置填 ffmpeg -f rawvideo -pix_fmt rgb565 -s 640x480 -i frame.raw frame.png如果转出来的图是绿的或者全黑先确认 pixelformat 设置的是 V4L2_PIX_FMT_RGB565 还是 YUYVOV9650 两种都支持但驱动里默认输出格式和测试程序请求的格式必须一致否则驱动会做一次格式转换或者直接拒绝 S_FMT。4. 避坑与排查五个让驱动跑不起来的典型问题4.1 probe 失败dmesg 里 chip id 读到 0x00 或 0xFF现象是 insmod 之后没有 video 节点dmesg 打印 chip id mismatch。原因通常是 I2C 通信根本没建立要么上电时序里 MCLK 没起来OV9650 内部逻辑没工作要么 I2C 地址写错0x30 和 0x60 混淆要么板子上 SIO_C/SIO_D 没有上拉电阻信号浮空。解决顺序是先量 MCLK 有没有波形再用 i2c-tools 的i2cdetect扫总线看设备在不在最后对照原理图确认地址引脚。4.2 出图花屏或颜色错乱现象是能取到帧但画面是彩色噪点或者颜色整体偏。原因多半是寄存器初始化表里缺少延时PLL 还没锁定就写了后续寄存器或者输出格式和测试程序请求的不一致驱动按 YUYV 配的寄存器但程序按 RGB565 解析。解决方法是先在初始化表的关键位置插mdelay(1)试再确认 S_FMT 返回的 pixelformat 和预期一致。4.3 取几帧后卡死或系统挂掉现象是 capture 程序跑几秒后不再输出严重时内核 panic。原因是 DMA 地址用了虚拟地址或者 buffer 释放后 DMA 还在写。检查vidioc_qbuf里地址转换是否走了virt_to_phys或平台 DMA 映射接口以及 STREAMOFF 之后是否等 DMA 真正停止再释放内存。老平台上没有 IOMMU 兜底这类错误是直接挂死没有后悔药。4.4 画面静止不动DQBUF 一直返回同一帧内容现象是程序在跑、帧数在涨但每帧数据一模一样。原因是 cache 一致性没处理DMA 写进内存的数据还在 cache 里没刷到用户态可见的位置。解决是在 DQBUF 之前调用dma_sync_single_for_cpuQBUF 时调用dma_sync_single_for_device。如果驱动用的是dma_alloc_coherent分配的内存则不需要但很多老代码是kmalloc加手动映射就必须补这一步。4.5 换到另一个平台编译报寄存器未定义现象是在 2440 上编过的代码拿到 A8 平台报一堆regs-camif.h里的宏找不到。原因是 host 层直接 include 了平台头文件换平台头文件路径和宏名都变了。解决是把平台相关的寄存器操作抽成一组函数指针或者条件编译sensor 层完全不动只替换 host 层的实现文件。这也是这份代码标三个平台时最需要验证的地方——sensor 部分应该零修改host 部分按平台选编译。5. 二次开发换 sensor、加格式、提帧率的改造路径拿到这份代码之后最常见的改造需求有三个换一颗别的 sensor、增加一种输出格式、把帧率提上去。三个方向的改动位置完全不同分开说。换 sensor 是最省事的因为分层做对了的话只动 sensor 层。新建一个xxx.c照着 ov9650.c 实现v4l2_subdev_ops里的s_power、s_stream、get_fmt、set_fmt几个回调寄存器表换成新 sensor 的初始化序列chip id 校验改掉host 层一行不用动。验证方法是先只加载 host 和新 sensor 模块用v4l2-ctl --list-formats看格式枚举对不对再跑 capture 取一帧。这里有个技巧新 sensor 的初始化表不要一次全抄 datasheet先只配最小集合时钟、输出格式、分辨率能出图之后再逐段加自动曝光、白平衡这些出问题容易定位是哪一段配置引起的。加输出格式要同时改两层。sensor 层在set_fmt里根据请求的 pixelformat 选不同的寄存器表host 层在 DMA 配置里改每像素字节数和帧大小。比如从 RGB565 加一路 YUYV帧大小从w*h*2变成w*h*2YUYV 也是两字节但 DMA 的 burst 长度和 FIFO 阈值可能要调。改完用v4l2-ctl --set-fmt-videowidth640,height480,pixelformatYUYV配合 capture 验证重点看颜色对不对——YUYV 的 Y 和 UV 顺序搞反了画面会偏绿或偏紫。提帧率是最麻烦的瓶颈通常在三个地方sensor 的 PLL 配置、host 的 DMA 带宽、buffer 数量。先确认 sensor 寄存器表里帧率相关的分频值OV9650 的 CLKRC 和 COM 系列寄存器决定 PCLK 和帧时序再看 host 的 DMA 是否用了 burst 模式单次传输长度够不够最后把 buffer 数量从 3 加到 4 或 5减少 DQBUF 等待时的丢帧。我一般的做法是先不改代码用v4l2-ctl --stream-mmap --stream-count100测当前实际帧率确认瓶颈在 sensor 还是 host 再动手避免改了一圈发现是测试程序自己处理慢。改造方向改动文件验证命令易错点换 sensor新增 sensor .c 改 Makefilev4l2-ctl --list-formatschip id 和上电时序加格式sensor set_fmt host DMA 配置v4l2-ctl --set-fmt-videoYUV 分量顺序提帧率sensor PLL 表 buffer 数量v4l2-ctl --stream-count先测瓶颈再改最后说个习惯每次改完 sensor 寄存器表我都会先用最小分辨率比如 160x120跑一遍 capture确认链路通了再上目标分辨率。小分辨率下 DMA 压力小、出问题容易复现等大分辨率花屏了再回头查变量太多根本分不清是时序问题还是带宽问题。这套流程走下来换 sensor 和调格式基本能在一两天内出图比对着 datasheet 盲调快得多。希望帮到你。本文还有配套的精品资源点击获取