STM32F1之OV7725摄像头-CSDN博客
STM32F1之OV7725摄像头·像素数据输出时序、FIFO 读写时序以及摄像头的驱动原理详解-CSDN博客
目录
1. 硬件设计
1.1 SCCB 控制相关
1.2 VGA 时序相关
1.3 FIFO 相关
1.4 XCLK 信号
2. 代码设计
2.1 SCCB总线软件实现
2.1.1 宏定义
2.1.2 SCCB管脚配置
2.1.3 延时函数
2.1.4 SCCB起始信号
2.1.5 SCCB终止信号
2.1.6 SCCB应答信号
2.1.7 SCCB非应答信号
2.1.8 SCCB等待应答信号
2.1.9 发送数据
2.1.10 SCCB总线返回的数据
2.1.11 SCCB写一个字节数据
2.1.12 SCCB读一串数据
1. 硬件设计
摄像头与 STM32 连接关系中主要分为 SCCB 控制、VGA 时序控制、FIFO 数据读取部分,介绍如下:
1.1 SCCB 控制相关
摄像头中的 SIO_C 和 SIO_D 引脚直接连接到 STM32 普通的 GPIO,它们不具有硬件I2C 的功能,所以在后面的代码中采用模拟 I2C 时序,实际上直接使用硬件 I2C 是完全可以实现 SCCB 协议的,本设计采用模拟 I2C 是芯片资源分配妥协的结果。
1.2 VGA 时序相关
检测 VGA 时序的 HREF、VSYNC 引脚,它们与 STM32 连接的 GPIO 均设置为输入模式,其中 HREF 在本实验中并没有使用,它已经通过摄像头内部的与非门控制了 FIFO 的写使能;VSYNC 与 STM32 连接的 GPIO 引脚会在程序中配置成中断模式,STM32 利用该中断信号获知新的图像是否采集完成,从而控制 FIFO 是否写使能。
1.3 FIFO 相关
与 FIFO 控制相关的 RCLK、RRST、WRST、WEN 及 OE 与 STM32 连接的引脚均直接配置成推挽输出,STM32 根据图像的采集情况利用这些引脚控制 FIFO;读取 FIFO 数据内容使用的数据引脚 DO[0:7]均连接到 STM32 同一个 GPIO 端口连续的高 8 位引脚 PB[8:15],这些引脚使用时均配置成输入,程序设计中直接读取 GPIO 端口的高 8 位状态直接获取一个字节的 FIFO 内容,建议在连接这部分数据信号时,参考本设计采用同一个 GPIO 端口连续的 8 位(高 8 位或低 8 位均可),否则会导致读取数据的程序非常复杂。
1.4 XCLK 信号
本设计中 STM32 的摄像头接口还预留了 PA8 引脚用于与摄像头的 XCLK 连接,STM32的 PA8 可以对外输出时钟信号,所以在使用不带晶振的摄像头时,可以通过该引脚给摄像头提供时钟,秉火摄像头内部已自带晶振,在程序中没有使用 PA8 引脚。
2. 代码设计
2.1 SCCB总线软件实现
2.1.1 宏定义
#ifndef __SCCB_H
#define __SCCB_H#include "stm32f10x.h"/************************** OV7725 连接引脚定义********************************/
#define OV7725_SIO_C_SCK_APBxClock_FUN RCC_APB2PeriphClockCmd
#define OV7725_SIO_C_GPIO_CLK RCC_APB2Periph_GPIOC
#define OV7725_SIO_C_GPIO_PORT GPIOC
#define OV7725_SIO_C_GPIO_PIN GPIO_Pin_6#define OV7725_SIO_D_SCK_APBxClock_FUN RCC_APB2PeriphClockCmd
#define OV7725_SIO_D_GPIO_CLK RCC_APB2Periph_GPIOC
#define OV7725_SIO_D_GPIO_PORT GPIOC
#define OV7725_SIO_D_GPIO_PIN GPIO_Pin_7#define SCL_H GPIO_SetBits(OV7725_SIO_C_GPIO_PORT , OV7725_SIO_C_GPIO_PIN)
#define SCL_L GPIO_ResetBits(OV7725_SIO_C_GPIO_PORT , OV7725_SIO_C_GPIO_PIN) #define SDA_H GPIO_SetBits(OV7725_SIO_D_GPIO_PORT , OV7725_SIO_D_GPIO_PIN)
#define SDA_L GPIO_ResetBits(OV7725_SIO_D_GPIO_PORT , OV7725_SIO_D_GPIO_PIN) #define SCL_read GPIO_ReadInputDataBit(OV7725_SIO_C_GPIO_PORT , OV7725_SIO_C_GPIO_PIN)
#define SDA_read GPIO_ReadInputDataBit(OV7725_SIO_D_GPIO_PORT , OV7725_SIO_D_GPIO_PIN) #define ADDR_OV7725 0x42void SCCB_GPIO_Config(void);
int SCCB_WriteByte( u16 WriteAddress , u8 SendByte);
int SCCB_ReadByte(u8* pBuffer, u16 length, u8 ReadAddress);#endif
2.1.2 SCCB管脚配置
前面我们说过,SCCB的引脚配置类似于IIC的引脚配置,这里我们对SCCB的引脚进行初始化,使用的类似软件IIC的协议,可以不用规定必须是IIC的引脚,进行配置为开漏输出模式:
void SCCB_GPIO_Config(void)
{GPIO_InitTypeDef GPIO_InitStructure; /* SCL(PC6)、SDA(PC7)管脚配置 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE );GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_Init(GPIOC, &GPIO_InitStructure);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE );GPIO_InitStructure.GPIO_Pin =GPIO_Pin_7;GPIO_Init(GPIOC, &GPIO_InitStructure);
}
按照宏定义进行转换:
void SCCB_GPIO_Config(void)
{GPIO_InitTypeDef GPIO_InitStructure; /* SCL(PC6)、SDA(PC7)管脚配置 */OV7725_SIO_C_SCK_APBxClock_FUN ( OV7725_SIO_C_GPIO_CLK, ENABLE );GPIO_InitStructure.GPIO_Pin = OV7725_SIO_C_GPIO_PIN ;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_Init(OV7725_SIO_C_GPIO_PORT, &GPIO_InitStructure);OV7725_SIO_D_SCK_APBxClock_FUN ( OV7725_SIO_D_GPIO_CLK, ENABLE );GPIO_InitStructure.GPIO_Pin = OV7725_SIO_D_GPIO_PIN ;GPIO_Init(OV7725_SIO_D_GPIO_PORT, &GPIO_InitStructure);}
2.1.3 延时函数
一个简单的循环等于0时跳出循环,起到延时的作用:
static void SCCB_delay(void)
{ uint16_t i = 400; while(i) { i--; }
}
2.1.4 SCCB起始信号
类比IIC协议,起始条件下,SCL高电平期间,SDA从高电平切换到低电平。
static int SCCB_Start(void)
{GPIO_SetBits(GPIOC, GPIO_Pin_7);SCCB_delay();GPIO_SetBits(GPIOC, GPIO_Pin_6);SCCB_delay();GPIO_ResetBits(GPIOC, GPIO_Pin_7);SCCB_delay();GPIO_ResetBits(GPIOC, GPIO_Pin_6);SCCB_delay();}
为了提高程序的健壮性,可以加入:GPIO_ReadInputDataBit 函数,其作用是读取指定GPIO端口的指定引脚的输入状态,并返回该引脚的输入值(0 或 1) ,进行检测SDA线是否忙碌是否正常。
static int SCCB_Start(void)
{GPIO_SetBits(GPIOC, GPIO_Pin_7);GPIO_SetBits(GPIOC, GPIO_Pin_6);SCCB_delay();if(!GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_7))return DISABLE; /* SDA线为低电平则总线忙,退出 */GPIO_ResetBits(GPIOC, GPIO_Pin_7);SCCB_delay();if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_7)) return DISABLE; /* SDA线为高电平则总线出错,退出 */GPIO_ResetBits(GPIOC, GPIO_Pin_7);SCCB_delay();return ENABLE;
}
按照宏定义进行转换:
static int SCCB_Start(void)
{SDA_H;SCL_H;SCCB_delay();if(!SDA_read)return DISABLE; /* SDA线为低电平则总线忙,退出 */SDA_L;SCCB_delay();if(SDA_read) return DISABLE; /* SDA线为高电平则总线出错,退出 */SDA_L;SCCB_delay();return ENABLE;
}
2.1.5 SCCB终止信号
终止条件下,SCL高电平期间,SDA从低电平切换到高电平。
static void SCCB_Stop(void)
{GPIO_ResetBits(GPIOC, GPIO_Pin_6);SCCB_delay();GPIO_ResetBits(GPIOC, GPIO_Pin_7);SCCB_delay();GPIO_SetBits(GPIOC, GPIO_Pin_6);SCCB_delay();GPIO_SetBits(GPIOC, GPIO_Pin_7);SCCB_delay();
}
按照宏定义进行转换:
static void SCCB_Stop(void)
{SCL_L;SCCB_delay();SDA_L;SCCB_delay();SCL_H;SCCB_delay();SDA_H;SCCB_delay();
}
2.1.6 SCCB应答信号
与 I2C 时序类似,在 SCCB 时序也使用自由位(Don’t care bit )和非应答(NA)信号来保证正常通讯。自由位和非应答信号位于 SCCB 每个传输阶段中的第九位。
在写数据的第一个传输阶段中,第 9 位为自由位,在一般的正常通讯中,第 9 位时,主机的 SDA 线输出高电平,而从机把 SDA 线拉低作为响应,只是传输的内容分别为目的寄存器地址和要写入的数据。
应答信号的发送是 SCCB 协议中的一个重要步骤,用于确认数据传输的成功。
static void SCCB_Ack(void)
{GPIO_ResetBits(GPIOC, GPIO_Pin_6);SCCB_delay();GPIO_ResetBits(GPIOC, GPIO_Pin_7);SCCB_delay();GPIO_SetBits(GPIOC, GPIO_Pin_6);SCCB_delay();GPIO_ResetBits(GPIOC, GPIO_Pin_6);SCCB_delay();
}
按照宏定义进行转换:
static void SCCB_Ack(void)
{ SCL_L;SCCB_delay();SDA_L;SCCB_delay();SCL_H;SCCB_delay();SCL_L;SCCB_delay();
}
2.1.7 SCCB非应答信号
非应答信号的发送也是 SCCB 协议中的一部分,用于处理数据传输失败或其他错误情况。在某些情况下,如果从设备无法正确接收数据,主设备可能会发送非应答信号并采取相应的错误处理措施。
static void SCCB_NoAck(void)
{GPIO_ResetBits(GPIOC, GPIO_Pin_6);SCCB_delay();GPIO_SetBits(GPIOC, GPIO_Pin_7);SCCB_delay();GPIO_SetBits(GPIOC, GPIO_Pin_6);SCCB_delay();GPIO_ResetBits(GPIOC, GPIO_Pin_6);SCCB_delay();
}
按照宏定义进行转换:
static void SCCB_NoAck(void)
{ SCL_L;SCCB_delay();SDA_H;SCCB_delay();SCL_H;SCCB_delay();SCL_L;SCCB_delay();
}
2.1.8 SCCB等待应答信号
让主机把 SDA 线设为高电平,延时一段时间后再检测 SDA 线的电平,若为低则返回 ENABLE 表示接收到从机的应答,反之返回 DISABLE。(L代表低,H代表高,以下直接使用SCL和SDA的宏定义表示)
static int SCCB_WaitAck(void)
{SCL_L;SCCB_delay();SDA_H; SCCB_delay();SCL_H;SCCB_delay();if(SDA_read){SCL_L;return DISABLE;}SCL_L;return ENABLE;
}
2.1.9 发送数据
首先,我们先创建一个用于接收数据的参数SendByt,它接受一个 8 位的字节数据作为输入参数,使用一个循环逐位发送这个字节的数据。循环从最高位开始,依次发送每一位,直到最低位。在每一次循环中,首先将 SCL(时钟线)拉低,然后通过 SCCB_delay 函数产生一定延迟,接着,根据 SendByte 的当前最高位,控制 SDA(数据线)为高电平或低电平,以此来发送数据的当前位,然后将 SendByte 左移一位,准备发送下一位数据。通过循环8次来完成一位数据的发送。
static void SCCB_SendByte(uint8_t SendByte)
{uint8_t i=8;while(i--){SCL_L;SCCB_delay();if(SendByte&0x80)SDA_H; else SDA_L; SendByte<<=1;SCCB_delay();SCL_H;SCCB_delay();}SCL_L;
}
2.1.10 SCCB总线返回的数据
首先声明了一个变量 i 并初始化为 8,以及再声明了一个变量 ReceiveByte 并初始化为 0,用于存储接收到的字节数据,然后,将 SDA(数据线)拉高,准备接收数据,通过一个循环逐位接收一个字节的数据。循环从最高位开始,依次接收每一位,直到最低位,读取 SDA 线上的数据,并根据其值决定是否将 ReceiveByte 的当前最低位设置为 1,将 ReceiveByte 左移一位,准备接收下一位数据。
static int SCCB_ReceiveByte(void)
{ uint8_t i=8;uint8_t ReceiveByte=0;SDA_H; while(i--){ReceiveByte<<=1; SCL_L;SCCB_delay();SCL_H;SCCB_delay(); if(SDA_read){ReceiveByte|=0x01;}}SCL_L;return ReceiveByte;
}
2.1.11 SCCB写一个字节数据
首先,明确两个宏,其实OV7725的设备地址:
#define ADDR_OV7725 0x42
#define DEV_ADR ADDR_OV7725
为了确保SCCB总线通信正常启动,我们可以先进行判断SCCB是否发送起始信号,若是发送继续,若是失败则返回DISABLE。
if(!SCCB_Start()){return DISABLE;}
然后,通过 SCCB_SendByte 函数发送设备地址 DEV_ADR,用于指定要通信的设备,此时指定要通信的设备会发送应答,通过判断本机时候接收到应答,来确定两个设备间是否能进行正常通信,若是不能则终止信号,并返回DISABLE。
SCCB_SendByte( DEV_ADR ); if( !SCCB_WaitAck() ){SCCB_Stop(); return DISABLE;}
若是能则将要写入的寄存器地址的低八位(WriteAddress 的低八位)发送给设备,以确定写入数据的目标寄存器,再次等待指定要通信的设备的应答,然后发送要写入的数据 SendByte 给设备,再次等待设备的应答,,停止 SCCB 通信,并返回 ENABLE 表示写入操作成功。
SCCB_SendByte((uint8_t)(WriteAddress & 0x00FF)); SCCB_SendByte(SendByte);SCCB_WaitAck(); SCCB_Stop(); return ENABLE;
完整代码:
#define ADDR_OV7725 0x42
#define DEV_ADR ADDR_OV7725int SCCB_WriteByte( uint16_t WriteAddress , uint8_t SendByte )
{ if(!SCCB_Start()){return DISABLE;}SCCB_SendByte( DEV_ADR ); /* Æ÷¼þµØÖ· */if( !SCCB_WaitAck() ){SCCB_Stop(); return DISABLE;}SCCB_SendByte((uint8_t)(WriteAddress & 0x00FF)); /* ÉèÖõÍÆðʼµØÖ· */ SCCB_WaitAck(); SCCB_SendByte(SendByte);SCCB_WaitAck(); SCCB_Stop(); return ENABLE;
}
2.1.12 SCCB读一串数据
int SCCB_ReadByte(uint8_t* pBuffer, uint16_t length, uint8_t ReadAddress)
{ if(!SCCB_Start()){return DISABLE;}SCCB_SendByte( DEV_ADR ); /* Æ÷¼þµØÖ· */if( !SCCB_WaitAck() ){SCCB_Stop(); return DISABLE;}SCCB_SendByte( ReadAddress ); /* ÉèÖõÍÆðʼµØÖ· */ SCCB_WaitAck(); SCCB_Stop(); if(!SCCB_Start()){return DISABLE;}SCCB_SendByte( DEV_ADR + 1 ); /* Æ÷¼þµØÖ· */ if(!SCCB_WaitAck()){SCCB_Stop(); return DISABLE;}while(length){*pBuffer = SCCB_ReceiveByte();if(length == 1){SCCB_NoAck();}else{SCCB_Ack(); }pBuffer++;length--;}SCCB_Stop();return ENABLE;
}
OV7725摄像头_时光の尘的博客-CSDN博客