STM32F103C8T6与无FIFO功能的OV7670图像采集方案实战解析

目录

硬件选择

一、OV7670核心特性

1. 基本参数

2. 关键优势

二、硬件接口详解

1. 引脚定义与连接

三、SCCB协议基础

1. SCCB与I2C的关系

2. SCCB信号线

四、图像时序基础概念

1. 图像数据的组成

2. 关键时序信号

 五、实操开始

六、实际效果演示


OV7670 是一款由 OmniVision 推出的低成本、低功耗CMOS图像传感器,广泛应用于嵌入式视觉、智能家居、机器人视觉等领域。本文将从 硬件结构核心特性寄存器配置实战开发 进行全面解析,帮助开发者快速上手OV7670。

硬件选择

  • 主控:STM32F103C8T6
  • 摄像头:无FIFO的OV7670
  • 软件:KEIL5,山外多功能助手
  • USB转TTL模块
  • 一、OV7670核心特性

    1. 基本参数

  • 分辨率:支持 VGA(640×480) 到 QQVGA(160×120) 多档分辨率。
  • 输出格式:YUV、RGB565/555、GRB 4:2:2 等格式,默认常用 RGB565
  • 帧率:最高 30fps@VGA,可调。
  • 接口:SCCB(兼容I2C)配置接口,8位并行数据输出。
  • 功耗:工作电流约 20mA@3.3V,低功耗模式支持待机。
  • 2. 关键优势

  • 低成本:适合预算有限的嵌入式项目。
  • 灵活性:寄存器可编程配置,支持多种图像处理功能。
  • 易集成:直接输出数字信号,无需外部ADC。
  • 二、硬件接口详解

    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的关系

  • 协议兼容性:SCCB协议与I2C高度相似,但存在关键差异:
  • 数据有效性:SCCB数据在时钟线(SCL)低电平时变化(I2C在高电平时稳定)。
  • 停止条件:SCCB写操作无需发送停止信号(STOP),而I2C必须发送。
  • 读操作:SCCB读操作需发送两次START信号,I2C只需一次。
  • 典型应用:SCCB专用于OmniVision摄像头模块(如OV7670、OV2640等)。
  • 2. SCCB信号线

  • SIO_C(SCL):串行时钟线,由主控制器(MCU)生成。
  • SIO_D(SDA):串行数据线,支持双向通信。
  • SCCB时序图

  •  图像时序是摄像头传感器输出图像数据的核心机制,决定了图像如何被采集、传输和处理。本文以 OV7670摄像头模块 为例,深入解析其图像时序的工作原理、关键信号及实战应用中的注意事项,帮助开发者掌握图像数据捕获的核心技术。

    四、图像时序基础概念

    1. 图像数据的组成

  • 帧(Frame):一张完整图像的所有像素集合。
  • 行(Line):图像的一行像素。
  • 像素(Pixel):图像的最小单元,包含颜色和亮度信息。
  • 2. 关键时序信号

  • VSYNC(垂直同步信号):标志一帧图像的开始和结束。
  • HREF(行同步信号):标志一行像素的开始和结束。
  • PCLK(像素时钟):控制每个像素数据的传输节奏。
  • 线(D0-D7):传输像素数据(如RGB565格式)。
  • 这个图片是一些关键时序,在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_小杰

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32F103C8T6与无FIFO功能的OV7670图像采集方案实战解析

    发表回复