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 ); /* ?÷?tμ??· */if( !SCCB_WaitAck() ){SCCB_Stop(); return DISABLE;}SCCB_SendByte((uint8_t)(WriteAddress & 0x00FF)); /* éè??μí?eê?μ??· */ 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 ); /* ?÷?tμ??· */if( !SCCB_WaitAck() ){SCCB_Stop(); return DISABLE;}SCCB_SendByte( ReadAddress ); /* éè??μí?eê?μ??· */ SCCB_WaitAck(); SCCB_Stop(); if(!SCCB_Start()){return DISABLE;}SCCB_SendByte( DEV_ADR + 1 ); /* ?÷?tμ??· */ 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博客