STM32F103C8T6与无FIFO功能的OV7670图像采集方案实战解析
目录
硬件选择
一、OV7670核心特性
1. 基本参数
2. 关键优势
二、硬件接口详解
1. 引脚定义与连接
三、SCCB协议基础
1. SCCB与I2C的关系
2. SCCB信号线
四、图像时序基础概念
1. 图像数据的组成
2. 关键时序信号
五、实操开始
六、实际效果演示
OV7670 是一款由 OmniVision 推出的低成本、低功耗CMOS图像传感器,广泛应用于嵌入式视觉、智能家居、机器人视觉等领域。本文将从 硬件结构、核心特性、寄存器配置 到 实战开发 进行全面解析,帮助开发者快速上手OV7670。
硬件选择
一、OV7670核心特性
1. 基本参数
2. 关键优势
二、硬件接口详解
1. 引脚定义与连接
引脚名称 | 功能描述 | 典型连接方式 |
---|---|---|
SIO_C | SCCB时钟线 | MCU的I2C_SCL |
SIO_D | SCCB数据线 | MCU的I2C_SDA |
VSYNC | 垂直同步信号 | 外部中断引脚 |
HREF | 行同步信号 | GPIO输入 |
PCLK | 像素时钟输出 | 定时器捕获引脚 |
D0-D7 | 8位数据总线 | 连续GPIO或硬件接口 |
XCLK | 外部时钟输入(12/24MHz) | MCU/PWM输出 |
三、SCCB协议基础
1. SCCB与I2C的关系
2. SCCB信号线
SCCB时序图

图像时序是摄像头传感器输出图像数据的核心机制,决定了图像如何被采集、传输和处理。本文以 OV7670摄像头模块 为例,深入解析其图像时序的工作原理、关键信号及实战应用中的注意事项,帮助开发者掌握图像数据捕获的核心技术。
四、图像时序基础概念
1. 图像数据的组成
2. 关键时序信号
这个图片是一些关键时序,在OV7670的手册中会有相对应的。
五、实操开始
根据上面的了解相信你已经对OV7670有了不错的理解,现在我们就开始实际操作吧。
1. SCCB部分代码
#include "stm32f10x.h"
#include "Delay.h"
#include "sccb.h"
#include "GPIOLIKE51.h"
void SCCB_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10|GPIO_Pin_11;
GPIO_Init(GPIOB,&GPIO_InitStruct);
}
void SCCB_SDA_IN(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE );
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_11;
GPIO_Init(GPIOB,&GPIO_InitStruct);
}
void SCCB_SDA_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE );
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_11;
GPIO_Init(GPIOB,&GPIO_InitStruct );
}
//开始信号
void SCCB_Start(void)
{
SCCB_SDA=1;
SCCB_SCL=1;
delay_us(100);
SCCB_SDA=0;
delay_us(100); //并不是必须为50us,不要太短即可
SCCB_SCL=0;
}
//结束信号
void SCCB_Stop(void)
{
SCCB_SDA=0;
delay_us(100);
SCCB_SCL=1;
delay_us(100);
SCCB_SDA=1;
delay_us(100);
}
//写数据
uint8_t SCCB_WR_Byte(u8 dat)
{
uint8_t j,res;
for(j=0;j<8;j++) //循环发送bit7-bit0
{
if(dat&0x80)
SCCB_SDA=1;
else
SCCB_SDA=0;
dat<<=1;
delay_us(100);
SCCB_SCL=1;
delay_us(100);
SCCB_SCL=0;
}
SCCB_SDA_IN(); //设置SDA为输入
delay_us(100);
SCCB_SCL=1; //将SCL置1,此时如果数据已被从机接收,从机将把SDA置0
delay_us(100);
if(SCCB_READ_SDA)
res=1; //SDA置1,说明从机没有成功接收数据
else
res=0; //发送成功
SCCB_SCL=0;
SCCB_SDA_OUT(); //设置SDA为输出,为下一个相的输出作准备
return res;
}
//写数据到寄存器
uint8_t SCCB_WR_Reg(uint8_t reg,uint8_t data)
{
uint8_t res=0;
SCCB_Start(); //启动传输的标志
if(SCCB_WR_Byte(SCCB_ID))res=1; //写入OV7670传感器ID
delay_us(100);
if(SCCB_WR_Byte(reg))res=1; //写寄存器地址
delay_us(100);
if(SCCB_WR_Byte(data))res=1; //写要向寄存器写入的数据
SCCB_Stop(); //结束传输的标志
return res;
}
//应答信号
void SCCB_No_Ack(void)
{
delay_us(100);
SCCB_SDA=1;
SCCB_SCL=1;
delay_us(100);
SCCB_SCL=0;
delay_us(100);
SCCB_SDA=0;
delay_us(100);
}
//读数据
uint8_t SCCB_RD_Byte(void)
{
uint8_t temp=0,j;
SCCB_SDA_IN(); //设置主机SDA连接的IO口为输入
for(j=8;j>0;j--) //循环读取bit7-bit0
{
delay_us(100);
SCCB_SCL=1;
temp=temp<<1;
if(SCCB_READ_SDA)temp++; //SCCB_READ_SDA是从IO口读到的数据
delay_us(100);
SCCB_SCL=0;
}
SCCB_SDA_OUT(); //将主机连接SDA的IO口设置为输出
return temp;
}
//读寄存器数据
uint8_t SCCB_RD_Reg(u8 reg)
{
uint8_t val=0;
//对应两相写操作
SCCB_Start(); //启动传输
SCCB_WR_Byte(SCCB_ID);
delay_us(100);
SCCB_WR_Byte(reg);
delay_us(100);
SCCB_Stop(); //结束传输
delay_us(100);
//对应两相读操作
SCCB_Start(); //启动传输
SCCB_WR_Byte(SCCB_ID|0X01);
delay_us(100);
val=SCCB_RD_Byte();
SCCB_No_Ack(); //读取完8bit数据后的应答
SCCB_Stop(); //结束传输
return val;
}
这部分代码是OV7670的通信代码,通过这段代码可以配置OV7670的各个寄存器。
2、OV7670的初始化代码
#include "stm32f10x.h"
#include "ov7670.h"
#include "sccb.h"
#include "Delay.h"
#include "mco.h"
#include "usart.h"
#include "stdio.h"
#include "QDTFT_demo.h"
uint8_t OV7670_VS(void)
{
return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_12);
}
uint8_t OV7670_HREF(void)
{
return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_11);
}
uint8_t OV7670_PCLK(void)
{
return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_15);
}
// GPIO 初始化函数
void GPIO_Init_OV7670(void) {
GPIO_InitTypeDef GPIO_InitStructure;
// 启用 GPIOA 和 GPIOB 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
// 配置 GPIOA(数据引脚:PA0-PA7)为浮空输入模式(IN_FLOATING)
GPIO_InitStructure.GPIO_Pin = OV7670_DATA_PIN; // 数据引脚 PA0-PA7
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 设置为浮空输入模式,用于接收数据
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚工作速度为 50 MHz
GPIO_Init(OV7670_DATA_PORT, &GPIO_InitStructure);
// 配置 GPIOA(控制引脚:VSYNC, HREF, PCLK)为上拉输入(IPU)
GPIO_InitStructure.GPIO_Pin = OV7670_HSYNC_PIN | OV7670_VSYNC_PIN | OV7670_PCLK_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置频率为 50 MHz
GPIO_Init(OV7670_DATA_PORT, &GPIO_InitStructure);
}
//设置图像获取窗口大小函数
void OV7670_config_window(unsigned int startx,unsigned int starty,unsigned int width, unsigned int height){
unsigned int endx;
unsigned int endy;// "v*2"必须
unsigned char temp_reg1, temp_reg2;
unsigned char temp=0;
endx=(startx+width);
endy=(starty+height+height);// "v*2"必须
temp_reg1 = SCCB_RD_Reg(0x03);
temp_reg1 &= 0xf0;
temp_reg2 = SCCB_RD_Reg(0x32);
temp_reg2 &= 0xc0;
// Horizontal
temp = temp_reg2|((endx&0x7)<<3)|(startx&0x7);
SCCB_WR_Reg(0x32, temp );
temp = (startx&0x7F8)>>3;
SCCB_WR_Reg(0x17, temp );
temp = (endx&0x7F8)>>3;
SCCB_WR_Reg(0x18, temp );
// Vertical
temp =temp_reg1|((endy&0x3)<<2)|(starty&0x3);
SCCB_WR_Reg(0x03, temp );
temp = starty>>2;
SCCB_WR_Reg(0x19, temp );
temp = endy>>2;
SCCB_WR_Reg(0x1A, temp );
}
//OV7670 传感器寄存器、初始化相关设置
const u8 ov7670_init_reg_tbl[][2]=
{
/*以下为OV7670 QVGA RGB565参数 */
{0x3a, 0x04},//dummy
{0x40, 0xd0},//565
{0x12, 0x14},//QVGA,RGB输出
//输出窗口设置
{0x32, 0x80},//HREF control bit[2:0] HREF start 3 LSB bit[5:3] HSTOP HREF end 3LSB
{0x17, 0x16},//HSTART start high 8-bit MSB
{0x18, 0x04},//5 HSTOP end high 8-bit
{0x19, 0x02},
{0x1a, 0x7b},//0x7a,
{0x03, 0x06},//0x0a,帧竖直方向控制
{0x0c, 0x00},
{0x15, 0x00},//0x00
{0x3e, 0x00},//10
{0x70, 0x3a},
{0x71, 0x35},//0x80 调试彩条
{0x72, 0x11},
{0x73, 0x00},//
{0xa2, 0x02},//15
{0x11, 0x00},//时钟分频设置,0,不分频.
{0x7a, 0x20},
{0x7b, 0x1c},
{0x7c, 0x28},
{0x7d, 0x3c},//20
{0x7e, 0x55},
{0x7f, 0x68},
{0x80, 0x76},
{0x81, 0x80},
{0x82, 0x88},
{0x83, 0x8f},
{0x84, 0x96},
{0x85, 0xa3},
{0x86, 0xaf},
{0x87, 0xc4},//30
{0x88, 0xd7},
{0x89, 0xe8},
{0x13, 0xe0},
{0x00, 0x00},//AGC
{0x10, 0x00},
{0x0d, 0x00},//全窗口, 位[5:4]: 01 半窗口,10 1/4窗口,11 1/4窗口
{0x14, 0x28},//0x38, limit the max gain
{0xa5, 0x05},
{0xab, 0x07},
{0x24, 0x75},//40
{0x25, 0x63},
{0x26, 0xA5},
{0x9f, 0x78},
{0xa0, 0x68},
{0xa1, 0x03},//0x0b,
{0xa6, 0xdf},//0xd8,
{0xa7, 0xdf},//0xd8,
{0xa8, 0xf0},
{0xa9, 0x90},
{0xaa, 0x94},//50
{0x13, 0xe5},
{0x0e, 0x61},
{0x0f, 0x4b},
{0x16, 0x02},
{0x1e, 0x27},//图像输出镜像控制.0x07
{0x21, 0x02},
{0x22, 0x91},
{0x29, 0x07},
{0x33, 0x0b},
{0x35, 0x0b},//60
{0x37, 0x1d},
{0x38, 0x71},
{0x39, 0x2a},
{0x3c, 0x78},
{0x4d, 0x40},
{0x4e, 0x20},
{0x69, 0x00},
{0x6b, 0x60},//PLL*4=48Mhz
{0x74, 0x19},
{0x8d, 0x4f},
{0x8e, 0x00},//70
{0x8f, 0x00},
{0x90, 0x00},
{0x91, 0x00},
{0x92, 0x00},//0x19,//0x66
{0x96, 0x00},
{0x9a, 0x80},
{0xb0, 0x84},
{0xb1, 0x0c},
{0xb2, 0x0e},
{0xb3, 0x82},//80
{0xb8, 0x0a},
{0x43, 0x14},
{0x44, 0xf0},
{0x45, 0x34},
{0x46, 0x58},
{0x47, 0x28},
{0x48, 0x3a},
{0x59, 0x88},
{0x5a, 0x88},
{0x5b, 0x44},//90
{0x5c, 0x67},
{0x5d, 0x49},
{0x5e, 0x0e},
{0x64, 0x04},
{0x65, 0x20},
{0x66, 0x05},
{0x94, 0x04},
{0x95, 0x08},
{0x6c, 0x0a},
{0x6d, 0x55},
{0x4f, 0x80},
{0x50, 0x80},
{0x51, 0x00},
{0x52, 0x22},
{0x53, 0x5e},
{0x54, 0x80},
//{0x54, 0x40},//110
{0x09, 0x03},//驱动能力最大
{0x6e, 0x11},//100
{0x6f, 0x9f},//0x9e for advance AWB
{0x55, 0x00},//亮度
{0x56, 0x40},//对比度 0x40
{0x57, 0x40},//0x40, change according to Jim's request
{0x6a, 0x40},
{0x01, 0x40},
{0x02, 0x40},
{0x13, 0xe7},
{0x15, 0x00},
{0x58, 0x9e},
{0x41, 0x08},
{0x3f, 0x00},
{0x75, 0x05},
{0x76, 0xe1},
{0x4c, 0x00},
{0x77, 0x01},
{0x3d, 0xc2},
{0x4b, 0x09},
{0xc9, 0x60},
{0x41, 0x38},
{0x34, 0x11},
{0x3b, 0x02},
{0xa4, 0x89},
{0x96, 0x00},
{0x97, 0x30},
{0x98, 0x20},
{0x99, 0x30},
{0x9a, 0x84},
{0x9b, 0x29},
{0x9c, 0x03},
{0x9d, 0x4c},
{0x9e, 0x3f},
{0x78, 0x04},
{0x79, 0x01},
{0xc8, 0xf0},
{0x79, 0x0f},
{0xc8, 0x00},
{0x79, 0x10},
{0xc8, 0x7e},
{0x79, 0x0a},
{0xc8, 0x80},
{0x79, 0x0b},
{0xc8, 0x01},
{0x79, 0x0c},
{0xc8, 0x0f},
{0x79, 0x0d},
{0xc8, 0x20},
{0x79, 0x09},
{0xc8, 0x80},
{0x79, 0x02},
{0xc8, 0xc0},
{0x79, 0x03},
{0xc8, 0x40},
{0x79, 0x05},
{0xc8, 0x30},
{0x79, 0x26},
{0x09, 0x00},
};
这部分代码是OV7670的初始化代码,通过这段代码可以进行摄像头的初始化。
3、OV7670的时钟信号代码
//使用PWM给OV7670提供一个24MHz的时钟信号
void TIM1_Config(void) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 使能 TIM1 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置 GPIOA Pin 8 作为 TIM1 CH1
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 定时器时基配置
TIM_TimeBaseStructure.TIM_Period = 3 - 1; // 产生 24MHz 时钟信号 (72MHz/3 = 24MHz)
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
// PWM 配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 1; // 50% 占空比
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
TIM_CtrlPWMOutputs(TIM1, ENABLE); // 使能 PWM 输出
TIM_Cmd(TIM1, ENABLE); // 使能定时器
}
六、实际效果演示
完成以上步骤后就可以使用摄像头来获取图像数据了
下面部分就是效果图
这部分是把图像显示在PC端的山外助手上的
这部分是把图像显示在LCD上,可以看到图像的像素并不高,并不是所设置的320*240,是因为C8T6的SRAM只有20K,而一张完整的320*240的大小是150K远远超过了SRAM的大小,所以程序中所获取的图像大小是128*70的,所以会小许多。
发这篇文章主要是因为目前网上关于使用STM32F103C8T6来获取无FIFO的OV7670的图像的教程太少了,所以分享出来给大家,本程序没有使用到DMA,所以图像的显示是十分慢的,几乎5秒左右才会显示一张新图片,后续会加入DMA,这样子图像的显示方面应该会有大幅度的提升,如果有什么不对的都可以联系我进行修改,毕竟博主也是找了不是的资料才将图像显示出来,也是十分的不容易,如果对你有帮助的话,请给我点个赞吧!!!
紧急通告:由于博主的问题,程序还有些Bug没有搞定,所以在这里给大家说说首先是7670引脚连接问题复位以及PWDN引脚,复位引脚直接给个3.3V的电源就行了,PWDN直接接地这两个在手册上都有说明,然后就是在获取图像函数里,如果不使用串口发送到PC端就需要把串口发送那部分的代码注释掉,不然就会导致图像无法获取成功(这个问题博主也不知道为什么,有知道的也可以告诉博主一声),自此感谢大家的支持。
以下附上程序源码
链接: https://pan.baidu.com/s/1IkRv8weTruOkuHlC5VxIEw?pwd=pkmn 提取码: pkmn
作者:Rep_小杰