平台:STM32ZET6(核心板)+ST-LINK/V2+SD卡+USB串口线+鹰眼OV7725摄像头(注意,为了减少摄像头连线的麻烦,建议初学者选取单片机时选用带有摄像头接口的板子)
工程介绍:需要移植FatFs文件系统,同时需要了解BMP位图的存储数据结构,从而实现将摄像头输出的RGB565像素数据直接输出到sd卡上,保存为*.bmp文件。
1. BMP位图的存储
1.1 数据结构介绍
//BMP头文件
typedef __packed struct
{
u16 bfType ; //文件标志.只对'BM',用来识别BMP位图类型
u32 bfSize ; //文件大小,占四个字节
u16 bfReserved1 ;//保留
u16 bfReserved2 ;//保留
u32 bfOffBits ; //从文件开始到位图数据(bitmap data)开始之间的的偏移量
}BITMAPFILEHEADER ;
//BMP信息头
typedef __packed struct
{
u32 biSize ; //说明BITMAPINFOHEADER结构所需要的字数。
long biWidth ; //说明图象的宽度,以象素为单位
long biHeight ; //说明图象的高度,以象素为单位
u16 biPlanes ; //为目标设备说明位面数,其值将总是被设为1
u16 biBitCount ; //说明比特数/象素,其值为1、4、8、16、24、或32
/*说明图象数据压缩的类型。其值可以是下述值之一:
BI_RGB:没有压缩;
BI_RLE8:每个象素8比特的RLE压缩编码,压缩格式由2字节组成(重复象素计数和颜色索引);
BI_RLE4:每个象素4比特的RLE压缩编码,压缩格式由2字节组成
BI_BITFIELDS:每个象素的比特由指定的掩码决定。*/
u32 biCompression ;
u32 biSizeImage ; //说明图象的大小,以字节为单位。当用BI_RGB格式时,可设置为0
long biXPelsPerMeter ; //说明水平分辨率,用象素/米表示
long biYPelsPerMeter ; //说明垂直分辨率,用象素/米表示
u32 biClrUsed ; //说明位图实际使用的彩色表中的颜色索引数
u32 biClrImportant ; //说明对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要。
}BITMAPINFOHEADER ;
//彩色表
typedef __packed struct
{
u8 rgbBlue ; //指定蓝色强度
u8 rgbGreen ; //指定绿色强度
u8 rgbRed ; //指定红色强度
u8 rgbReserved ; //保留,设置为0
}RGBQUAD ;
//整体信息头(以上数据结构的组合)
typedef __packed struct
{
BITMAPFILEHEADER bmfHeader;
BITMAPINFOHEADER bmiHeader;
RGBQUAD RGB_MASK[3]; //调色板用于存放RGB掩码.
}BITMAPINFO;
1.2 设置方法
//提前检查是否需要保存图片
if(ov_sta && KEY_Scan(S1))//按键1按下(拍照)
{
printf("开始保存图片\r\n");
//打开文件,若不存在就创建
res_sd = f_open(&fnew, "0:test1.bmp", FA_OPEN_ALWAYS | FA_WRITE);
//文件打开成功
if(res_sd == FR_OK)
{
//填写文件信息头信息
bmp.bmfHeader.bfType = 0x4D42; //bmp类型
bmp.bmfHeader.bfSize= 54 + 320*240*2; //文件大小(信息结构体+像素数据)
bmp.bmfHeader.bfReserved1 = 0x0000; //保留,必须为0
bmp.bmfHeader.bfReserved2 = 0x0000;
bmp.bmfHeader.bfOffBits=54; //位图信息结构体所占的字节数
//填写位图信息头信息
bmp.bmiHeader.biSize=40; //位图信息头的大小
bmp.bmiHeader.biWidth=320; //位图的宽度
bmp.bmiHeader.biHeight=240; //图像的高度
bmp.bmiHeader.biPlanes=1; //目标设别的级别,必须是1
bmp.bmiHeader.biBitCount=16; //每像素位数
bmp.bmiHeader.biCompression=0; //RGB555格式
bmp.bmiHeader.biSizeImage=320*240*2; //实际位图所占用的字节数(仅考虑位图像素数据)
bmp.bmiHeader.biXPelsPerMeter=0; //水平分辨率
bmp.bmiHeader.biYPelsPerMeter=0; //垂直分辨率
bmp.bmiHeader.biClrImportant=0; //说明图像显示有重要影响的颜色索引数目,0代表所有的颜色一样重要
bmp.bmiHeader.biClrUsed=0; //位图实际使用的彩色表中的颜色索引数,0表示使用所有的调色板项
//RGB565格式掩码
bmp.RGB_MASK[0].rgbBlue = 0;
bmp.RGB_MASK[0].rgbGreen = 0xF8;
bmp.RGB_MASK[0].rgbRed = 0;
bmp.RGB_MASK[0].rgbReserved = 0;
bmp.RGB_MASK[1].rgbBlue = 0xE0;
bmp.RGB_MASK[1].rgbGreen = 0x07;
bmp.RGB_MASK[1].rgbRed = 0;
bmp.RGB_MASK[1].rgbReserved = 0;
bmp.RGB_MASK[2].rgbBlue = 0x1F;
bmp.RGB_MASK[2].rgbGreen = 0;
bmp.RGB_MASK[2].rgbRed = 0;
bmp.RGB_MASK[2].rgbReserved = 0;
//写文件头进文件
res_sd= f_write(&fnew, &bmp, sizeof(bmp), &fnum);
}
//读指针复位
OV7725_RRST=0; //开始复位读指针
OV7725_RCK_L;
OV7725_RCK_H;
OV7725_RCK_L;
OV7725_RRST=1; //复位读指针结束
OV7725_RCK_H;
/*图像花屏的原因在于读取时的干扰和读取时漏掉几个像素*/
for(i=0;i<240;i++)
{
for(j=0;j<320;j++)
{
OV7725_RCK_L;
color=GPIOC->IDR&0XFF; //读数据
OV7725_RCK_H;
color<<=8;
OV7725_RCK_L;
color|=GPIOC->IDR&0XFF; //读数据
OV7725_RCK_H;
//写位图信息头进内存卡(FatFs中的方法)
f_write(&fnew, &color, sizeof(color), &fnum);
}
}
//关闭文件
f_close(&fnew);
delay_ms(1000);
return;
}
2. OV7725驱动程序设计
2.1 OV7725寄存器设置
#ifndef _OV7725CFG_H
#define _OV7725CFG_H
#include "ov7725.h"
/* OV7725寄存器宏定义 */
#define GAIN 0x00
#define BLUE 0x01
#define RED 0x02
#define RED 0x02
#define GREEN 0x03
#define BAVG 0x05
#define GAVG 0x06
#define RAVG 0x07
#define AECH 0x08
#define COM2 0x09
#define PID 0x0A
#define VER 0x0B
#define COM3 0x0C
#define COM4 0x0D
#define COM5 0x0E
#define COM6 0x0F
#define AEC 0x10
#define CLKRC 0x11
#define COM7 0x12
#define COM8 0x13
#define COM9 0x14
#define COM10 0x15
#define REG16 0x16
#define HSTART 0x17
#define HSIZE 0x18
#define VSTRT 0x19
#define VSIZE 0x1A
#define PSHFT 0x1B
#define MIDH 0x1C
#define MIDL 0x1D
#define LAEC 0x1F
#define COM11 0x20
#define BDBase 0x22
#define BDMStep 0x23
#define AEW 0x24
#define AEB 0x25
#define VPT 0x26
#define REG28 0x28
#define HOutSize 0x29
#define EXHCH 0x2A
#define EXHCL 0x2B
#define VOutSize 0x2C
#define ADVFL 0x2D
#define ADVFH 0x2E
#define YAVE 0x2F
#define LumHTh 0x30
#define LumLTh 0x31
#define HREF 0x32
#define DM_LNL 0x33
#define DM_LNH 0x34
#define ADoff_B 0x35
#define ADoff_R 0x36
#define ADoff_Gb 0x37
#define ADoff_Gr 0x38
#define Off_B 0x39
#define Off_R 0x3A
#define Off_Gb 0x3B
#define Off_Gr 0x3C
#define COM12 0x3D
#define COM13 0x3E
#define COM14 0x3F
#define COM16 0x41
#define TGT_B 0x42
#define TGT_R 0x43
#define TGT_Gb 0x44
#define TGT_Gr 0x45
#define LC_CTR 0x46
#define LC_XC 0x47
#define LC_YC 0x48
#define LC_COEF 0x49
#define LC_RADI 0x4A
#define LC_COEFB 0x4B
#define LC_COEFR 0x4C
#define FixGain 0x4D
#define AREF1 0x4F
#define AREF6 0x54
#define UFix 0x60
#define VFix 0x61
#define AWBb_blk 0x62
#define AWB_Ctrl0 0x63
#define DSP_Ctrl1 0x64
#define DSP_Ctrl2 0x65
#define DSP_Ctrl3 0x66
#define DSP_Ctrl4 0x67
#define AWB_bias 0x68
#define AWBCtrl1 0x69
#define AWBCtrl2 0x6A
#define AWBCtrl3 0x6B
#define AWBCtrl4 0x6C
#define AWBCtrl5 0x6D
#define AWBCtrl6 0x6E
#define AWBCtrl7 0x6F
#define AWBCtrl8 0x70
#define AWBCtrl9 0x71
#define AWBCtrl10 0x72
#define AWBCtrl11 0x73
#define AWBCtrl12 0x74
#define AWBCtrl13 0x75
#define AWBCtrl14 0x76
#define AWBCtrl15 0x77
#define AWBCtrl16 0x78
#define AWBCtrl17 0x79
#define AWBCtrl18 0x7A
#define AWBCtrl19 0x7B
#define AWBCtrl20 0x7C
#define AWBCtrl21 0x7D
#define GAM1 0x7E
#define GAM2 0x7F
#define GAM3 0x80
#define GAM4 0x81
#define GAM5 0x82
#define GAM6 0x83
#define GAM7 0x84
#define GAM8 0x85
#define GAM9 0x86
#define GAM10 0x87
#define GAM11 0x88
#define GAM12 0x89
#define GAM13 0x8A
#define GAM14 0x8B
#define GAM15 0x8C
#define SLOP 0x8D
#define DNSTh 0x8E
#define EDGE0 0x8F
#define EDGE1 0x90
#define DNSOff 0x91
#define EDGE2 0x92
#define EDGE3 0x93
#define MTX1 0x94
#define MTX2 0x95
#define MTX3 0x96
#define MTX4 0x97
#define MTX5 0x98
#define MTX6 0x99
#define MTX_Ctrl 0x9A
#define BRIGHT 0x9B
#define CNST 0x9C
#define UVADJ0 0x9E
#define UVADJ1 0x9F
#define SCAL0 0xA0
#define SCAL1 0xA1
#define SCAL2 0xA2
#define SDE 0xA6
#define USAT 0xA7
#define VSAT 0xA8
#define HUECOS 0xA9
#define HUESIN 0xAA
#define SIGN 0xAB
#define DSPAuto 0xAC
//初始化寄存器序列及其对应的值
const u8 ov7725_init_reg_tb1[][2]=
{
/*输出窗口设置*/
{CLKRC, 0x00}, //clock config
{COM7, 0x06}, //VGA RGB565
{HSTART, 0x3f}, //水平起始位置
{HSIZE, 0x50}, //水平尺寸
{VSTRT, 0x03}, //垂直起始位置
{VSIZE, 0x78}, //垂直尺寸
{HREF, 0x00},
{HOutSize, 0x50}, //输出尺寸
{VOutSize, 0x78}, //输出尺寸
/*DSP control*/
{TGT_B, 0x7f},
{FixGain, 0x09},
{AWB_Ctrl0, 0xe0},
{DSP_Ctrl1, 0xff},
{DSP_Ctrl2, 0x00},
{DSP_Ctrl3, 0x00},
{DSP_Ctrl4, 0x00},
/*AGC AEC AWB*/
{COM8, 0xf0},
{COM4, 0x81}, /*Pll AEC CONFIG*/
{COM6, 0xc5},
{COM9, 0x11},
{BDBase, 0x7F},
{BDMStep, 0x03},
{AEW, 0x40},
{AEB, 0x30},
{VPT, 0xa1},
{EXHCL, 0x9e},
{AWBCtrl3, 0xaa},
{COM8, 0xff},
/*matrix shapness brightness contrast*/
{EDGE1, 0x08},
{DNSOff, 0x01},
{EDGE2, 0x03},
{EDGE3, 0x00},
{MTX1, 0xb0},
{MTX2, 0x9d},
{MTX3, 0x13},
{MTX4, 0x16},
{MTX5, 0x7b},
{MTX6, 0x91},
{MTX_Ctrl, 0x1e},
{BRIGHT, 0x08},
{CNST, 0x20},
{UVADJ0, 0x81},
{SDE, 0X06},
{USAT, 0x65},
{VSAT, 0x65},
{HUECOS, 0X80},
{HUESIN, 0X80},
/*GAMMA config*/
{GAM1, 0x0c},
{GAM2, 0x16},
{GAM3, 0x2a},
{GAM4, 0x4e},
{GAM5, 0x61},
{GAM6, 0x6f},
{GAM7, 0x7b},
{GAM8, 0x86},
{GAM9, 0x8e},
{GAM10, 0x97},
{GAM11, 0xa4},
{GAM12, 0xaf},
{GAM13, 0xc5},
{GAM14, 0xd7},
{GAM15, 0xe8},
{SLOP, 0x20},
{COM3, 0x50},/*Horizontal mirror image*/
//注:datasheet默认0x10,即改变YUV为UVY格式。但是摄像头不是芯片而是模组时,
//要将0X10中的1变成0,即设置YUV格式。
/*night mode auto frame rate control*/
{COM5, 0xf5}, /*在夜视环境下,自动降低帧率,保证低照度画面质量*/
//{COM5, 0x31}, /*夜视环境帧率不变*/
};
#endif
2.2 OV7725初始化
#define OV7725_MID 0X7FA2
#define OV7725_PID 0X7721
#define OV7725_VSYNC PAin(8) //同步信号检测IO
#define OV7725_WRST PDout(6) //写指针复位
#define OV7725_WREN PBout(3) //写入FIFO使能
#define OV7725_RCK_H GPIOB->BSRR=1<<4 //设置读数据时钟高电平
#define OV7725_RCK_L GPIOB->BRR=1<<4 //设置读数据时钟低电平
#define OV7725_RRST PGout(14) //读指针复位
#define OV7725_CS PGout(15) //片选信号(OE)
#define OV7725_DATA GPIO_ReadInputData(GPIOC,0x00FF) //数据输入端口
#include "sys.h"
#include "ov7725.h"
#include "ov7725config.h"
#include "delay.h"
#include "usart.h"
#include "sccb.h"
//初始化OV7725
//返回0:成功
//返回其他值:错误代码
u8 OV7725_Init(void)
{
u16 i=0;
u16 reg=0;
//设置IO
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOG|RCC_APB2Periph_AFIO, ENABLE);//使能相关端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //PA8 输入 上拉
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_8);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4; // 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_3|GPIO_Pin_4);
GPIO_InitStructure.GPIO_Pin = 0xff; //PC0~7 输入 上拉
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_SetBits(GPIOD,GPIO_Pin_6);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14|GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOG, &GPIO_InitStructure);
GPIO_SetBits(GPIOG,GPIO_Pin_14|GPIO_Pin_15);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); //SWD
SCCB_Init(); //初始化SCCB 的IO口
if(SCCB_WR_Reg(0x12,0x80))return 1; //软复位OV7725
delay_ms(100);
reg=SCCB_RD_Reg(0X1c); //读取厂家ID 高八位
reg<<=8;
reg|=SCCB_RD_Reg(0X1d); //读取厂家ID 低八位
if(reg!=OV7725_MID)
{
printf("MID:%d\r\n",reg);
return 1;
}
reg=SCCB_RD_Reg(0X0a); //读取厂家ID 高八位
reg<<=8;
reg|=SCCB_RD_Reg(0X0b); //读取厂家ID 低八位
if(reg!=OV7725_PID)
{
printf("HID:%d\r\n",reg);
return 2;
}
//初始化 OV7725,采用QVGA分辨率(320*240)
for(i=0;i
2.3 OV7725其他功能设置
////////////////////////////////////////////////////////////////////////////
//OV7725功能设置
//白平衡设置
//0:自动模式
//1:晴天
//2,多云
//3,办公室
//4,家里
//5,夜晚
void OV7725_Light_Mode(u8 mode)
{
switch(mode)
{
case 0: //Auto,自动模式
SCCB_WR_Reg(0x13, 0xff); //AWB on
SCCB_WR_Reg(0x0e, 0x65);
SCCB_WR_Reg(0x2d, 0x00);
SCCB_WR_Reg(0x2e, 0x00);
break;
case 1://sunny,晴天
SCCB_WR_Reg(0x13, 0xfd); //AWB off
SCCB_WR_Reg(0x01, 0x5a);
SCCB_WR_Reg(0x02, 0x5c);
SCCB_WR_Reg(0x0e, 0x65);
SCCB_WR_Reg(0x2d, 0x00);
SCCB_WR_Reg(0x2e, 0x00);
break;
case 2://cloudy,多云
SCCB_WR_Reg(0x13, 0xfd); //AWB off
SCCB_WR_Reg(0x01, 0x58);
SCCB_WR_Reg(0x02, 0x60);
SCCB_WR_Reg(0x0e, 0x65);
SCCB_WR_Reg(0x2d, 0x00);
SCCB_WR_Reg(0x2e, 0x00);
break;
case 3://office,办公室
SCCB_WR_Reg(0x13, 0xfd); //AWB off
SCCB_WR_Reg(0x01, 0x84);
SCCB_WR_Reg(0x02, 0x4c);
SCCB_WR_Reg(0x0e, 0x65);
SCCB_WR_Reg(0x2d, 0x00);
SCCB_WR_Reg(0x2e, 0x00);
break;
case 4://home,家里
SCCB_WR_Reg(0x13, 0xfd); //AWB off
SCCB_WR_Reg(0x01, 0x96);
SCCB_WR_Reg(0x02, 0x40);
SCCB_WR_Reg(0x0e, 0x65);
SCCB_WR_Reg(0x2d, 0x00);
SCCB_WR_Reg(0x2e, 0x00);
break;
case 5://night,夜晚
SCCB_WR_Reg(0x13, 0xff); //AWB on
SCCB_WR_Reg(0x0e, 0xe5);
break;
}
}
//色度设置
//sat:-4~+4
void OV7725_Color_Saturation(s8 sat)
{
if(sat>=-4 && sat<=4)
{
SCCB_WR_Reg(USAT,(sat+4)<<4);
SCCB_WR_Reg(VSAT,(sat+4)<<4);
}
}
//亮度设置
//bright:-4~+4
void OV7725_Brightness(s8 bright)
{
u8 bright_value,sign;
switch(bright)
{
case 4:
bright_value = 0x48;
sign = 0x06;
break;
case 3:
bright_value = 0x38;
sign = 0x06;
break;
case 2:
bright_value = 0x28;
sign = 0x06;
break;
case 1:
bright_value = 0x18;
sign = 0x06;
break;
case 0:
bright_value = 0x08;
sign = 0x06;
break;
case -1:
bright_value = 0x08;
sign = 0x0e;
break;
case -2:
bright_value = 0x18;
sign = 0x0e;
break;
case -3:
bright_value = 0x28;
sign = 0x0e;
break;
case -4:
bright_value = 0x38;
sign = 0x0e;
break;
}
SCCB_WR_Reg(BRIGHT, bright_value);
SCCB_WR_Reg(SIGN, sign);
}
//对比度设置
//contrast:-4~+4
void OV7725_Contrast(s8 contrast)
{
if(contrast >= -4 && contrast <=4)
{
SCCB_WR_Reg(CNST,(0x30-(4-contrast)*4));
}
}
//特效设置
//0:普通模式
//1,负片
//2,黑白
//3,偏红色
//4,偏绿色
//5,偏蓝色
//6,复古
void OV7725_Special_Effects(u8 eft)
{
switch(eft)
{
case 0://正常
SCCB_WR_Reg(0xa6, 0x06);//TSLB设置
SCCB_WR_Reg(0x60, 0x80);//MANV,手动V值
SCCB_WR_Reg(0x61, 0x80);//MANU,手动U值
break;
case 1://负片
SCCB_WR_Reg(0xa6, 0x46);
break;
case 2://黑白
SCCB_WR_Reg(0xa6, 0x26);
SCCB_WR_Reg(0x60, 0x80);
SCCB_WR_Reg(0x61, 0x80);
break;
case 3://偏红
SCCB_WR_Reg(0xa6, 0x1e);
SCCB_WR_Reg(0x60, 0x80);
SCCB_WR_Reg(0x61, 0xc0);
break;
case 4://偏绿
SCCB_WR_Reg(0xa6, 0x1e);
SCCB_WR_Reg(0x60, 0x60);
SCCB_WR_Reg(0x61, 0x60);
break;
case 5://偏蓝
SCCB_WR_Reg(0xa6, 0x1e);
SCCB_WR_Reg(0x60, 0xa0);
SCCB_WR_Reg(0x61, 0x40);
break;
case 6://复古
SCCB_WR_Reg(0xa6, 0x1e);
SCCB_WR_Reg(0x60, 0x40);
SCCB_WR_Reg(0x61, 0xa0);
break;
}
}
2.4 设置OV7725输出窗口和输出数据的格式(QVGA或VGA)
//设置图像输出窗口
//width:输出图像宽度,<=320
//height:输出图像高度,<=240
//mode:0,QVGA输出模式;1,VGA输出模式
//QVGA模式可视范围广但近物不是很清晰,VGA模式可视范围小近物清晰
void OV7725_Window_Set(u16 width,u16 height,u8 mode)
{
u8 raw,temp;
u16 sx,sy;
if(mode)
{
sx=(640-width)/2;
sy=(480-height)/2;
SCCB_WR_Reg(COM7,0x06); //设置为VGA模式
SCCB_WR_Reg(HSTART,0x23); //水平起始位置
SCCB_WR_Reg(HSIZE,0xA0); //水平尺寸
SCCB_WR_Reg(VSTRT,0x07); //垂直起始位置
SCCB_WR_Reg(VSIZE,0xF0); //垂直尺寸
SCCB_WR_Reg(HREF,0x00);
SCCB_WR_Reg(HOutSize,0xA0); //输出尺寸
SCCB_WR_Reg(VOutSize,0xF0); //输出尺寸
}
else
{
sx=(320-width)/2;
sy=(240-height)/2;
SCCB_WR_Reg(COM7,0x46); //设置为QVGA模式
SCCB_WR_Reg(HSTART,0x3f); //水平起始位置
SCCB_WR_Reg(HSIZE, 0x50); //水平尺寸
SCCB_WR_Reg(VSTRT, 0x03); //垂直起始位置
SCCB_WR_Reg(VSIZE, 0x78); //垂直尺寸
SCCB_WR_Reg(HREF, 0x00);
SCCB_WR_Reg(HOutSize,0x50); //输出尺寸
SCCB_WR_Reg(VOutSize,0x78); //输出尺寸
}
raw=SCCB_RD_Reg(HSTART);
temp=raw+(sx>>2);//sx高8位存在HSTART,低2位存在HREF[5:4]
SCCB_WR_Reg(HSTART,temp);
SCCB_WR_Reg(HSIZE,width>>2);//width高8位存在HSIZE,低2位存在HREF[1:0]
raw=SCCB_RD_Reg(VSTRT);
temp=raw+(sy>>1);//sy高8位存在VSTRT,低1位存在HREF[6]
SCCB_WR_Reg(VSTRT,temp);
SCCB_WR_Reg(VSIZE,height>>1);//height高8位存在VSIZE,低1位存在HREF[2]
raw=SCCB_RD_Reg(HREF);
temp=((sy&0x01)<<6)|((sx&0x03)<<4)|((height&0x01)<<2)|(width&0x03)|raw;
SCCB_WR_Reg(HREF,temp);
SCCB_WR_Reg(HOutSize,width>>2);
SCCB_WR_Reg(VOutSize,height>>1);
SCCB_RD_Reg(EXHCH);
temp = (raw|(width&0x03)|((height&0x01)<<2));
SCCB_WR_Reg(EXHCH,temp);
}
3. VSYNC中断配置(帧中断信号处理)
3.1 中断GPIO口初始化
//外部中断8初始化
void EXTI8_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//选择中断信号源,开启中断线
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource8);//PA8对中断线8
EXTI_InitStructure.EXTI_Line=EXTI_Line8;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;;//下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; //使能按键所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}
3.2 中断服务程序
//外部中断5~9服务程序,外部中断5-9和中断10-15向量存放在一起
void EXTI9_5_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line8)==SET)//是8线的中断
{
if(ov_sta<2)
{
if(ov_sta==0)
{
OV7725_WRST=0; //复位写指针
OV7725_WRST=1;
OV7725_WREN=1; //允许写入FIFO
}else
{
OV7725_WREN=0; //禁止写入FIFO
OV7725_WRST=0; //复位写指针
OV7725_WRST=1;
}
ov_sta++;
}
}
EXTI_ClearITPendingBit(EXTI_Line8); //清除EXTI8线路挂起位
}
4. 主函数
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"
#include "string.h"
#include "OV7725.h"
#include "timer.h"
#include "exti.h"
#include "ff.h"
#include "sdio.h"
//const u8*LMODE_TBL[5]={"Auto","Sunny","Cloudy","Office","Home"}; //5种光照模式
//const u8*EFFECTS_TBL[7]={"Normal","Negative","B&W","Redish","Greenish","Bluish","Antique"}; //7种特效
extern u8 ov_sta; //在exit.c里面定义
extern u8 ov_frame; //在timer.c里面定义
//由于OV7725传感器安装方式原因,OV7725_WINDOW_WIDTH相当于LCD的高度,OV7725_WINDOW_HEIGHT相当于LCD的宽度
//注意:此宏定义只对OV7725有效
#define OV7725_WINDOW_WIDTH 320 // <=320
#define OV7725_WINDOW_HEIGHT 240 // <=240
//内存卡的读写状态
SD_Error Status;
FATFS fs; //FatFs文件系统对象
FIL fnew; //文件对象
FRESULT res_sd;//文件操作结果
UINT fnum; //文件成功读写数量
//BMP头文件
typedef __packed struct
{
u16 bfType ; //文件标志.只对'BM',用来识别BMP位图类型
u32 bfSize ; //文件大小,占四个字节
u16 bfReserved1 ;//保留
u16 bfReserved2 ;//保留
u32 bfOffBits ; //从文件开始到位图数据(bitmap data)开始之间的的偏移量
}BITMAPFILEHEADER ;
//BMP信息头
typedef __packed struct
{
u32 biSize ; //说明BITMAPINFOHEADER结构所需要的字数。
long biWidth ; //说明图象的宽度,以象素为单位
long biHeight ; //说明图象的高度,以象素为单位
u16 biPlanes ; //为目标设备说明位面数,其值将总是被设为1
u16 biBitCount ; //说明比特数/象素,其值为1、4、8、16、24、或32
/*说明图象数据压缩的类型。其值可以是下述值之一:
BI_RGB:没有压缩;
BI_RLE8:每个象素8比特的RLE压缩编码,压缩格式由2字节组成(重复象素计数和颜色索引);
BI_RLE4:每个象素4比特的RLE压缩编码,压缩格式由2字节组成
BI_BITFIELDS:每个象素的比特由指定的掩码决定。*/
u32 biCompression ;
u32 biSizeImage ; //说明图象的大小,以字节为单位。当用BI_RGB格式时,可设置为0
long biXPelsPerMeter ; //说明水平分辨率,用象素/米表示
long biYPelsPerMeter ; //说明垂直分辨率,用象素/米表示
u32 biClrUsed ; //说明位图实际使用的彩色表中的颜色索引数
u32 biClrImportant ; //说明对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要。
}BITMAPINFOHEADER ;
//彩色表
typedef __packed struct
{
u8 rgbBlue ; //指定蓝色强度
u8 rgbGreen ; //指定绿色强度
u8 rgbRed ; //指定红色强度
u8 rgbReserved ;//保留,设置为0
}RGBQUAD ;
//整体信息头
typedef __packed struct
{
BITMAPFILEHEADER bmfHeader;
BITMAPINFOHEADER bmiHeader;
RGBQUAD RGB_MASK[3]; //调色板用于存放RGB掩码.
}BITMAPINFO;
//更新LCD显示
void camera_refresh(void)
{
u32 i,j;
u16 color;
BITMAPINFO bmp;
//提前检查是否需要保存图片
if(ov_sta && KEY_Scan(S1))//按键1按下
{
printf("开始保存图片\r\n");
//打开文件,若不存在就创建
res_sd = f_open(&fnew, "0:test1.bmp", FA_OPEN_ALWAYS | FA_WRITE);
//文件打开成功
if(res_sd == FR_OK)
{
//填写文件信息头信息
bmp.bmfHeader.bfType = 0x4D42; //bmp类型
bmp.bmfHeader.bfSize= 54 + 320*240*2; //文件大小(信息结构体+像素数据)
bmp.bmfHeader.bfReserved1 = 0x0000; //保留,必须为0
bmp.bmfHeader.bfReserved2 = 0x0000;
bmp.bmfHeader.bfOffBits=54; //位图信息结构体所占的字节数
//填写位图信息头信息
bmp.bmiHeader.biSize=40; //位图信息头的大小
bmp.bmiHeader.biWidth=320; //位图的宽度
bmp.bmiHeader.biHeight=240; //图像的高度
bmp.bmiHeader.biPlanes=1; //目标设别的级别,必须是1
bmp.bmiHeader.biBitCount=16; //每像素位数
bmp.bmiHeader.biCompression=0; //RGB555格式
bmp.bmiHeader.biSizeImage=320*240*2; //实际位图所占用的字节数(仅考虑位图像素数据)
bmp.bmiHeader.biXPelsPerMeter=0; //水平分辨率
bmp.bmiHeader.biYPelsPerMeter=0; //垂直分辨率
bmp.bmiHeader.biClrImportant=0; //说明图像显示有重要影响的颜色索引数目,0代表所有的颜色一样重要
bmp.bmiHeader.biClrUsed=0; //位图实际使用的彩色表中的颜色索引数,0表示使用所有的调色板项
//RGB565格式掩码
bmp.RGB_MASK[0].rgbBlue = 0;
bmp.RGB_MASK[0].rgbGreen = 0xF8;
bmp.RGB_MASK[0].rgbRed = 0;
bmp.RGB_MASK[0].rgbReserved = 0;
bmp.RGB_MASK[1].rgbBlue = 0xE0;
bmp.RGB_MASK[1].rgbGreen = 0x07;
bmp.RGB_MASK[1].rgbRed = 0;
bmp.RGB_MASK[1].rgbReserved = 0;
bmp.RGB_MASK[2].rgbBlue = 0x1F;
bmp.RGB_MASK[2].rgbGreen = 0;
bmp.RGB_MASK[2].rgbRed = 0;
bmp.RGB_MASK[2].rgbReserved = 0;
//写文件头进文件
res_sd= f_write(&fnew, &bmp, sizeof(bmp), &fnum);
}
//读指针复位
OV7725_RRST=0; //开始复位读指针
OV7725_RCK_L;
OV7725_RCK_H;
OV7725_RCK_L;
OV7725_RRST=1; //复位读指针结束
OV7725_RCK_H;
/*图像花屏的原因在于读取时的干扰和读取时漏掉几个像素*/
for(i=0;i<240;i++)
{
for(j=0;j<320;j++)
{
OV7725_RCK_L;
color=GPIOC->IDR&0XFF; //读数据
OV7725_RCK_H;
color<<=8;
OV7725_RCK_L;
color|=GPIOC->IDR&0XFF; //读数据
OV7725_RCK_H;
//写位图信息头进内存卡
f_write(&fnew, &color, sizeof(color), &fnum);
}
}
//关闭文件
f_close(&fnew);
delay_ms(1000);
return;
}
//不需要保存图片,继续刷新LCD
if(ov_sta)
{
LCD_Scan_Dir(U2D_L2R); //从上到下,从左到右
LCD_WriteRAM_Prepare(); //开始写入GRAM
//读指针复位
OV7725_RRST=0; //开始复位读指针
OV7725_RCK_L;
OV7725_RCK_H;
OV7725_RCK_L;
OV7725_RRST=1; //复位读指针结束
OV7725_RCK_H;
/*图像花屏的原因在于读取时的干扰和读取时漏掉几个像素*/
for(i=0;i<240;i++)
{
for(j=0;j<320;j++)
{
OV7725_RCK_L;
color=GPIOC->IDR&0XFF; //读数据
OV7725_RCK_H;
color<<=8;
OV7725_RCK_L;
color|=GPIOC->IDR&0XFF; //读数据
OV7725_RCK_H;
LCD->LCD_RAM=color;
}
}
ov_sta=0; //开始下一次采集
ov_frame++;
LCD_Scan_Dir(DFT_SCAN_DIR); //恢复默认扫描方向
}
}
int main(void)
{
u8 lightmode=0,saturation=2,brightness=2,contrast=2;
u8 effect=0;
u8 i=0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为 115200
LED_Init(); //初始化与LED连接的硬件接口
KEY_Init(); //初始化按键
LCD_Init(); //初始化LCD
//初始化SD卡
if(SD_Init() == SD_OK)
{
printf("SD卡初始化成功,即将挂载SD卡。\r\n");
}
//挂载SD卡
res_sd = f_mount(&fs, "0:", 1);
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(60,50,200,16,16,"M3S STM32");
LCD_ShowString(60,70,200,16,16,"OV7725 TEST");
LCD_ShowString(60,90,200,16,16,"www.doflye.net");
LCD_ShowString(60,110,200,16,16,"need a 7725 camera");
LCD_ShowString(60,130,200,16,16,"S1:Light Mode");
LCD_ShowString(60,150,200,16,16,"S2:Saturation");
LCD_ShowString(60,170,200,16,16,"S3:Brightness");
LCD_ShowString(60,190,200,16,16,"S4:Contrast");
LCD_ShowString(60,210,200,16,16,"OV7725 Init...");
while(1)
{
//初始化ov7725
if(OV7725_Init()==0)
{
LCD_ShowString(60,210,200,16,16,"OV7725 Init OK ");
OV7725_Light_Mode(lightmode);
OV7725_Color_Saturation(saturation);
OV7725_Brightness(brightness);
OV7725_Contrast(contrast);
OV7725_Special_Effects(effect);
//设置输出格式
OV7725_Window_Set(OV7725_WINDOW_WIDTH,OV7725_WINDOW_HEIGHT,0); //QVGA模式输出
//输出使能
OV7725_CS=0;
break;
}
}
EXTI8_Init(); //使能定时器捕获
LCD_Clear(BLACK); //可以防止割屏
while(1)
{
camera_refresh(); //更新显示
i++;
if(i==15) //DS0闪烁.
{
i=0;
LED0=!LED0;
}
}
}
5. 实验效果
按下按钮S1,LCD图像停止刷新一秒,然后再SD卡上生成一张test1.bmp文件,在电脑端查看如下所示
(数据有点问题,(:)