(一) 背景介绍

其实在很久以前我就一直想搞一下摄像头的移植。当时就在淘宝上买了一个没FIFO,OV7670的模块其实当时自己连什么是FIFO都不知道。就看他便宜然后就买了。结果买回来根本不会用而且没有提供驱动。虽然好像正点原子写了一个驱动但是和F1的接口略有不同,以我当时的水平又不能理解。捣鼓了一段时间后就放弃了,当时在论坛里到处找代码也没找到,虽然有人实现了但也没有给源码。之后一会忙考试,一会有要学点东西就把这个东西给忘了。正好自己好久都没有写驱动代码了,这次正好来练练手。其实本来是准备用STM32F4来实现的F4因为外扩了SDRAM,同时可以提供较高的时钟,可以获得比较好的刷屏效果。但是因为涉及到DCMI接口,所以以后再说吧。

(二)硬件介绍

之前看OV7670的接口发现正点原子的接口对不上买的骑飞电子的模块,标记部分不一样,其实本质上还是一样的。先来看一下他们的照片。

从接口上来看区别就在于SCCB口的描述不一样,行同步信号命名不一样,时钟脚不一样。
总结起来就是

                           SIOC =SCLSIOD=SDAWRST=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发送失败,返回1else res=0;         //SDA=0发送成功,返回0SCCB_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 输入 上拉                    VSYNCGPIO_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  端口配置          PCLKGPIO_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~8GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;      //GPIO_Mode_IPU;GPIO_Init(GPIOC, &GPIO_InitStructure);GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);   //SWD   关闭JTAGCLK_init_ON();SCCB_Init();                //初始化SCCB 的IO口if(SCCB_WR_Reg(0x12,0x80))return 1;   //复位SCCBdelay_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   实测出来为8MHzRCC_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*2endy=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://sunnyreg13val=0XE5;reg01val=0X5A;reg02val=0X5C;break;   case 2://cloudyreg13val=0XE5;reg01val=0X58;reg02val=0X60;break;  case 3://officereg13val=0XE5;reg01val=0X84;reg02val=0X4c;break;  case 4://homereg13val=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://-2reg4f5054val=0X40;      reg52val=0X11;reg53val=0X2F;      break; case 1://-1reg4f5054val=0X66;      reg52val=0X1B;reg53val=0X4B;    break;    case 3://1reg4f5054val=0X99;      reg52val=0X28;reg53val=0X71;      break;   case 4://2reg4f5054val=0XC0;      reg52val=0X33;reg53val=0X8D;      break;   }SCCB_WR_Reg(0X4F,reg4f5054val);    //色彩矩阵系数1SCCB_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=2switch(bright){case 0://-2reg55val=0XB0;      break; case 1://-1reg55val=0X98;       break; case 3://1reg55val=0X18;        break; case 4://2reg55val=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=2switch(contrast){case 0://-2reg56val=0X30;      break; case 1://-1reg56val=0X38;       break; case 3://1reg56val=0X50;        break; case 4://2reg56val=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-1while(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-1color|=GPIOC->IDR&0XFF;    //读数据while(OV7670_PCLK==1);       //1-0//显示LCDLCD->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 32767int 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 maximummaxRGB=((maxRGB>=B)?maxRGB:B);minRGB=((R<=G)?R:G);                           //the minimumminRGB=((minRGB<=B)?minRGB:B);des=maxRGB-minRGB;V=maxRGB;                                                   //the value of VVi=(int)(V*366/255);                           //force to intif(maxRGB==0)                                           //the value of SS=0;elseS=1-minRGB/maxRGB;Si=(int)(S*360);                                   //force to intif(maxRGB==minRGB)                              //the value of HH=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;elseH=0;Hi=(int)H*60;if((H>60)&&(H<300))                              //the color is not redRedHSV=0;else                                            //the color is redRedHSV=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 valuefloat xx,yy,pixelnum;                               //the transitional valuefor(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); elsereturn  (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){// 直接赋值为0pixel = 0;}else{// 直接赋值为255pixel= 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);     //串口初始化为115200LED_Init();                   //初始化与LED连接的硬件接口KEY_Init();                 //初始化按键LCD_Init();                  //初始化LCD     W25QXX_Init();             //初始化W25Q128my_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;//关闭DS1delay_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平台应用

【单片机开发】无FIFO的OV7670模组在STM32F1平台上的应用相关推荐

  1. STM32F103C8T6+无FIFO的OV7670的输出测试图像实例代码

    一.工程文件链接及说明 Keil5工程文件: STM32连接OV7670的工程文件 链接:https://pan.baidu.com/s/18td0AX0sOYzV7pidIf1B3w?pwd=767 ...

  2. 制作一个有趣的涂鸦物联网小项目(涂鸦模组SDK开发 CBU BK7231N WiFi+蓝牙模组 HSV彩色控制)

    实现的功能: l  APP控制月球灯 l  本地月球灯控制 l  APP控制"大白"颜色,实现各种颜色变身 l  门状态传感器状态APP显示 l  网络状态指示灯,连接服务器长亮, ...

  3. M5311模组对接OneNet平台—AT指令基本操作流程(LwM2M协议)

    目录 概述 一.开机驻网流程 二.注册onenet平台 概述 下面将介绍M5311模组对接OneNet平台-AT指令基本操作流程(LwM2M协议),已在项目中使用. 一.开机驻网流程 1.AT+SM= ...

  4. 快速开发GD32和涂鸦CBU模组通信

    MCU和CBU模组通信 采用兆易创新的GD32单片机和涂鸦 CBU (低功耗嵌入式Wi-Fi+BLE 双协议)模组进行通信. 本文将教大家如何从0开始上手GD32系列单片机,并移植涂鸦MCU-SDK来 ...

  5. 小米wifi开发:初始配置wifi模组

    [通用模组接入指南] 官方网址: (https://iot.mi.com/new/doc/04-%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%BC%80%E5%8F%91%E6%8C% ...

  6. 华为5G模组MH5000-31在TX2上配置联网

    准备 TX2一块,Linux tegra-ubuntu 4.4.38-tegra-realtimes系统 MH5000-31模块一枚 开发底板一枚 模块升级 此步骤在Windows环境下操作,我在配置 ...

  7. M5310A模组与onenet平台通信流程详解

    M5310A与onenet平台通信AT指令流程,我将流程放在了上面,有一部分注意事项以及AT指令解释写在流程下面. 1.上电检查 AT //判断模组是否上电开机成功 AT+CSQ //信号质量检查 A ...

  8. AI-K210 开发家庭万用宝模组(1)

    手头有个以前开发自动跟随拍的K210器件(视频  https://www.douyin.com/video/6943813162625961252?previous_page=app_code_lin ...

  9. 无接触梯控模组及设备

    无接触空中成像交互技术及应用,高科技,大蓝海,全国唯一技术.#高科技#全息投影#电梯梯控#无接触#数字化展览展示

最新文章

  1. 语音合成的语音相位图
  2. 【观点】开发人员的测试悖论
  3. 计算机视觉测试数据集 dataset
  4. php 登陆信息 传递,PHP传递POST信息
  5. 【强化学习】Actor Critic原理
  6. 算法 - KMP算法(字符串匹配)
  7. python调用外部程序 退出_Python调用外部程序——os.system()和subprocess.call
  8. java voip 的sip服务器搭建_用ASTERISK搭建自己的免费VOIP服务器
  9. 小猫钓鱼纸牌游戏 python
  10. php 模拟蜘蛛,php 实现使用curl模拟百度蜘蛛进行采集
  11. 彻底关闭Adobe Flash Player的弹窗广告(不影响Flash正常使用)
  12. 华为认证级别有哪些级别分类?考HCIP还是考HCIA?
  13. 计算机系统故障如何处理,安装操作系统出错怎么办?几种常见的异常处理方法介绍(图文)...
  14. C++中时间记录的常用操作
  15. 常见的几种网络Hack方式
  16. 未来几十年内人类将可在月球定居:生育繁衍下一代
  17. 解决Ubuntu 16.04 的应用商店卸载或加载不出来的教程
  18. 2022-2028年全球与中国射频识别打印机行业深度分析
  19. 7-63 查验身份证 (15 分)
  20. 抢先看:DHS和NIST发布IoT安全指南

热门文章

  1. MHA 高可用配置(故障切换)(理论详解+实验步骤)
  2. Vue 简单的记录div滚动条的位置,并返回顶部
  3. Windows 10 清理系统休眠文件
  4. Lock()与RLock()锁
  5. 基于Unity3d 引擎的Android游戏优化 1
  6. 国密SM2加解密 for delphi xe 11.1
  7. php mysql 报错_Mac下PHP连接MySQL报错"No such file or directory"的解决办法
  8. 为什么项目管理很努力却得不到相应的回报呢?谈谈项目管理软能力实践与心得
  9. 【windows-教程】打开telnet客户端功能
  10. Effective STL之容器