平台: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<sizeof(ov7725_init_reg_tb1)/sizeof(ov7725_init_reg_tb1[0]);i++) { SCCB_WR_Reg(ov7725_init_reg_tb1[i][0],ov7725_init_reg_tb1[i][1]); } printf("HID:%d\r\n",reg); return 0x00; //ok }
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文件,在电脑端查看如下所示
(数据有点问题,(:)