免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

s3c6410与TVP5150视频采集驱动移植:从I2C调试到DMA丢帧的完整排错指南

s3c6410与TVP5150视频采集驱动移植:从I2C调试到DMA丢帧的完整排错指南 简介基于s3c6410平台与WinCE6.0环境的TVP5150驱动源代码面向嵌入式BSP开发工程师与视频采集驱动学习者可有效解决模拟视频AVIN输入驱动的移植、编译与调试问题。整个rar压缩包内共7个文件包括驱动头文件、C实现源码、makefile构建脚本、sources配置及宏定义文件等整体仅17KB结构精简便于直接阅读、编译集成或按项目裁剪移植。驱动内部可直接修改TVP5150寄存器对图像饱和度、色度、对比度和亮度进行灵活调节同时支持设定图像输出尺寸以及将彩色图像转为灰度图像等常用功能代码还能适配友坚AVIN模块缩短外设接入的二次开发周期。目前已有244人学习下载非常适合具备WinCE6.0基础、需要在s3c6410上快速实现视频输入或参考TVP5150寄存器配置的工程师使用是一份轻量且实用的驱动参考源码。 去年秋天接了个老项目的维护活儿客户把一批当年用 s3c6410 做的板卡又翻了出来要求接入模拟摄像头做视频采集解码芯片用的是德州仪器的 tvp5150。这活儿本身不算新但麻烦就麻烦在这套组合的驱动源代码在网上磨了三天也没找到一份能直接编译的BSP 里自带的 camera 驱动和 tvp5150 驱动又各自为政拼起来各种对不上。这篇文章就是我把整套驱动重新梳理、移植、调通之后的一次完整记录适合正在做 s3c6410 视频采集、或者在其它 ARM 平台上移植 tvp5150 驱动源代码的工程师参考。1. 先别急着写代码把视频信号链路走一遍很多人在这种项目上栽跟头不是因为代码难写而是因为没搞明白模拟视频信号从进来那根线到内存里那帧数据中间到底经过了哪些环节。s3c6410 和 tvp5150 这套组合链路其实非常清晰但我还是建议先把信号路径画出来再动键盘。1.1 TVP5150 在这条链路里扮演什么角色TVP5150 是德州仪器一款非常经典的模拟视频解码芯片输入可以是 CVBS 复合视频也可以是 S-Video 的 Y/C 分量输出则是 8 位 YUV 4:2:2 格式的 BT.656 数字流。所谓 BT.656就是数字视频流里把行场同步信息以 SAV/EAV 码的形式嵌入到数据流中而不是像 BT.601 那样用独立的 VSYNC/HREF 引脚去传。这意味着数据线上除了像素时钟 PCLK 和 8 位数据线以外不需要额外的同步线硬件连接会干净很多。芯片的控制接口是 I2C默认从设备地址是 0xBA8 位写地址换算成 7 位地址就是 0x5D。这句话在后面调试时特别重要因为 i2c_board_info 里填的是 7 位地址而很多人习惯按 8 位地址去扫总线结果自然是扫不到设备。1.2 S3C6410 的 Camera 接口和 BT.656 的匹配度S3C6410 内部自带一个 Camera Interface 控制器支持 ITU-R BT.601/656 协议8 位 YUV 4:2:2 输入数据进来之后 DMA 直接把帧写入 DDR。设计上它本来就是为了接 CMOS 摄像头或者解码器输出的数字 YUV 流所以和 TVP5150 的输出格式是天然匹配的中间不需要加 FPGA、CPLD 或者额外的电平转换芯片。典型接线关系大概是这样的TVP5150 引脚描述S3C6410 Camera 接口YO0 ~ YO78 位 YUV 数据输出CAM_DATA0 ~ CAM_DATA7PCLK像素时钟输出CAM_PCLKSCL / SDAI2C 控制接口接到 I2C 总线XTI参考时钟输入外接 27MHz 晶振或由 CAM_MCLK 提供PWDN掉电控制需要拉低否则芯片不工作VREF / HREF垂直/水平参考BT.656 下可悬空可接可不接这里有一个容易踩的点TVP5150 需要外部参考时钟绝大多数参考电路用的是 27MHz 晶振。有人想从 s3c6410 的 CAM_MCLK 输出直接引过去但这个时钟是给 CMOS 传感器用的频率是否配得出 27MHz 要看具体 BSP 的时钟树我建议直接外挂无源晶振或者有源晶振省得在时钟分频上浪费时间。2. 驱动源代码的骨架v4l2_subdev 和 Platform Camera 接口如何分层把硬件链路搞清楚之后再看软件框架就会顺很多。Linux 下这类视频采集驱动内核里管得非常明白一条链路分成三块用户空间的 /dev/videoX 由 camera host driver 提供tvp5150 作为一个 I2C 从设备以 v4l2_subdev 身份挂载host 和 subdev 之间通过 V4L2 框架完成格式协商和控制信令的传递。2.1 老 BSP 和新内核的框架分歧点s3c6410 最活跃的年代Linux 内核还停留在 2.6.28、2.6.35 左右那时候三星官方 BSP 里的 camera 驱动多数走的是一套私有接口类似 v4l2-int-device 或者 soc_camera 框架。如果你现在拿着那个年代的驱动源代码直接往新内核上编译大概率会碰到接口已经面目全非的问题。如果你手头的内核是 3.x 以上我强烈建议直接走标准 v4l2_subdev platform_driver 的路线不要再去适配那套老接口。我在移植时选的就是这条路径整个驱动源代码结构清晰调试起来也方便。2.2 TVP5150 侧的 subdev 注册骨架TVP5150 驱动的核心说到底就是三件事I2C probe 出芯片、注册 v4l2_subdev 操作集、在 s_stream 时把寄存器配好。代码骨架大致是这样static const struct v4l2_subdev_ops tvp5150_ops { .core tvp5150_core_ops, .video tvp5150_video_ops, .pad tvp5150_pad_ops, }; static int tvp5150_probe(struct i2c_client *client) { struct v4l2_subdev *sd; sd devm_kzalloc(client-dev, sizeof(*sd), GFP_KERNEL); if (!sd) return -ENOMEM; v4l2_i2c_subdev_init(sd, client, tvp5150_ops); tvp5150_reset(sd); return 0; } static const struct i2c_device_id tvp5150_id[] { { tvp5150, 0 }, { } }; MODULE_DEVICE_TABLE(i2c, tvp5150_id); static struct i2c_driver tvp5150_i2c_driver { .driver { .name tvp5150, }, .probe tvp5150_probe, .id_table tvp5150_id, }; module_i2c_driver(tvp5150_i2c_driver);视频操作集里最核心的是 s_stream一个 0 参数代表停止采集1 参数代表启动采集。TVP5150 的寄存器初始化最好放在这里做因为有人可能先把 video device open 了但不立刻采集过早配置没有意义。2.3 S3C6410 CAMIF 侧的 platform 驱动骨架Camera host 这边的驱动本质是一个 platform_driver负责手机控制器的寄存器、时钟、DMA并注册一个 video_device。s3c6410 没有设备树所以设备注册都在板级代码里完成。static struct platform_driver s3c_camif_driver { .probe s3c_camif_probe, .remove s3c_camif_remove, .driver { .name s3c-camif, }, }; module_platform_driver(s3c_camif_driver);板级文件里要么用 platform_device_register_simple要么在初始化数组里填好 resource把 CAMIF 的寄存器基地址、中断号、时钟名称一次性传给驱动。很多人在这一步卡住是因为不知道 CAMIF 的时钟名字在时钟驱动里不叫 camera而叫 camif或者是别的变体导致 clk_get 失败DMA 从未启动。3. 初始化寄存器与格式协商看起来简单实际决定成败TVP5150 的寄存器初始化网上一搜能搜到一大堆初始化表有的写了几十行。但我的经验是TVP5150 出厂默认配置就能工作真正需要改的寄存器非常少初始化表越长反而越容易因为某个位写错导致出各种奇奇怪怪的图像问题。3.1 I2C 地址的 7 位 / 8 位陷阱我在调试时先用 i2cdetect 扫描 I2C 总线在地址 0x5D 处发现了一个设备当时判断应该就是 TVP5150。但用 i2cset 直接读写时却始终失败后来才反应过来i2c-tools 的 i2cset 操作的是 8 位地址而内核 i2c_board_info 填的是 7 位地址两个体系经常把人绕晕。在内核里注册 tvp5150 时board_info 应该这么写static struct i2c_board_info __initdata xxx_i2c_devs[] { { I2C_BOARD_INFO(tvp5150, 0x5d) }, }; i2c_register_board_info(1, xxx_i2c_devs, ARRAY_SIZE(xxx_i2c_devs));注意这里第二个参数是 0x5d不是 0xba。如果你拿到的 BSP 里写的是 0xba要么那个驱动在寄存器读写时做了移位要么你遇到的就是一个 8 位地址和 7 位地址混用的历史遗留 bug。3.2 寄存器初始化策略不是越多越好TVP5150 的寄存器 0x00 控制输入源选择默认是 CVBS 输入。如果你的摄像头接的是 AIP1A 口低两位保持默认即可如果是 AIP1B才需要改这个寄存器的值。另外一个需要针对性设置的是制式PAL 制式输出 720x576NTSC 输出 720x480如果摄像头是 PAL 而代码里写死 720x480画面会滚动或者下半屏是花屏。我建议的初始化流程是上电后先读回几个关键寄存器确认 I2C 通信正常保持默认寄存器配置先试采一帧根据采集到的图像现象再针对性修改输入源寄存器和制式相关寄存器。不要一上来就把网上那份几十行的初始化表整个灌进去问题会变得很难定位因为你不知道是哪一行配置引入的异常。3.3 格式协商里最容易漏掉的对齐TVP5150 输出的是 YUYV即 V4L2_PIX_FMT_YUYV每个像素两个字节宽度方向必须保证 16 字节对齐这也是很多摄像头 buffer 申请失败、DMA 写入异常的原因之一。在 s32c6410 的 CAMIF 驱动里如果用户空间请求的分辨率宽度不是 4 的倍数最好在 try_fmt 回调里做一次向上对齐别把这个问题丢给 DMA 层。4. 排错实战从 i2cdetect 到 DMA 丢帧的完整排查链路驱动第一次完整编译通过不代表链路就正常接下来的调试才是大头。我把自己在这套组合上踩过的坑按排查顺序列出来这个过程比最终解决方案本身更有价值。4.1 I2C 扫不到设备先把供电和复位管脚查一遍如果 i2cdetect -y 1 里看不到 0x5D首先要查的其实不是驱动代码而是硬件。TVP5150 的 PWDN 引脚如果被拉高芯片直接进入掉电模式I2C 自然没有任何回应。很多评估板的原理图里 PWDN 是由 GPIO 控制复位的板级初始化里必须先把对应 GPIO 拉到低电平。其次检查参考时钟是否起振。用示波器点 TVP5150 的 XTI 引脚如果没有 27MHz 波形I2C 也是扫不到的因为芯片内部完全没有时钟逻辑在跑。建议在板级代码里单独加一个初始化函数上电后先延迟几十毫秒再释放复位这个时序问题容易被人忽略。4.2 能出图但花屏、绿屏多半是制式和极性没有对齐我在第一次采到图时画面全是绿色和灰色条纹第一反应是寄存器配错了。后来发现 tvp5150 默认自动检测制式其实不太可靠信号不好的时候经常误判。解决方法是显式地锁定制式PAL 的摄像头就固定配置 PAL 相关寄存器NTSC 就固定 NTSC。另外注意 s3c6410 CAMIF 的 PCLK、VSYNC、HREF 极性配置。TVP5150 输出的像素时钟默认是上升沿有效数据如果 BSP 板级配置里把 pclk 极性设成了下降沿图像会产生明显的横纹和错位。这个参数在CAMIF 的板级 platform_data 结构体里struct s3c_platform_camera { unsigned int default_width; unsigned int default_height; unsigned int pclk_polarity; /* 0: 上升沿 1: 下降沿 */ unsigned int vsync_polarity; unsigned int href_polarity; };4.3 颜色偏色或者完全没有颜色检查 YUV 和 RGB 转换链TVP5150 输出的本来就是 YUV 4:2:2用户空间如果直接用 RGB 格式打开 /dev/videoX颜色必然不对。正确做法是设置 pixelformat 为 V4L2_PIX_FMT_YUYV拿到原始 YUV 数据后再由上层库转成 RGB 去显示。这个坑在屏幕预览阶段特别常见因为大部分人第一反应是驱动写错了实际上驱动已经在老老实实地输出 YUYV是上层没有正确解释数据格式。另一个偏色来源是 CAMIF 的 YCbCr 色空间配置有的三星 BSP 提供 BT.601 和 BT.709 色域切换默认值不对会导致红色偏橙、蓝色偏紫。4.4 DMA 丢帧的问题急救办法是多给缓冲s3c6410 CAMIF 的 DMA 是直接用描述符把帧数据写到指定内存的如果用户空间只申请了 2 个 buffer而应用层消费速度跟不上硬件就会丢弃新的中断或覆盖旧的帧。在调试阶段我的建议是至少申请 4 个 buffer 进行 mmap 采集同时把 VIDIOC_REQBUFS 的数量提到 8可以解决绝大多数丢帧问题。如果已经申请了很多 buffer 还是周期性丢帧就要怀疑 camif 中断处理里耗时过长。帧完成中断里尽量不要做耗时的操作比如寄存器回读、打印日志只做 buffer 状态迁移和 wake_up 就好。5. 验证手段从裸流 YUV 到一张能看清的画面驱动移植完后验证效果也是门学问。尤其在这种老平台上图形环境未必完整最可靠的办法还是抓裸数据流到本地然后拿到 PC 上去看。5.1 板端抓帧v4l2-ctl 是最趁手的工具在 s3c6410 上跑 v4l2-ctl 需要交叉编译但值得做因为它的调试能力很强。采集 10 帧裸数据到文件的命令大概是这样v4l2-ctl -d /dev/video0 --set-fmt-videowidth720,height576,pixelformatYUYV \ --stream-mmap --stream-count10 --stream-totvp5150.yuv如果板子上实在放不下 v4l2-ctl也可以自己写一个几十行的采集小程序核心就是 open 设备、 VIDIOC_S_FMT、VIDIOC_REQBUFS、VIDIOC_QBUF、VIDIOC_DQBUF 这一套标准流程。5.2 PC 端查看裸 YUV 文件拿到 tvp5150.yuv 后在 PC 上用 mplayer 可以直接看mplayer -demuxer rawvideo -rawvideo w720:h576:formatyuy2 tvp5150.yuv能看到画面说明从 TVP5150 到 CAMIF 到内存的链路完全通了。如果画面有横纹说明制式或者极性还有问题如果画面正常但颜色怪说明上层解释或色空间设置有偏差。这两种现象能帮你快速把问题定位到驱动还是应用层。5.3 运行时用 i2cset 做临时寄存器实验调试过程中我习惯在板端用 i2cset 直接修改寄存器来做 A/B 测试避免反复编译驱动。比如想确认输入源到底有没有选对就在运行时把寄存器 0x00 的输入源位改一改观察输出是否变化。确认寄存器的具体位定义以手册为准别凭记忆写值。手工改寄存器定位问题是一个很有效的手段但改完之后一定要记得把最终确认的值回写到驱动源代码的初始化表里否则重新上电后问题复现你会以为驱动没改对。如果你现在准备复刻这套 s3c6410 tvp5150 的方案我的建议是第一步先别埋在代码细节里上电后老老实实把 I2C 地址扫出来然后用最少的寄存器配置去采一帧原始 YUV 数据链路通了再去逐步叠加功能。驱动调试最怕的不是寄存器没配好而是链路理解错了后面所有努力都是在错误前提上打转。这套 subdev 框架不止适用于 s3c6410换到 i.MX、全志、瑞芯微的平台也基本是同样套路核心代码可以平移这就是为什么值得花时间把驱动源代码骨架和排错链路真正吃透。本文还有配套的精品资源点击获取
返回列表