ARTICLE DETAIL

资讯详情

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

C8051F330直读OV7670:SCCB配置与图像数据读取实战

C8051F330直读OV7670:SCCB配置与图像数据读取实战 简介面向嵌入式开发者与单片机学习者这份资源提供C8051F330单片机驱动OV7670摄像头模块的Keil完整工程源代码适合需要快速掌握图像采集、外设配置及驱动移植的入门到进阶人群。资源共15个文件以C源文件、头文件、Keil工程文件为主同时包含启动文件、链接映射及目标文件压缩包仅68KB文件结构清晰便于查阅和二次开发。目前已有199人学习下载。代码覆盖摄像头初始化配置、像素格式与分辨率设置、数据读取及中断服务等关键环节可帮助读者理解C8051F330与OV7670的硬件交互流程并提供了可编译验证的完整参考实现。对于正在做嵌入式视觉、机器人或安防类项目的开发者是一份值得借鉴的实际驱动源码包。1. 一块 8KB Flash 的 C8051F330怎么接 30 万像素的 OV7670C8051F330 是 Silicon Labs 的 25MIPS 8051Flash 只有 8KB片内 RAM 768 字节拿来跑 OV7670 第一反应是不太够。但真正做过一次会发现OV7670 自己产生 PCLK、VSYNC、HREF单片机要做的只是通过两线 SCCB 配置寄存器再按行把数据从 P1 口取走。问题不在算力而在接口时序和内存布局。这份 Keil 源码工程 c8051f330_camera.uvproj 正是沿着这个思路用 IO 模拟完成摄像头初始化和行读取适合做低成本图像采集、需要把 RGB565 接到串口或屏幕的开发者。下面从 SCCB 开始把整条数据链拆开。2. OV7670 V2.0 的接口与 SCCB 读写先把配置总线打通2.1 模块引脚和 C8051F330 的接线建议OV7670 对外信号可以分成两组一组是配置用的 SCCBSIOC/SIOD另一组是图像数据用的 DVPD0-D7、PCLK、VSYNC、HREF、RESET、PWDN。很多资料把 SCCB 说成 SPI实际它是类似 I2C 的两线协议只有开始、结束、读、写四个状态没有 ACK 应答位。C8051F330 没有摄像头接口所以这两个信号用普通 IO 模拟即可。下面这张表是我常用的接法同时标出了方向后面代码就是按这个表写的信号方向相对单片机建议引脚说明SIOC输出P0.0SCCB 时钟需要推挽输出SIOD双向P0.1开漏输出外部上拉到 3.3VVSYNC输入P0.2帧同步信号HREF输入P0.3行有效信号PCLK输入P0.4像素时钟D0-D7输入P1.0-P1.78 位并行数据RESET 一般接一个普通 IO上电后拉高再拉低完成复位PWDN 直接接地不要悬空。V2.0 模块有两种常见形态带 AL422 FIFO 的和不带 FIFO 的。如果带 FIFO还要控制 WE、RRST、OE 等引脚这里先按不带 FIFO 直读传感器的接法分析这份源码里的 c8051f330_camera.c 走的也是直接读数据线的路线。2.2 SCCB 时序和 IO 模拟SCCB 的时序就是四步开始、发地址、发数据或读数据、结束。写寄存器的格式是“器件地址 寄存器地址 写入值”读寄存器的格式是“器件地址 寄存器地址 重复开始 读地址 读出值”。器件地址是 0x42写/0x43读对应 OV7670 的 7 位地址 0x21。注意不像 I2C传感器不会拉低 SIOD 来表示 ACK主机发完字节后不需要等应答。下面是 C8051F330 上用 IO 模拟的核心驱动代码延时函数用空循环加 NOP 调整SCCB 时钟控制在 400kHz 以内#include c8051F330.h #include intrins.h sbit SCCB_SIC P0^0; sbit SCCB_SID P0^1; static void sccb_delay(void) { _nop_(); _nop_(); _nop_(); _nop_(); } static void sccb_start(void) { SCCB_SID 1; SCCB_SIC 1; sccb_delay(); SCCB_SID 0; sccb_delay(); SCCB_SIC 0; sccb_delay(); } static void sccb_stop(void) { SCCB_SID 0; SCCB_SIC 0; sccb_delay(); SCCB_SIC 1; sccb_delay(); SCCB_SID 1; sccb_delay(); } static void sccb_write_byte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { SCCB_SID (dat 0x80) ? 1 : 0; dat 1; sccb_delay(); SCCB_SIC 1; sccb_delay(); SCCB_SIC 0; sccb_delay(); } } static unsigned char sccb_read_byte(void) { unsigned char i, dat 0; for (i 0; i 8; i) { SCCB_SIC 1; sccb_delay(); dat 1; if (SCCB_SID) dat | 1; sccb_delay(); SCCB_SIC 0; sccb_delay(); } return dat; } void ov7670_write_reg(unsigned char reg, unsigned char val) { sccb_start(); sccb_write_byte(0x42); // 器件写地址 sccb_write_byte(reg); // 寄存器地址 sccb_write_byte(val); // 寄存器值 sccb_stop(); } unsigned char ov7670_read_reg(unsigned char reg) { unsigned char val; sccb_start(); sccb_write_byte(0x42); sccb_write_byte(reg); sccb_start(); // SCCB 读需要重复开始 sccb_write_byte(0x43); // 器件读地址 val sccb_read_byte(); sccb_stop(); return val; }写函数里每个字节之间不需要插入停止位读函数最后返回的 val 就是寄存器值。SIOD 在写周期由单片机驱动读周期切换为传感器驱动所以 P0.1 必须保持开漏并接上拉否则两个输出抢一条线会把电平拉到不确定状态。2.3 先读传感器版本号验证通路拿到驱动后第一步不是去读图像而是读两个 PID 寄存器0x0A 和 0x0B。这两个寄存器出厂一般固定为 0x76 和 0x73。验证代码unsigned char pid ov7670_read_reg(0x0A); unsigned char ver ov7670_read_reg(0x0B); if ((pid 0x76) (ver 0x73)) { // SCCB 通路正常可以继续初始化 } else { // 检查接线、上拉和延时 }读不到 0x76 的时候先量 SIOC 和 SIOD 有没有波形没有示波器就把 sccb_delay 拉长到原来 4 倍看返回值会不会从 0xFF 变成其他数。这里最容易忽略的是 SIOD 的推挽设置如果把这根线配成强推挽读周期传感器拉不动PID 会一直读到 0xFF。3. C8051F330 的交叉开关与 Keil 工程裁剪把数据线让给 P13.1 端口初始化的顺序先关外设再配交叉开关C8051F330 的端口不是普通 8051 的一对一引脚所有数字外设都通过交叉开关映射到 P0/P1。OV7670 的数据线 D0-D7 使用 P1 整口配置的关键是把和摄像头冲突的外设关闭再把 P1 口设置为数字输入最后使能交叉开关。参考初始化段void ports_init(void) { P0MDOUT 0x01; // P0.0(SIOC)推挽输出其余开漏 P1MDOUT 0x00; // P1 全部开漏输入 P1MDIN 0xFF; // P1 全部数字功能 P0SKIP 0x1F; // 跳过 P0.0-P0.4保留给 IO 模拟和帧同步 XBR0 0x00; // 不使能 UART/SPI/SMBus 映射 XBR1 0x40; // 打开交叉开关 }P0.0-P0.4 被 P0SKIP 跳过所以它们继续作为普通 IO。P1MDIN 整字节写 0xFF是防止某些端口默认进入模拟输入模式造成读到的电平不稳定。如果后面还要用 UART 调试XBR0 要改成 0x04 打开 UART 到 P0.4/P0.5同时 P0SKIP 也要调整不能和 HREF、PCLK 重叠。这个“先关外设、再开开关”的顺序经常被忽略。很多人先在调试器里跑起来UART 占着 P0.4摄像头 PCLK 也接到 P0.4结果两个外设抢同一个引脚读到的图像数据全是乱的。我一般每加一个功能就重新对照交叉开关分配表看一遍再改代码。3.2 Keil 工程文件哪些是直接有用的源码包解压后是典型的 Keil C51 工程c8051f330_camera.uvproj 是主工程文件source 目录里只有 c8051f330_camera.c 一个业务文件inc 里有 c8051F330.h 和 english_16x8.h后者多数是字符点阵字库。obj 目录下的 .m51、.lnp、.lst 是编译中间产物发布包里带上它们的好处是可以直接打开工程后按调试不用重新选择芯片型号和内存布局。工程里还有一个 STARTUP.A51它是 Keil C51 的启动文件负责清零内部数据和 XRAM并把栈指针初始化。C8051F330 的 XRAM 是 512 字节加上 256 字节直接寻址区才是总 RAM 768 字节。STARTUP.A51 默认从链接器配置读取 XDATA 大小不需要手工改。但如果你在工程设置里把 MEMORY MODEL 改成 compact就要注意变量分配会进入 P2 寻址反而增加间接访问指令开销。我建议保持 small model摄像头缓冲显式放在 XDATA。3.3 内部振荡器和中断的取舍C8051F330 没有外部晶振也可以跑内部振荡器默认 24.5MHz。对 SCCB 来说这个频率足够但读 PCLK 时需要精准轮询中断插进来反而会让采样抖动。很多新手会把 VSYNC 接外部中断进去后置标志再回主循环读行实际上在 C8051F330 上外部中断引脚也走交叉开关默认位置和 P0.0/P0.1 容易冲突。我更推荐直接查询 VSYNC 电平void ov7670_wait_frame(void) { while (OV_VSYNC); // 等待上一帧结束 while (!OV_VSYNC); // 等待新帧起始 }在 160x120 输出下这个函数的阻塞时间只有几毫秒主循环完全跟得上。如果一定要用中断先查交叉开关寄存器表确认 INT0 最终映射到哪个引脚同时保证中断服务程序里只置标志位不在中断里等待 HREF 或 PCLK否则一个长中断就会丢掉一行数据。4. 初始化寄存器序列与帧读取实战QVGA 和开窗分别怎么取数4.1 RGB565 初始化配置OV7670 上电后默认输出 VGA 尺寸的 YUV422。C8051F330 的 XRAM 只有 512 字节YUV422 每像素 2 字节RGB565 也是每像素 2 字节为什么还要切到 RGB565因为后续如果接 TFT 屏或者串口上位机RGB565 可以直接拼成颜色值省去颜色空间转换代码。一组最关键的初始化寄存器如下寄存器值含义0x12 COM70x04选择 QVGA 输出0x40 COM150xD0RGB565 输出格式0x11 CLKRC0x00内部 PCLK 不分频0x0C COM30x00关闭缩放保持默认时序0x3E COM140x00不启用像素缩放在源码驱动里这些值通常组织成一张初始化表按顺序写入表中最后一个 0xFF 作为结束标志。初始化完成后要等待约 100ms让传感器内部 PLL 和自动曝光稳定再开始读帧。如果手上这块模块的批次比较特殊0x3E 和窗口寄存器可能略有差异。我遇到过一块只能读到花屏的模块最后发现是 COM7 的 QVGA 位没生效实际输出还是 VGA 时序。所以初始化后不要急着信寄存器表读回 0x12 确认 bit2 是否为 1。4.2 帧同步和行读取代码不带 FIFO 的直读方式图像数据是连续字节流在 C8051F330 上轮询 HREF 和 PCLK 逐行读。下面是读一行 RGB565 的核心流程缓冲放在 XDATA#define LINE_W 160 // 开窗后的宽度单位像素 unsigned char xdata line_buf[LINE_W * 2]; // 一行 RGB565 void ov7670_read_line(void) { unsigned int i; ov7670_wait_frame(); for (i 0; i LINE_W * 2; i) { while (!OV_HREF); // 行有效期间读 while (!OV_PCLK); // 等待像素时钟高电平 line_buf[i] OV_DATA; while (OV_PCLK); // 等一个完整 PCLK } }这段代码有两个细节。第一ov7670_wait_frame 的顺序不能反过来否则第一次循环可能卡在上一帧的 VSYNC 高电平里。第二line_buf 只存一行下一行到来后会被覆盖所以读取一行后要立刻把这一行处理掉比如串口发送或者送屏。如果 PCLK 的频率高于单片机连续执行这些 while 循环的速度数据就会错位。解决办法是调大 CLKRC 的分频值或者减少每次读取的像素数。4.3 512 字节 XRAM 下的缓冲设计QVGA 宽度 320一行 RGB565 需要 640 字节C8051F330 的 XRAM 只有 512 字节整个 QVGA 行根本放不下。更稳妥的做法是把窗口开到 160x120只采集视野中央区域这样一行只有 320 字节同时还能避开 OV7670 在窗口边界处的几行抖动脉冲。开窗使用 HSTART、HSTOP、VSTART、VSTOP 四个寄存器。下面这组是 160 像素宽度的参考值// 水平方向显示中央 160 像素 ov7670_write_reg(0x17, 0x16); // HSTART ov7670_write_reg(0x18, 0x74); // HSTOP // 垂直方向显示中央 120 行 ov7670_write_reg(0x19, 0x03); // VSTART ov7670_write_reg(0x1A, 0x7F); // VSTOP这几组值在不同版本的 OV7670 模块上可能需要微调。调整方法是固定 HREF 信号接一个 IO用逻辑分析仪看 HREF 高电平持续了多少个 PCLK没有逻辑分析仪就不断改 HSTOP 的步长观察串口收下来的整行像素是不是中间没有空行。窗口参数不能随意填必须满足传感器内部的每行寄存器上下限乱填会直接白屏。5. 排错白屏、花屏、读不到 ID和一条串口验证链路5.1 症状对照排查现象优先排查点常见原因读 PID 返回 0xFFSIOD 上拉和方向读周期没有释放总线或上拉电阻缺失全部白屏COM7 未生效写入 0x12 失败或初始化表提前被终止画面花屏PCLK 太快CLKRC 分频不够单片机追不上 PCLK图像偏色RGB/YUV 配置错位按 RGB565 读 YUV 数据高低字节颠倒行错位HSTART/HSTOP 边界不对开窗参数越界或读行循环多读少读5.2 数据通路自测在确认 SCCB 能读寄存器以后可以先让摄像头输出纯色来验证 P1 数据线。比如把 COM7 写回 0x80这会关闭大部分自动功能输出变成接近纯色的画面再把读到的行缓冲发出来如果缓冲里不是全 0 也不是全 1说明 P1 口接线和端口配置都是通的。更直接的办法是断开摄像头用杜邦线把 P1.0-P1.7 接到固定电平在调试器里观察 line_buf 是否按预期填入 0xAA这样能同时验证 GPIO 输入模式。5.3 串口验证链路C8051F330 的 UART0 可以在读取一行后立刻把数据发出。波特率用 115200 或更低每次发完一行再发一个 0x55 0xAA 的帧头PC 端按帧头切分。最小发送逻辑void uart_send_line(unsigned char xdata *buf, unsigned int len) { unsigned int i; SBUF0 0x55; // 帧头1 while (!TI0); TI0 0; SBUF0 0xAA; // 帧头2 while (!TI0); TI0 0; for (i 0; i len; i) { SBUF0 buf[i]; while (!TI0); TI0 0; } }PC 侧按帧头解析即可不需要外部库。常见问题是线缆过长时 HREF 上的反射会让某一行多读一个字节解决方法是把 PCLK 采样移到下一个时钟的上升沿或者在发送协议里加长度校验并丢弃首尾各两字节。如果工程里已经启用了 UART 映射记得在 ports_init 里把 XBR0 恢复成包含 UART 的值否则串口和 PCLK 会争抢同一根引脚。读图像的一个真实经验C8051F330 这级别的主控与其追求高帧率不如把分辨率压到 160x120再把 PCLK 分频到约 1MHz 慢慢读这样既绕开内存限制也让花屏问题的定位范围收敛一半以上。本文还有配套的精品资源点击获取
返回列表