其实在很久以前我就一直想搞一下摄像头的移植。当时就在淘宝上买了一个没FIFO,OV7670的模块其实当时自己连什么是FIFO都不知道。就看他便宜然后就买了。结果买回来根本不会用而且没有提供驱动。虽然好像正点原子写了一个驱动但是和F1的接口略有不同,以我当时的水平又不能理解。捣鼓了一段时间后就放弃了,当时在论坛里到处找代码也没找到,虽然有人实现了但也没有给源码。之后一会忙考试,一会有要学点东西就把这个东西给忘了。正好自己好久都没有写驱动代码了,这次正好来练练手。其实本来是准备用STM32F4来实现的F4因为外扩了SDRAM,同时可以提供较高的时钟,可以获得比较好的刷屏效果。但是因为涉及到DCMI接口,所以以后再说吧。
之前看OV7670的接口发现正点原子的接口对不上买的骑飞电子的模块,标记部分不一样,其实本质上还是一样的。先来看一下他们的照片。
从接口上来看区别就在于SCCB口的描述不一样,行同步信号命名不一样,时钟脚不一样。
总结起来就是
SIOC =SCL SIOD=SDA WRST=RESET
其实本质上摄像头是DCMI接口,而F1没有所以驱动会有很大的变化。
所以说有没有FIFO在接口上还是区别很大的。虽然不知道为什么同样是OV7670,是否使用FIFO在接口上就产生了 这么大的区别。但是总的来说接口还是可以分为三个部分。
SCCB
时钟输入和输出
并行数据接口
首先当然先写SCCB接口,这个与I2C区别不大。
SCCB.c
SCCB接口底层实现
#include "sys.h"
#include "sccb.h"
#include "delay.h"
//初始化SCCB接口
//CHECK OK
void SCCB_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOG, ENABLE); //使能PB端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; // 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOG, &GPIO_InitStructure);
GPIO_SetBits(GPIOG,GPIO_Pin_13); // 输出高
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; // 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //输输出
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_SetBits(GPIOD,GPIO_Pin_3); // 输出高
SCCB_SDA_OUT();
}
//SCCB起始信号
//当时钟为高的时候,数据线的高到低,为SCCB起始信号
//在激活状态下,SDA和SCL均为低电平
void SCCB_Start(void)
{
SCCB_SDA=1; //数据线高电平
SCCB_SCL=1; //在时钟线高的时候数据线由高至低
delay_us(50);
SCCB_SDA=0;
delay_us(50);
SCCB_SCL=0; //数据线恢复低电平,单操作函数必要
}
//SCCB停止信号
//当时钟为高的时候,数据线的低到高,为SCCB停止信号
//空闲状况下,SDA,SCL均为高电平
void SCCB_Stop(void)
{
SCCB_SDA=0;
delay_us(50);
SCCB_SCL=1;
delay_us(50);
SCCB_SDA=1;
delay_us(50);
}
//产生NA信号
void SCCB_No_Ack(void)
{
delay_us(50);
SCCB_SDA=1;
SCCB_SCL=1;
delay_us(50);
SCCB_SCL=0;
delay_us(50);
SCCB_SDA=0;
delay_us(50);
}
//SCCB,写入一个字节
//返回值:0,成功;1,失败.
u8 SCCB_WR_Byte(u8 dat)
{
u8 j,res;
for(j=0;j<8;j++) //循环8次发送数据
{
if(dat&0x80)SCCB_SDA=1;
else SCCB_SDA=0;
dat<<=1;
delay_us(50);
SCCB_SCL=1;
delay_us(50);
SCCB_SCL=0;
}
SCCB_SDA_IN(); //设置SDA为输入
delay_us(50);
SCCB_SCL=1; //接收第九位,以判断是否发送成功
delay_us(50);
if(SCCB_READ_SDA)res=1; //SDA=1发送失败,返回1
else res=0; //SDA=0发送成功,返回0
SCCB_SCL=0;
SCCB_SDA_OUT(); //设置SDA为输出
return res;
}
//SCCB 读取一个字节
//在SCL的上升沿,数据锁存
//返回值:读到的数据
u8 SCCB_RD_Byte(void)
{
u8 temp=0,j;
SCCB_SDA_IN(); //设置SDA为输入
for(j=8;j>0;j--) //循环8次接收数据
{
delay_us(50);
SCCB_SCL=1;
temp=temp<<1;
if(SCCB_READ_SDA)temp++;
delay_us(50);
SCCB_SCL=0;
}
SCCB_SDA_OUT(); //设置SDA为输出
return temp;
}
//写寄存器
//返回值:0,成功;1,失败.
u8 SCCB_WR_Reg(u8 reg,u8 data)
{
u8 res=0;
SCCB_Start(); //启动SCCB传输
if(SCCB_WR_Byte(SCCB_ID))res=1; //写器件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;
}
//读寄存器
//返回值:读到的寄存器值
u8 SCCB_RD_Reg(u8 reg)
{
u8 val=0;
SCCB_Start(); //启动SCCB传输
SCCB_WR_Byte(SCCB_ID); //写器件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();
SCCB_Stop();
return val;
}
SCCB.h
#ifndef __SCCB_H
#define __SCCB_H
#include "sys.h"
#define SCCB_SDA_IN() {GPIOG->CRH&=0XFF0FFFFF;GPIOG->CRH|=0X00800000;}
#define SCCB_SDA_OUT() {GPIOG->CRH&=0XFF0FFFFF;GPIOG->CRH|=0X00300000;}
//IO操作函数
#define SCCB_SCL PDout(3) //SCL
#define SCCB_SDA PGout(13) //SDA
#define SCCB_READ_SDA PGin(13) //输入SDA
#define SCCB_ID 0X42 //OV7670的ID
///////////////////////////////////////////
void SCCB_Init(void);
void SCCB_Start(void);
void SCCB_Stop(void);
void SCCB_No_Ack(void);
u8 SCCB_WR_Byte(u8 dat);
u8 SCCB_RD_Byte(void);
u8 SCCB_WR_Reg(u8 reg,u8 data);
u8 SCCB_RD_Reg(u8 reg);
#endif
注意:实现了软件SCCB的底层后就是模块的初始化了。
所有接口处理都要特别小心。
OV7670.c
摄像头驱动
#include "sys.h"
#include "ov7670.h"
#include "ov7670cfg.h"
#include "delay.h"
#include "usart.h"
#include "sccb.h"
#include "lcd.h"
//VSYNC PG15 (帧同步信号:out)
//HREF PD6 (行同步信号:out)
//XCLK PA8 (时钟信号:in)
//PCLK PB4 (像素时钟:out)
//PWDN PB3 (功耗选择模式 0工作 1POWER DOWN)正常使用拉低,该引脚与JTDO连接,需先关闭
//RESET PG14 (复位端:0RESET 1一般模式 )正常使用拉高
//SIOC PD3
//SIOD PG13
//初始化OV7670
//返回0:成功
//返回其他值:错误代码
u8 OV7670_Init(void)
{
u8 temp;
u16 i=0;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOG, ENABLE); //使能相关端口时钟
RCC->APB2ENR|=1<<0; //开启AF时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; //PG15 输入 上拉 VSYNC
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOG, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; //PB4 端口配置 PCLK
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空上拉输入
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_4);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //PD6 HREF
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_SetBits(GPIOD,GPIO_Pin_6);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //PB3 端口配置 PWDN 正常使用拉低
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_ResetBits(GPIOB,GPIO_Pin_3);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; //PG14 RESET 正常使用拉高
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOG, &GPIO_InitStructure);
GPIO_SetBits(GPIOG,GPIO_Pin_14);
GPIO_InitStructure.GPIO_Pin = 0xff; //PC0~7 输入 上拉 D0~8
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //GPIO_Mode_IPU;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); //SWD 关闭JTAG
CLK_init_ON();
SCCB_Init(); //初始化SCCB 的IO口
if(SCCB_WR_Reg(0x12,0x80))return 1; //复位SCCB
delay_ms(50);
temp=SCCB_RD_Reg(0x12);
//读取产品型号
temp=SCCB_RD_Reg(0x0b);
if(temp!=0x73)return 2;
temp=SCCB_RD_Reg(0x0a);
if(temp!=0x76)return 2;
//初始化序列 对OV7670寄存器进行操作
for(i=0;i<sizeof(ov7670_init_reg_tbl)/sizeof(ov7670_init_reg_tbl[0])/2;i++)
{
SCCB_WR_Reg(ov7670_init_reg_tbl[i][0],ov7670_init_reg_tbl[i][1]);
delay_ms(2);
}
return 0x00; //ok
}
void CLK_init_ON(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP ;
GPIO_Init(GPIOA, &GPIO_InitStructure); //HSE是高速外部时钟,频率范围为4MHz~16MHz 实测出来为8MHz
RCC_MCOConfig(RCC_MCO_HSE );//hsi Selects the clock source to output on MCO pin. RCC_MCO_HSE: HSE oscillator clock selected
}
/*void CLK_init_OFF(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}*/
//设置图像输出窗口
//对QVGA设置。
void OV7670_Window_Set(u16 sx,u16 sy,u16 width,u16 height)
{
u16 endx;
u16 endy;
u8 temp;
endx=sx+width*2; //V*2
endy=sy+height*2;
if(endy>784)endy-=784;
temp=SCCB_RD_Reg(0X03); //读取Vref之前的值
temp&=0XF0;
temp|=((endx&0X03)<<2)|(sx&0X03);
SCCB_WR_Reg(0X03,temp); //设置Vref的start和end的最低2位
SCCB_WR_Reg(0X19,sx>>2); //设置Vref的start高8位
SCCB_WR_Reg(0X1A,endx>>2); //设置Vref的end的高8位
temp=SCCB_RD_Reg(0X32); //读取Href之前的值
temp&=0XC0;
temp|=((endy&0X07)<<3)|(sy&0X07);
SCCB_WR_Reg(0X17,sy>>3); //设置Href的start高8位
SCCB_WR_Reg(0X18,endy>>3); //设置Href的end的高8位
}
////////////////////////////////////////////////////////////////////////////
//OV7670功能设置
//白平衡设置
//0:自动
//1:太阳sunny
//2,阴天cloudy
//3,办公室office
//4,家里home
void OV7670_Light_Mode(u8 mode)
{
u8 reg13val=0XE7;//默认就是设置为自动白平衡
u8 reg01val=0;
u8 reg02val=0;
switch(mode)
{
case 1://sunny
reg13val=0XE5;
reg01val=0X5A;
reg02val=0X5C;
break;
case 2://cloudy
reg13val=0XE5;
reg01val=0X58;
reg02val=0X60;
break;
case 3://office
reg13val=0XE5;
reg01val=0X84;
reg02val=0X4c;
break;
case 4://home
reg13val=0XE5;
reg01val=0X96;
reg02val=0X40;
break;
}
SCCB_WR_Reg(0X13,reg13val);//COM8设置
SCCB_WR_Reg(0X01,reg01val);//AWB蓝色通道增益
SCCB_WR_Reg(0X02,reg02val);//AWB红色通道增益
}
//色度设置
//0:-2
//1:-1
//2,0
//3,1
//4,2
void OV7670_Color_Saturation(u8 sat)
{
u8 reg4f5054val=0X80;//默认就是sat=2,即不调节色度的设置
u8 reg52val=0X22;
u8 reg53val=0X5E;
switch(sat)
{
case 0://-2
reg4f5054val=0X40;
reg52val=0X11;
reg53val=0X2F;
break;
case 1://-1
reg4f5054val=0X66;
reg52val=0X1B;
reg53val=0X4B;
break;
case 3://1
reg4f5054val=0X99;
reg52val=0X28;
reg53val=0X71;
break;
case 4://2
reg4f5054val=0XC0;
reg52val=0X33;
reg53val=0X8D;
break;
}
SCCB_WR_Reg(0X4F,reg4f5054val); //色彩矩阵系数1
SCCB_WR_Reg(0X50,reg4f5054val); //色彩矩阵系数2
SCCB_WR_Reg(0X51,0X00); //色彩矩阵系数3
SCCB_WR_Reg(0X52,reg52val); //色彩矩阵系数4
SCCB_WR_Reg(0X53,reg53val); //色彩矩阵系数5
SCCB_WR_Reg(0X54,reg4f5054val); //色彩矩阵系数6
SCCB_WR_Reg(0X58,0X9E); //MTXS
}
//亮度设置
//0:-2
//1:-1
//2,0
//3,1
//4,2
void OV7670_Brightness(u8 bright)
{
u8 reg55val=0X00;//默认就是bright=2
switch(bright)
{
case 0://-2
reg55val=0XB0;
break;
case 1://-1
reg55val=0X98;
break;
case 3://1
reg55val=0X18;
break;
case 4://2
reg55val=0X30;
break;
}
SCCB_WR_Reg(0X55,reg55val); //亮度调节
}
//对比度设置
//0:-2
//1:-1
//2,0
//3,1
//4,2
void OV7670_Contrast(u8 contrast)
{
u8 reg56val=0X40;//默认就是contrast=2
switch(contrast)
{
case 0://-2
reg56val=0X30;
break;
case 1://-1
reg56val=0X38;
break;
case 3://1
reg56val=0X50;
break;
case 4://2
reg56val=0X60;
break;
}
SCCB_WR_Reg(0X56,reg56val); //对比度调节
}
//特效设置
//0:普通模式
//1,负片
//2,黑白
//3,偏红色
//4,偏绿色
//5,偏蓝色
//6,复古
void OV7670_Special_Effects(u8 eft)
{
u8 reg3aval=0X04;//默认为普通模式
u8 reg67val=0XC0;
u8 reg68val=0X80;
switch(eft)
{
case 1://负片
reg3aval=0X24;
reg67val=0X80;
reg68val=0X80;
break;
case 2://黑白
reg3aval=0X14;
reg67val=0X80;
reg68val=0X80;
break;
case 3://偏红色
reg3aval=0X14;
reg67val=0Xc0;
reg68val=0X80;
break;
case 4://偏绿色
reg3aval=0X14;
reg67val=0X40;
reg68val=0X40;
break;
case 5://偏蓝色
reg3aval=0X14;
reg67val=0X80;
reg68val=0XC0;
break;
case 6://复古
reg3aval=0X14;
reg67val=0XA0;
reg68val=0X40;
break;
}
SCCB_WR_Reg(0X3A,reg3aval);//TSLB设置
SCCB_WR_Reg(0X68,reg67val);//MANU,手动U值
SCCB_WR_Reg(0X67,reg68val);//MANV,手动V值
}
/*
//更新LCD显示
void camera_refresh(void)
{
u32 i,j;
u16 color;
LCD_Scan_Dir(U2D_L2R); //从上到下,从左到右
LCD_SetCursor(0x00,0x0000); //设置光标位置
LCD_WriteRAM_Prepare(); //开始写入GRAM
while(OV7670_VSYNC==0);//0-1
while(OV7670_VSYNC==1);//1-0 只有在VSYNC为低时,才传输数据
//240*320=76800 又每个像素用RGB565表示,即每个像素占用两个字节(16位数据) I2C一次传输8位数据 故一个像素要传2次
//将读取到的数据按RGB565的处理,保存到一个16位(color)变量中
for(i=0;i<240;i++)
{
while(OV7670_HREF==0);//0-1 只有在HREF为高时,才传输数据
for(j=0;j<320;j++)
{
//读取高8位
while(OV7670_PCLK==0); //0-1 数据在PCLK上升沿保持稳定,在此时读取数据
color=GPIOC->IDR&0XFF; //读数据
while(OV7670_PCLK==1); //1-0
//左移高八位
color<<=8;
//读取低8位
while(OV7670_PCLK==0); //0-1
color|=GPIOC->IDR&0XFF; //读数据
while(OV7670_PCLK==1); //1-0
//显示LCD
LCD->LCD_RAM=color;
}
while(OV7670_HREF==1); //1-0
}
LCD_Scan_Dir(DFT_SCAN_DIR); //恢复默认扫描方向
}
*/
}
*/
OV7670.h
#ifndef _OV7670_H
#define _OV7670_H
#include "sys.h"
#include "sccb.h"
//VSYNC PG15 (帧同步信号:out)
//HREF PD6 (行同步信号:out)
//XCLK PA8 (时钟信号:in)
//PCLK PB4 (像素时钟:out)
//PWDN PB3 (功耗选择模式 0工作 1POWER DOWN)正常使用拉低
//RESET PG14 (复位端:0RESET 1一般模式 )正常使用拉高
#define OV7670_VSYNC PGin(15)
#define OV7670_HREF PDin(6)
#define OV7670_XCLK PAin(8)
#define OV7670_PCLK PBin(4)
#define OV7670_PWDN PBin(3)
#define OV7670_RESET PGin(14)
#define OV7670_DATA GPIO_ReadInputData(GPIOC,0x00FF) //数据输入端口
//GPIOC->IDR&0x00FF
/////////////////////////////////////////
#define CHANGE_REG_NUM 171 //需要配置的寄存器总数
extern const u8 ov7670_init_reg_tbl[CHANGE_REG_NUM][2]; //寄存器及其配置表
u8 OV7670_Init(void);
void OV7670_Window_Set(u16 sx,u16 sy,u16 width,u16 height);
void CLK_init_ON(void);
void CLK_init_OFF(void);
void timer_init(void);
#endif
OV7670cfg.h
//初始化寄存器序列及其对应的值
const u8 ov7670_init_reg_tbl[][2]=
{
//以下为OV7670 QVGA RGB565参数
{0x3a, 0x04}, //
{0x40, 0x10}, //设置数据位RGB565 00-FF
//{0x40, 0xd0},
{0x12, 0x14},//QVGA,RGB输出******************************************
//输出窗口设置
{0x32, 0x80},
{0x17, 0x16},
{0x18, 0x04},//5
{0x19, 0x02},
{0x1a, 0x7a},//0x7a,
{0x03, 0x0a},//0x0a,
{0x0c, 0x0c},
{0x15, 0x00},
{0x3e, 0x00},//10 pclk不分频
{0x70, 0x00},
{0x71, 0x01},
{0x72, 0x11},
{0x73, 0x09},//
{0xa2, 0x02},//15
{0x11, 0x04},//内部时钟分频设置,0,不分频.*******************************************************04
{0x7a, 0x20}, //@0x12
{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},//@(0xe4)使能AGC
{0x00, 0x00},//AGC
{0x10, 0x00},
{0x0d, 0x00},
{0x14, 0x20},//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, 0x17},//图像输出镜像控制.0x07, 0x37水平镜像+竖直翻转 0x27
{0x21, 0x02},
{0x22, 0x91},
{0x29, 0x07},
{0x33, 0x0b},
{0x35, 0x0b},//60
{0x37, 0x1d},
{0x38, 0x71},
{0x39, 0x2a},
{0x3c, 0x78},
{0x4d, 0x40},
{0x4e, 0x20},
{0x69, 0x5d},
{0x6b, 0x40},//PLL*4=48Mhz 40
{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},//对比度
{0x57, 0x80},//0x40, change according to Jim's request
};
//初始化寄存器序列及其对应的值
const u8 ov7670_init_reg_tb2[][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},
{0x72, 0x11},
{0x73, 0x00},//
{0xa2, 0x02},//15
{0x11, 0x81},//时钟分频设置,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, 0x40},//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
///////////////////////////////////////////////////////////////////////
//以下部分代码由开源电子网网友:duanzhang512 提出
//添加此部分代码将可以获得更好的成像效果,但是最下面一行会有蓝色的抖动.
//如不想要,可以屏蔽此部分代码.然后将:OV7670_Window_Set(12,176,240,320);
//改为:OV7670_Window_Set(12,174,240,320);,即可去掉最下一行的蓝色抖动
// {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},
///////////////////////////////////////////////////////////////////////
};
#endif
这样底层其实就差不多了。
然后再加上一些图像处理函数
#include "PicHandle.h"
#include "lcd.h"
//二值化阈值
#define threshold 32767
int MaxRed=0; //关于红色的HSV加权最大值
int Red_X,Red_Y; //记录最红最亮的点的坐标值
float PixelNums; //最红最亮的点与中心点的像素点数
float Final_Dis; //记录距离
//float abs(float x)
//{
// if(x<0) x=0-x;
// return x;
//}
//
//float sin(float x)
//{
// const float B = 1.2732395447;
// const float C = -0.4052847346;
// const float P = 0.2310792853; //0.225;
// float y = B * x + C * x * abs(x);
// y = P * (y * abs(y) - y) + y;
// return y;
//}
////
//float cos(float x)
//{
// const float Q = 1.5707963268;
// const float PI =3.1415926536;
// x += Q;
// if(x > PI)
// x -= 2 * PI;
// return( sin(x));
//}
////
//float tan(float y) //input radian
//{
// float tan1;
// tan1=sin(y)/cos(y);
// return tan1;
//}
/***********************************************
*name: SqrtByNewton(float x)
*parameter: float x
*function: square root
*return: the result of square root
*reserved:
***********************************************/
float SqrtByNewton(float x)
{
int temp=0x1fc00000+((*(int *)&x)>>1);
float val=*(float*)&temp;
val =(val + x/val) / 2;
val =(val + x/val) / 2;
val =(val + x/val) / 2;
return val;
}
/***********************************************
*name: int RGB2HSV(int R,int G,int B)
*parameter: int R,int G,int B
*function: convert RGB to HSV
*return: 返回关于HSV对于红色的一个加权
*reserved:
***********************************************/
int RGB2HSV(int R,int G,int B) //R'G'B变量类型可改。
{
float maxRGB,minRGB,des;
float H,S,V;
int Hi,Si,Vi;
int RedHSV;
maxRGB=((R>=G)?R:G); //the maximum
maxRGB=((maxRGB>=B)?maxRGB:B);
minRGB=((R<=G)?R:G); //the minimum
minRGB=((minRGB<=B)?minRGB:B);
des=maxRGB-minRGB;
V=maxRGB; //the value of V
Vi=(int)(V*366/255); //force to int
if(maxRGB==0) //the value of S
S=0;
else
S=1-minRGB/maxRGB;
Si=(int)(S*360); //force to int
if(maxRGB==minRGB) //the value of H
H=0;
else if((maxRGB==R)&&(G>=B))
H=60*(G-B)/des;
else if((maxRGB==R)&&(G<B))
H=60*(G-B)/des+360;
else if(maxRGB==G)
H=60*(B-R)/des+120;
else if(maxRGB==B)
H=60*(R-G)/des+240;
else
;
if(H<0)
H=H+360;
if((H>=0)&&(H<=60)) //对于红色进行加权
H=60-H;
else if((H>=300)&&(H<=360))
H=360-H;
else
H=0;
Hi=(int)H*60;
if((H>60)&&(H<300)) //the color is not red
RedHSV=0;
else //the color is red
RedHSV=Hi+Si+Vi;
return(RedHSV);
}
/***********************************************
*name:
*parameter:
*function:
*return:
*reserved:
***********************************************/
void PicHandle(int sta_y,unsigned int *RGB)
{
int j;
int maxRed1;
int RGB_R,RGB_G,RGB_B; //the separate value
float xx,yy,pixelnum; //the transitional value
for(j=0;j<320;j++)
{
RGB_R=(RGB[j]&RGB565_R)>>11;
RGB_G=(RGB[j]&RGB565_G)>>5;
RGB_B=(RGB[j]&RGB565_B);
maxRed1=RGB2HSV(RGB_R,RGB_G,RGB_B);
if(MaxRed<maxRed1)
{
MaxRed=maxRed1;
xx=(float)j;
yy=(float)sta_y;
}
}
pixelnum=(xx-159)*(xx-159)+(yy-119)*(yy-119); //(159,119)
pixelnum=SqrtByNewton(pixelnum);
Red_X=(int)xx; //返回x,y值,在屏上做十字标记
Red_Y=(int)yy;
PixelNums=pixelnum;
Drawx(Red_X,Red_Y);
return;
}
//该函数用于计算输入颜色与实际的差距
short int Getsub(u16 RGBReal,u16 RGBCompare)
{
if((RGBReal-RGBCompare)<0)
return (RGBCompare-RGBReal);
else
return (RGBReal-RGBCompare);
}
/***********************************************
*name: Distance(float pixelnum)
*parameter: float pixelnum//光斑到中心的像素点
*function: return the distance/cm
*return: the distance;unit:cm.
*reserved:
***********************************************/
void Distance(void)
{
float dis;
dis=RPC*PixelNums+RO;
dis=tan(dis);
dis=(L2C_H/dis);
Final_Dis=dis;
return;
}
//二值化算法
u16 Binary(u16 pixel)
{
static u16 Gray;
u8 R,G,B;
/*******提取R,G,B值*******/
R = (pixel&RGB565_R)>>11;
G = (pixel&RGB565_G)>>5;
B = (pixel&RGB565_B);
/*******灰度值计算*******/
/*******网络上大部分的公式是针对8位的*******/
/*******这条公式是针对12位的******/
Gray = (u16)((R*634+G*613+B*232));
/*******二值化*******/
if(Gray<threshold)
Gray = BLACK;
else if(Gray>=threshold)
Gray = WHITE;
return Gray;
}
//移位法进行灰度化
u16 GRB2GRey(u16 pixel)
{
static u16 Gray;
u8 R,G,B;
u8 temp;
/*******提取R,G,B值*******/
R = (pixel&RGB565_R)>>11;
G = (pixel&RGB565_G)>>5;
B = (pixel&RGB565_B);
temp =(R*28+G*151+B*77)>>8;
Gray = (u16)(((temp)&0xf8)>>3)|(((temp)&0xfc)<<3)|(((temp)&0xf8)<<8) ;
return Gray;
}
//阈值变换
//参数说明:
//LPSTR lpDIBBits:指向源DIB图像指针
//LONG lWidth:源图像宽度(象素数)
//LONG lHeight:源图像高度(象素数)
//BYTE bThre:阈值
//程序说明:
//该函数用来对图像进行阈值变换。对于灰度值小于阈值的象素直接设置
//灰度值为0;灰度值大于阈值的象素直接设置为255。
u16 ThresholdTrans(u16 pixel, u16 bThre)
{
// 判断是否小于阈值
if (pixel< bThre)
{
// 直接赋值为0
pixel = 0;
}
else
{
// 直接赋值为255
pixel= 255;
}
return pixel;
}
然后就差不多了。
main.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "lcd.h"
#include "key.h"
#include "malloc.h"
#include "sdio_sdcard.h"
#include "w25qxx.h"
#include "ff.h"
#include "exfuns.h"
#include "text.h"
#include "piclib.h"
#include "string.h"
#include "math.h"
#include "ov7670.h"
#include "timer.h"
#include "PicHandle.h"
/*****************************************
KEY0:拍照
KEY1:灰度图,二值化
KEY_UP:将BMP位图上传到电脑
********************************************/
u16 Row[320]; //用于存储一行像素信息
//u16 Color[240][320]; //用于存储一张图片240*320像素的信息
u16 s_xfact; //用于记录特征点位置
u16 s_yfact;
//文件名自增(避免覆盖)
//组合成:形如"0:PHOTO/PIC13141.bmp"的文件名
void camera_new_pathname(u8 *pname)
{
u8 res;
u16 index=0;
while(index<0XFFFF)
{
sprintf((char*)pname,"0:PHOTO/PIC%05d.bmp",index);
res=f_open(ftemp,(const TCHAR*)pname,FA_READ);//尝试打开这个文件
if(res==FR_NO_FILE)break; //该文件名不存在=正是我们需要的.
index++;
}
}
int main(void)
{
u8 res;
u8 *pname; //带路径的文件名
u8 key; //键值
u8 i;
u8 sd_ok=1; //0,sd卡不正常;1,SD卡正常.
u8 stopflag=0;
u8 effect=0;
u8 flag=0;
u16 pixcnt=0; //像素统计
u16 linecnt=0; //行数统计
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //初始化与LED连接的硬件接口
KEY_Init(); //初始化按键
LCD_Init(); //初始化LCD
W25QXX_Init(); //初始化W25Q128
my_mem_init(SRAMIN); //初始化内部内存池
exfuns_init(); //为fatfs相关变量申请内存
f_mount(fs[0],"0:",1); //挂载SD卡
f_mount(fs[1],"1:",1); //挂载FLASH.
POINT_COLOR=RED;
res=f_mkdir("0:/PHOTO"); //创建PHOTO文件夹
LCD_Scan_Dir(U2D_L2R);
pname=mymalloc(SRAMIN,30); //为带路径的文件名分配30个字节的内存
while(pname==NULL) //内存分配出错
{
}
while(OV7670_Init())//初始化OV7670
{
}
delay_ms(1500);
OV7670_Window_Set(10,174,240,320); //设置窗口
LCD_Clear(BLACK);
while(1)
{
key=KEY_Scan(0);//不支持连按
switch(key)
{
case KEY0_PRES:
{
if(sd_ok)
{
LED1=0; //点亮DS1,提示正在拍照
camera_new_pathname(pname);//得到文件名
if(bmp_encode(pname,(lcddev.width-240)/2,(lcddev.height-320)/2,240,320,0))//拍照有误
{
}
}
LED1=1;//关闭DS1
delay_ms(1800);//等待1.8秒钟
}break;
case KEY1_PRES:
{
flag++;
if(flag==3)
{flag=0;}
}break;
}
LCD_SetCursor(0x00,0x0000); //设置光标位置
LCD_WriteRAM_Prepare(); //开始写入GRAM
while(OV7670_VSYNC==0);//
while(OV7670_VSYNC==1);// 只有在VSYNC为低时,才传输数据
for(linecnt=0;linecnt<240;linecnt++)
{
while(OV7670_HREF==0);
for(pixcnt=0;pixcnt<320;pixcnt++)
{
while(OV7670_PCLK==0);
Row[pixcnt]=GPIOC->IDR&0XFF;
while(OV7670_PCLK==1);
Row[pixcnt]<<=8;
while(OV7670_PCLK==0);
Row[pixcnt]|=GPIOC->IDR&0XFF;
while(OV7670_PCLK==1);
}
for(pixcnt=0;pixcnt<320;pixcnt++)
{
if(flag==0)LCD->LCD_RAM=Row[pixcnt];
else if(flag==1)LCD->LCD_RAM=Binary(Row[pixcnt]);//RGB565二值化算法
else LCD->LCD_RAM=GRB2GRey(Row[pixcnt]);//灰度化算法
}
}
}
}
我尝试了两种数据传输方式,DMA,和直接写发现对速度影响不大,最后还是采用直接传输方式,实测帧率在4-5之间,跟PPT一样。
看一下最后的效果图 。
好了之前的遗憾也算弥补,希望对大家有所帮助。
下面是源码链接:
无FIFO的OV7670在STM32平台应用