【单片机开发】无FIFO的OV7670模组在STM32F1平台上的应用
(一) 背景介绍
其实在很久以前我就一直想搞一下摄像头的移植。当时就在淘宝上买了一个没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平台上的应用相关推荐
- STM32F103C8T6+无FIFO的OV7670的输出测试图像实例代码
一.工程文件链接及说明 Keil5工程文件: STM32连接OV7670的工程文件 链接:https://pan.baidu.com/s/18td0AX0sOYzV7pidIf1B3w?pwd=767 ...
- 制作一个有趣的涂鸦物联网小项目(涂鸦模组SDK开发 CBU BK7231N WiFi+蓝牙模组 HSV彩色控制)
实现的功能: l APP控制月球灯 l 本地月球灯控制 l APP控制"大白"颜色,实现各种颜色变身 l 门状态传感器状态APP显示 l 网络状态指示灯,连接服务器长亮, ...
- M5311模组对接OneNet平台—AT指令基本操作流程(LwM2M协议)
目录 概述 一.开机驻网流程 二.注册onenet平台 概述 下面将介绍M5311模组对接OneNet平台-AT指令基本操作流程(LwM2M协议),已在项目中使用. 一.开机驻网流程 1.AT+SM= ...
- 快速开发GD32和涂鸦CBU模组通信
MCU和CBU模组通信 采用兆易创新的GD32单片机和涂鸦 CBU (低功耗嵌入式Wi-Fi+BLE 双协议)模组进行通信. 本文将教大家如何从0开始上手GD32系列单片机,并移植涂鸦MCU-SDK来 ...
- 小米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% ...
- 华为5G模组MH5000-31在TX2上配置联网
准备 TX2一块,Linux tegra-ubuntu 4.4.38-tegra-realtimes系统 MH5000-31模块一枚 开发底板一枚 模块升级 此步骤在Windows环境下操作,我在配置 ...
- M5310A模组与onenet平台通信流程详解
M5310A与onenet平台通信AT指令流程,我将流程放在了上面,有一部分注意事项以及AT指令解释写在流程下面. 1.上电检查 AT //判断模组是否上电开机成功 AT+CSQ //信号质量检查 A ...
- AI-K210 开发家庭万用宝模组(1)
手头有个以前开发自动跟随拍的K210器件(视频 https://www.douyin.com/video/6943813162625961252?previous_page=app_code_lin ...
- 无接触梯控模组及设备
无接触空中成像交互技术及应用,高科技,大蓝海,全国唯一技术.#高科技#全息投影#电梯梯控#无接触#数字化展览展示
最新文章
- 语音合成的语音相位图
- 【观点】开发人员的测试悖论
- 计算机视觉测试数据集 dataset
- php 登陆信息 传递,PHP传递POST信息
- 【强化学习】Actor Critic原理
- 算法 - KMP算法(字符串匹配)
- python调用外部程序 退出_Python调用外部程序——os.system()和subprocess.call
- java voip 的sip服务器搭建_用ASTERISK搭建自己的免费VOIP服务器
- 小猫钓鱼纸牌游戏 python
- php 模拟蜘蛛,php 实现使用curl模拟百度蜘蛛进行采集
- 彻底关闭Adobe Flash Player的弹窗广告(不影响Flash正常使用)
- 华为认证级别有哪些级别分类?考HCIP还是考HCIA?
- 计算机系统故障如何处理,安装操作系统出错怎么办?几种常见的异常处理方法介绍(图文)...
- C++中时间记录的常用操作
- 常见的几种网络Hack方式
- 未来几十年内人类将可在月球定居:生育繁衍下一代
- 解决Ubuntu 16.04 的应用商店卸载或加载不出来的教程
- 2022-2028年全球与中国射频识别打印机行业深度分析
- 7-63 查验身份证 (15 分)
- 抢先看:DHS和NIST发布IoT安全指南
热门文章
- MHA 高可用配置(故障切换)(理论详解+实验步骤)
- Vue 简单的记录div滚动条的位置,并返回顶部
- Windows 10 清理系统休眠文件
- Lock()与RLock()锁
- 基于Unity3d 引擎的Android游戏优化 1
- 国密SM2加解密 for delphi xe 11.1
- php mysql 报错_Mac下PHP连接MySQL报错"No such file or directory"的解决办法
- 为什么项目管理很努力却得不到相应的回报呢?谈谈项目管理软能力实践与心得
- 【windows-教程】打开telnet客户端功能
- Effective STL之容器