目录

学习目标

运行结果

内容

OV2640

特点

时序

帧输出时序

配置

DCMI

特点

信号

DMA

寄存器

配置

硬件连接

代码

总结


学习目标

今天我们要学习的是OV2640摄像头实验,采用的是DCMI接口,进行传输。个人觉得难度较大,加上没有相应的串口线,导致部分实验无法进行,所以就先讲解理论知识,等串口线到了再把电脑端的实验补上。

运行结果

摄像头实验

内容

OV2640

OV2640 是 OV(OmniVision)公司生产的一颗 1/4 寸的 CMOS UXGA(1632*1232)图像传感器。该传感器体积小、工作电压低,提供单片 UXGA 摄像头和影像处理器的所有功能。通过 SCCB 总线控制,可以输出整帧、子采样、缩放和取窗口等方式的各种分辨率 8/10 位影像数据。该产品 UXGA 图像最高达到 15 帧/秒(SVGA 可达 30 帧,CIF 可达 60 帧)。用户可以完全控制图像质量、数据格式和传输方式。所有图像处理功能过程包括伽玛曲线、白平衡、对比度、色度等都可以通过 SCCB 接口编程。

特点

  • 高灵敏度、低电压适合嵌入式应用
  • 标准的 SCCB 接口,兼容 IIC 接口
  • 支持 RawRGB、RGB(RGB565/RGB555)、GRB422、YUV(422/420)和 YCbCr(422) 输出格式
  • 支持 UXGA、SXGA、SVGA 以及按比例缩小到从 SXGA 到 40*30 的任何尺寸
  • 支持自动曝光控制、自动增益控制、自动白平衡、自动消除灯光条纹、自动黑电平校 准等自动控制功能。同时支持色饱和度、色相、伽马、锐度等设置。
  • 支持闪光灯
  • 支持图像缩放、平移和窗口设置
  • 支持图像压缩,即可输出 JPEG 图像数据
  • 自带嵌入式微处理器
  • UXGA , 即 分 辨 率 位 1600*1200 的输出格式,类似的还有: SXGA(1280*1024) 、 WXGA+(1440*900)、XVGA(1280*960)、WXGA(1280*800)、XGA(1024*768)、SVGA(800*600)、 VGA(640*480)、CIF(352*288)、WQVGA(400*240)、QCIF(176*144)和 QQVGA(160*120)等。
  • PCLK,即像素时钟,一个 PCLK 时钟,输出一个像素(或半个像素)。
  • VSYNC,即帧同步信号。
  • HREF /HSYNC,即行同步信号。

我们的数据输出(通过 Y[9:0]),就是通过PCLK,VSYNC和HREF /HSYNC来实现的,接下来我们就来讲解一下OV2640的时序部分。

时序

图像数据在 HREF 为高的时候输出,当 HREF 变高后,每一个 PCLK 时钟,输出一个 8 位/10 位数据。我们采用 8 位接口,所以每个 PCLK 输出 1 个字节,且在 RGB/YUV 输出格式下,每个 tp=2 个 Tpclk,如果是 Raw 格式,则一个 tp=1 个 Tpclk。比如我们采用 UXGA 时序,RGB565 格式输出,每 2 个字节组成一个像素的颜色(高低字节顺序可通过 0XDA 寄存器设置),这样每行输出总共有 1600*2 个 PCLK 周期,输出 1600*2 个字节。

帧输出时序

我们以UXGA举例, 首先VSYNC发送一个高电平数据表示起始信号,然后在HREF为高电平期间采集有效数据,一共1600条,然后重复1200次,就达到了输出的效果。

窗口设置

传感器窗口设置,该功能允许用户设置整个传感器区域(1632*1220)的感兴趣部分,也就是在传感器里面开窗,开窗范围从 2*2~1632*1220 都可以设置,不过要求这个窗口必须大于等于随后设置的图像尺寸。传感器窗口设置,通过:0X03/0X19/0X1A/0X07/0X17/0X18 等寄存器设置,使用的函数是OV2640_Window_Set。

图像尺寸设置,也就是 DSP 输出(最终输出到 LCD 的)图像的最大尺寸,该尺寸要小于等于前面我们传感器窗口设置所设定的窗口尺寸。图像尺寸通过:0XC0/0XC1/0X8C 等寄存器设置。使用的函数是OV2640_ImageSize_Set。

图像窗口设置,这里起始和前面的传感器窗口设置类似,只是这个窗口是在我们前面设置的图像尺寸里面,再一次设置窗口大小,该窗口必须小于等于前面设置的图像尺寸。该窗口设置后的图像范围,将用于输出到外部。图像窗口设置通过:0X51/0X52/0X53/0X54/0X55/0X57等寄存器设置。使用的函数是OV2640_ImageWin_Set。

图像输出大小设置,这是最终输出到外部的图像尺寸。该设置将图像窗口设置所决定的窗口大小,通过内部 DSP 处理,缩放成我们输出到外部的图像大小。该设置将会对图像进行缩放处理,如果设置的图像输出大小不等于图像窗口设置图像大小,那么图像就会被缩放处理,只有这两者设置一样大的时候,输出比例才是 1:1 的。使用的函数是OV2640_OutSize_Set。

整体效果如下所示

配置

初始化

这是初始化的流程,首先初始化好IO口,然后上电复位,读取ID,执行初始化序列,这个是OV2640厂家做好的,我们就不需要重新写过了。

读取图像数据

DCMI

STM32F4 自带了一个数字摄像头(DCMI)接口,该接口是一个同步并行接口,能够接收外部 8 位、10 位、12 位或 14 位 CMOS 摄像头模块发出的高速数据流。可支持不同的数据格式:YCbCr4:2:2/RGB565 逐行视频和压缩数据 (JPEG)。

DCMI 接口是一个同步并行接口,可接收高速(可达 54 MB/s)数据流。该接口包含多达14 条数据线(D13-D0)和一条像素时钟线(PIXCLK)。像素时钟的极性可以编程,因此可以在像素时钟的上升沿或下降沿捕获数据。

特点

  • 8 位、10 位、12 位或 14 位并行接口
  • 内嵌码/外部行同步和帧同步
  • 连续模式或快照模式
  • 裁剪功能
  • 支持以下数据格式:
    • 8/10/12/14 位逐行视频:单色或原始拜尔(Bayer)格式
    • YCbCr 4:2:2 逐行视频
    • RGB 565 逐行视频
    • 压缩数据:JPEG

信号

  1. 数据输入(D[0:13]),用于接摄像头的数据输出,接 OV2640 我们只用了 8 位数据。
  2. 水平同步(行同步)输入(HSYNC),用于接摄像头的 HSYNC/HREF 信号。
  3. 垂直同步(场同步)输入(VSYNC),用于接摄像头的 VSYNC 信号。
  4. 像素时钟输入(PIXCLK),用于接摄像头的 PCLK 信号。

DCMI 接口的数据与 PIXCLK(即 PCLK)保持同步,并根据像素时钟的极性在像素时钟上升沿/下降沿发生变化。HSYNC(HREF)信号指示行的开始/结束,VSYNC 信号指示帧的开始/结束。DCMI 信号波形如图所示:

上图中,对应设置为:DCMI_PIXCLK 的捕获沿为下降沿,DCMI_HSYNC 和 DCMI_VSYNC 的有效状态为 1,注意,这里的有效状态实际上对应的是指示数据在并行接口上无效时, HSYNC/VSYNC 引脚上面的引脚电平。

我们用到 DCMI 的 8 位数据宽度,通过设置 DCMI_CR 中的 EDM[1:0]=00 设置。此时 DCMI_D0~D7 有效,DCMI_D8~D13 上的数据则忽略,这个时候,每次需要 4 个像素时钟来捕获一个 32 位数据。捕获的第一个数据存放在 32 位字的 LSB 位置,第四个数据存放在 32 位字的 MSB 位置 ,捕获数据字节在 32 位字中的排布如表所示:

DMA

DCMI 接口支持 DMA 传输,当 DCMI_CR 寄存器中的 CAPTURE 位置 1 时,激活 DMA 接口。摄像头接口每次在其寄存器中收到一个完整的 32 位数据块时,都将触发一个 DMA请求。

寄存器

寄存器就不详细介绍了。

配置

  1. 配置 OV2640 控制引脚,并配置 OV2640 工作模式。
  2. 配置相关引脚的模式和复用功能(AF13),使能时钟。
  3. 配置 DCMI 相关设置。
  4. 配置 DMA。
  5. 设置 OV2640 的图像输出大小,使能 DCMI 捕获。

硬件连接

代码

//dcmi.c
#include "sys.h"
#include "dcmi.h"
#include "led.h"
#include "ov2640.h" u8 ov_frame=0;                          //帧率
extern void jpeg_data_process(void);    //JPEG数据处理函数DCMI_InitTypeDef DCMI_InitStructure;//DCMI中断服务函数
void DCMI_IRQHandler(void)
{if(DCMI_GetITStatus(DCMI_IT_FRAME)==SET)//捕获到一帧图像{jpeg_data_process();     //jpeg数据处理  DCMI_ClearITPendingBit(DCMI_IT_FRAME);//清除帧中断LED1=!LED1;ov_frame++;  }
}
//DCMI DMA配置
//DMA_Memory0BaseAddr:存储器地址    将要存储摄像头数据的内存地址(也可以是外设地址)
//DMA_BufferSize:存储器长度    0~65535
//DMA_MemoryDataSize:存储器位宽
//DMA_MemoryDataSize:存储器位宽    @defgroup DMA_memory_data_size :DMA_MemoryDataSize_Byte/DMA_MemoryDataSize_HalfWord/DMA_MemoryDataSize_Word
//DMA_MemoryInc:存储器增长方式  @defgroup DMA_memory_incremented_mode  /** @defgroup DMA_memory_incremented_mode : DMA_MemoryInc_Enable/DMA_MemoryInc_Disable
void DCMI_DMA_Init(u32 DMA_Memory0BaseAddr,u16 DMA_BufferSize,u32 DMA_MemoryDataSize,u32 DMA_MemoryInc)
{ DMA_InitTypeDef  DMA_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能 DMA_DeInit(DMA2_Stream1);while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE){}//等待DMA2_Stream1可配置 /* 配置 DMA Stream */DMA_InitStructure.DMA_Channel = DMA_Channel_1;  //通道1 DCMI通道 DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&DCMI->DR;//外设地址为:DCMI->DRDMA_InitStructure.DMA_Memory0BaseAddr = DMA_Memory0BaseAddr;//DMA 存储器0地址DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//外设到存储器模式DMA_InitStructure.DMA_BufferSize = DMA_BufferSize;//数据传输量 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc;//存储器增量模式DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;//外设数据长度:32位DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize;//存储器数据长度 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用循环模式 DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; //FIFO模式        DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;//使用全FIFO DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//外设突发单次传输DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//存储器突发单次传输DMA_Init(DMA2_Stream1, &DMA_InitStructure);//初始化DMA Stream}
//DCMI初始化
void My_DCMI_Init(void)
{GPIO_InitTypeDef  GPIO_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOE, ENABLE);//使能GPIOA B C E 时钟RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_DCMI,ENABLE);//使能DCMI时钟//PA4/6初始化设置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_6;//PA4/6   复用功能输出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能输出GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHzGPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_6;// PB6/7   复用功能输出GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_11;//PC6/7/8/9/11 复用功能输出GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_6;//PE5/6  复用功能输出 GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化 GPIO_PinAFConfig(GPIOA,GPIO_PinSource4,GPIO_AF_DCMI); //PA4,AF13  DCMI_HSYNCGPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_DCMI); //PA6,AF13  DCMI_PCLK  GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_DCMI); //PB7,AF13  DCMI_VSYNC GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_DCMI); //PC6,AF13  DCMI_D0  GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_DCMI); //PC7,AF13  DCMI_D1 GPIO_PinAFConfig(GPIOC,GPIO_PinSource8,GPIO_AF_DCMI); //PC8,AF13  DCMI_D2GPIO_PinAFConfig(GPIOC,GPIO_PinSource9,GPIO_AF_DCMI); //PC9,AF13  DCMI_D3GPIO_PinAFConfig(GPIOC,GPIO_PinSource11,GPIO_AF_DCMI);//PC11,AF13 DCMI_D4 GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_DCMI); //PB6,AF13  DCMI_D5 GPIO_PinAFConfig(GPIOE,GPIO_PinSource5,GPIO_AF_DCMI); //PE5,AF13  DCMI_D6GPIO_PinAFConfig(GPIOE,GPIO_PinSource6,GPIO_AF_DCMI); //PE6,AF13  DCMI_D7DCMI_DeInit();//清除原来的设置 DCMI_InitStructure.DCMI_CaptureMode=DCMI_CaptureMode_Continuous;//连续模式DCMI_InitStructure.DCMI_CaptureRate=DCMI_CaptureRate_All_Frame;//全帧捕获DCMI_InitStructure.DCMI_ExtendedDataMode= DCMI_ExtendedDataMode_8b;//8位数据格式  DCMI_InitStructure.DCMI_HSPolarity= DCMI_HSPolarity_Low;//HSYNC 低电平有效DCMI_InitStructure.DCMI_PCKPolarity= DCMI_PCKPolarity_Rising;//PCLK 上升沿有效DCMI_InitStructure.DCMI_SynchroMode= DCMI_SynchroMode_Hardware;//硬件同步HSYNC,VSYNCDCMI_InitStructure.DCMI_VSPolarity=DCMI_VSPolarity_Low;//VSYNC 低电平有效DCMI_Init(&DCMI_InitStructure);DCMI_ITConfig(DCMI_IT_FRAME,ENABLE);//开启帧中断 DCMI_Cmd(ENABLE);   //DCMI使能NVIC_InitStructure.NVIC_IRQChannel = DCMI_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//抢占优先级1NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;     //子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;          //IRQ通道使能NVIC_Init(&NVIC_InitStructure);    //根据指定的参数初始化VIC寄存器、}
//DCMI,启动传输
void DCMI_Start(void)
{  DMA_Cmd(DMA2_Stream1, ENABLE);//开启DMA2,Stream1 DCMI_CaptureCmd(ENABLE);//DCMI捕获使能
}
//DCMI,关闭传输
void DCMI_Stop(void)
{ DCMI_CaptureCmd(DISABLE);//DCMI捕获使关闭  while(DCMI->CR&0X01);        //等待传输结束 DMA_Cmd(DMA2_Stream1,DISABLE);//关闭DMA2,Stream1
} //以下两个函数,供usmart调用,用于调试代码//DCMI设置显示窗口
//sx,sy;LCD的起始坐标
//width,height:LCD显示范围.
void DCMI_Set_Window(u16 sx,u16 sy,u16 width,u16 height)
{DCMI_Stop();
//  LCD_Clear(WHITE);
//  LCD_Set_Window(sx,sy,width,height);OV2640_OutSize_Set(width,height);
//  LCD_SetCursor(0,0);
//  LCD_WriteRAM_Prepare();     //开始写入GRAMDMA_Cmd(DMA2_Stream1,ENABLE); //开启DMA2,Stream1 DCMI_CaptureCmd(ENABLE);//DCMI捕获使能 }//通过usmart调试,辅助测试用.
//pclk/hsync/vsync:三个信号的有限电平设置
void DCMI_CR_Set(u8 pclk,u8 hsync,u8 vsync)
{DCMI_DeInit();//清除原来的设置 DCMI_InitStructure.DCMI_CaptureMode=DCMI_CaptureMode_Continuous;//连续模式DCMI_InitStructure.DCMI_CaptureRate=DCMI_CaptureRate_All_Frame;//全帧捕获DCMI_InitStructure.DCMI_ExtendedDataMode= DCMI_ExtendedDataMode_8b;//8位数据格式  DCMI_InitStructure.DCMI_HSPolarity= hsync<<6;//HSYNC 低电平有效DCMI_InitStructure.DCMI_PCKPolarity= pclk<<5;//PCLK 上升沿有效DCMI_InitStructure.DCMI_SynchroMode= DCMI_SynchroMode_Hardware;//硬件同步HSYNC,VSYNCDCMI_InitStructure.DCMI_VSPolarity=vsync<<7;//VSYNC 低电平有效DCMI_Init(&DCMI_InitStructure);DCMI_CaptureCmd(ENABLE);//DCMI捕获使能 DCMI_Cmd(ENABLE);  //DCMI使能}
// sccb.c
#include "sys.h"
#include "sccb.h"
#include "delay.h"//初始化SCCB接口
void SCCB_Init(void)
{               GPIO_InitTypeDef  GPIO_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);//使能GPIOD时钟//GPIOF9,F10初始化设置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;//PD6,7 推挽输出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;  //PD6,7 推挽输出GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//100MHzGPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉GPIO_Init(GPIOD, &GPIO_InitStructure);//初始化GPIO_SetBits(GPIOD,GPIO_Pin_6|GPIO_Pin_7);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;
}
//ov2640.c
#include "sys.h"
#include "ov2640.h"
#include "ov2640cfg.h"
#include "timer.h"
#include "delay.h"
#include "usart.h"
#include "sccb.h"   //初始化OV2640
//配置完以后,默认输出是1600*1200尺寸的图片!!
//返回值:0,成功
//    其他,错误代码
u8 OV2640_Init(void)
{ u16 i=0;u16 reg;//设置IO           GPIO_InitTypeDef  GPIO_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);//GPIOG9,15初始化设置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_15;//PG9,15推挽输出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //推挽输出GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//100MHzGPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化OV2640_PWDN=0;    //POWER ONdelay_ms(10);OV2640_RST=0;    //复位OV2640delay_ms(10);OV2640_RST=1;    //结束复位 SCCB_Init();             //初始化SCCB 的IO口   SCCB_WR_Reg(OV2640_DSP_RA_DLMT, 0x01); //操作sensor寄存器SCCB_WR_Reg(OV2640_SENSOR_COM7, 0x80); //软复位OV2640delay_ms(50); reg=SCCB_RD_Reg(OV2640_SENSOR_MIDH);   //读取厂家ID 高八位reg<<=8;reg|=SCCB_RD_Reg(OV2640_SENSOR_MIDL); //读取厂家ID 低八位if(reg!=OV2640_MID){printf("MID:%d\r\n",reg);return 1;}reg=SCCB_RD_Reg(OV2640_SENSOR_PIDH); //读取厂家ID 高八位reg<<=8;reg|=SCCB_RD_Reg(OV2640_SENSOR_PIDL); //读取厂家ID 低八位if(reg!=OV2640_PID){printf("HID:%d\r\n",reg);return 2;}   //初始化 OV2640,采用UXGA分辨率(1600*1200)  for(i=0;i<sizeof(ov2640_uxga_init_reg_tbl)/2;i++){SCCB_WR_Reg(ov2640_uxga_init_reg_tbl[i][0],ov2640_uxga_init_reg_tbl[i][1]);} return 0x00;     //ok
}
//OV2640切换为JPEG模式
void OV2640_JPEG_Mode(void)
{u16 i=0;//设置:YUV422格式for(i=0;i<(sizeof(ov2640_yuv422_reg_tbl)/2);i++){SCCB_WR_Reg(ov2640_yuv422_reg_tbl[i][0],ov2640_yuv422_reg_tbl[i][1]); } //设置:输出JPEG数据for(i=0;i<(sizeof(ov2640_jpeg_reg_tbl)/2);i++){SCCB_WR_Reg(ov2640_jpeg_reg_tbl[i][0],ov2640_jpeg_reg_tbl[i][1]);  }
}
//OV2640切换为RGB565模式
void OV2640_RGB565_Mode(void)
{u16 i=0;//设置:RGB565输出for(i=0;i<(sizeof(ov2640_rgb565_reg_tbl)/2);i++){SCCB_WR_Reg(ov2640_rgb565_reg_tbl[i][0],ov2640_rgb565_reg_tbl[i][1]); }
}
//自动曝光设置参数表,支持5个等级
const static u8 OV2640_AUTOEXPOSURE_LEVEL[5][8]=
{{0xFF,0x01,0x24,0x20,0x25,0x18,0x26,0x60,},{0xFF,0x01,0x24,0x34,0x25,0x1c,0x26,0x00,},{0xFF,0x01,  0x24,0x3e,  0x25,0x38,0x26,0x81,},{0xFF,0x01,0x24,0x48,0x25,0x40,0x26,0x81,},{0xFF,0x01,    0x24,0x58,  0x25,0x50,  0x26,0x92,  },
};
//OV2640自动曝光等级设置
//level:0~4
void OV2640_Auto_Exposure(u8 level)
{  u8 i;u8 *p=(u8*)OV2640_AUTOEXPOSURE_LEVEL[level];for(i=0;i<4;i++){ SCCB_WR_Reg(p[i*2],p[i*2+1]); }
}
//白平衡设置
//0:自动
//1:太阳sunny
//2,阴天cloudy
//3,办公室office
//4,家里home
void OV2640_Light_Mode(u8 mode)
{u8 regccval=0X5E;//Sunny u8 regcdval=0X41;u8 regceval=0X54;switch(mode){ case 0://auto SCCB_WR_Reg(0XFF,0X00);  SCCB_WR_Reg(0XC7,0X10);//AWB ON return;    case 2://cloudyregccval=0X65;regcdval=0X41;regceval=0X4F;break; case 3://officeregccval=0X52;regcdval=0X41;regceval=0X66;break; case 4://homeregccval=0X42;regcdval=0X3F;regceval=0X71;break;   }SCCB_WR_Reg(0XFF,0X00);     SCCB_WR_Reg(0XC7,0X40);    //AWB OFF SCCB_WR_Reg(0XCC,regccval); SCCB_WR_Reg(0XCD,regcdval); SCCB_WR_Reg(0XCE,regceval);
}
//色度设置
//0:-2
//1:-1
//2,0
//3,+1
//4,+2
void OV2640_Color_Saturation(u8 sat)
{ u8 reg7dval=((sat+2)<<4)|0X08;SCCB_WR_Reg(0XFF,0X00);       SCCB_WR_Reg(0X7C,0X00);     SCCB_WR_Reg(0X7D,0X02);             SCCB_WR_Reg(0X7C,0X03);         SCCB_WR_Reg(0X7D,reg7dval);         SCCB_WR_Reg(0X7D,reg7dval);
}
//亮度设置
//0:(0X00)-2
//1:(0X10)-1
//2,(0X20) 0
//3,(0X30)+1
//4,(0X40)+2
void OV2640_Brightness(u8 bright)
{SCCB_WR_Reg(0xff, 0x00);SCCB_WR_Reg(0x7c, 0x00);SCCB_WR_Reg(0x7d, 0x04);SCCB_WR_Reg(0x7c, 0x09);SCCB_WR_Reg(0x7d, bright<<4); SCCB_WR_Reg(0x7d, 0x00);
}
//对比度设置
//0:-2
//1:-1
//2,0
//3,+1
//4,+2
void OV2640_Contrast(u8 contrast)
{u8 reg7d0val=0X20;//默认为普通模式u8 reg7d1val=0X20;switch(contrast){case 0://-2reg7d0val=0X18;        reg7d1val=0X34;         break; case 1://-1reg7d0val=0X1C;       reg7d1val=0X2A;         break; case 3://1reg7d0val=0X24;        reg7d1val=0X16;         break; case 4://2reg7d0val=0X28;        reg7d1val=0X0C;         break; }SCCB_WR_Reg(0xff,0x00);SCCB_WR_Reg(0x7c,0x00);SCCB_WR_Reg(0x7d,0x04);SCCB_WR_Reg(0x7c,0x07);SCCB_WR_Reg(0x7d,0x20);SCCB_WR_Reg(0x7d,reg7d0val);SCCB_WR_Reg(0x7d,reg7d1val);SCCB_WR_Reg(0x7d,0x06);
}
//特效设置
//0:普通模式
//1,负片
//2,黑白
//3,偏红色
//4,偏绿色
//5,偏蓝色
//6,复古
void OV2640_Special_Effects(u8 eft)
{u8 reg7d0val=0X00;//默认为普通模式u8 reg7d1val=0X80;u8 reg7d2val=0X80; switch(eft){case 1://负片reg7d0val=0X40; break;  case 2://黑白reg7d0val=0X18; break;    case 3://偏红色reg7d0val=0X18; reg7d1val=0X40;reg7d2val=0XC0; break;  case 4://偏绿色reg7d0val=0X18; reg7d1val=0X40;reg7d2val=0X40; break;   case 5://偏蓝色reg7d0val=0X18; reg7d1val=0XA0;reg7d2val=0X40; break;   case 6://复古reg7d0val=0X18; reg7d1val=0X40;reg7d2val=0XA6; break;     }SCCB_WR_Reg(0xff,0x00);SCCB_WR_Reg(0x7c,0x00);SCCB_WR_Reg(0x7d,reg7d0val);SCCB_WR_Reg(0x7c,0x05);SCCB_WR_Reg(0x7d,reg7d1val);SCCB_WR_Reg(0x7d,reg7d2val);
}
//彩条测试
//sw:0,关闭彩条
//   1,开启彩条(注意OV2640的彩条是叠加在图像上面的)
void OV2640_Color_Bar(u8 sw)
{u8 reg;SCCB_WR_Reg(0XFF,0X01);reg=SCCB_RD_Reg(0X12);reg&=~(1<<1);if(sw)reg|=1<<1; SCCB_WR_Reg(0X12,reg);
}
//设置图像输出窗口
//sx,sy,起始地址
//width,height:宽度(对应:horizontal)和高度(对应:vertical)
void OV2640_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;SCCB_WR_Reg(0XFF,0X01);           temp=SCCB_RD_Reg(0X03);             //读取Vref之前的值temp&=0XF0;temp|=((endy&0X03)<<2)|(sy&0X03);SCCB_WR_Reg(0X03,temp);               //设置Vref的start和end的最低2位SCCB_WR_Reg(0X19,sy>>2);           //设置Vref的start高8位SCCB_WR_Reg(0X1A,endy>>2);           //设置Vref的end的高8位temp=SCCB_RD_Reg(0X32);             //读取Href之前的值temp&=0XC0;temp|=((endx&0X07)<<3)|(sx&0X07);SCCB_WR_Reg(0X32,temp);               //设置Href的start和end的最低3位SCCB_WR_Reg(0X17,sx>>3);           //设置Href的start高8位SCCB_WR_Reg(0X18,endx>>3);           //设置Href的end的高8位
}
//设置图像输出大小
//OV2640输出图像的大小(分辨率),完全由改函数确定
//width,height:宽度(对应:horizontal)和高度(对应:vertical),width和height必须是4的倍数
//返回值:0,设置成功
//    其他,设置失败
u8 OV2640_OutSize_Set(u16 width,u16 height)
{u16 outh;u16 outw;u8 temp; if(width%4)return 1;if(height%4)return 2;outw=width/4;outh=height/4; SCCB_WR_Reg(0XFF,0X00);    SCCB_WR_Reg(0XE0,0X04);         SCCB_WR_Reg(0X5A,outw&0XFF);        //设置OUTW的低八位SCCB_WR_Reg(0X5B,outh&0XFF);        //设置OUTH的低八位temp=(outw>>8)&0X03;temp|=(outh>>6)&0X04;SCCB_WR_Reg(0X5C,temp);                //设置OUTH/OUTW的高位 SCCB_WR_Reg(0XE0,0X00);    return 0;
}
//设置图像开窗大小
//由:OV2640_ImageSize_Set确定传感器输出分辨率从大小.
//该函数则在这个范围上面进行开窗,用于OV2640_OutSize_Set的输出
//注意:本函数的宽度和高度,必须大于等于OV2640_OutSize_Set函数的宽度和高度
//     OV2640_OutSize_Set设置的宽度和高度,根据本函数设置的宽度和高度,由DSP
//     自动计算缩放比例,输出给外部设备.
//width,height:宽度(对应:horizontal)和高度(对应:vertical),width和height必须是4的倍数
//返回值:0,设置成功
//    其他,设置失败
u8 OV2640_ImageWin_Set(u16 offx,u16 offy,u16 width,u16 height)
{u16 hsize;u16 vsize;u8 temp; if(width%4)return 1;if(height%4)return 2;hsize=width/4;vsize=height/4;SCCB_WR_Reg(0XFF,0X00); SCCB_WR_Reg(0XE0,0X04);                 SCCB_WR_Reg(0X51,hsize&0XFF);       //设置H_SIZE的低八位SCCB_WR_Reg(0X52,vsize&0XFF);     //设置V_SIZE的低八位SCCB_WR_Reg(0X53,offx&0XFF);      //设置offx的低八位SCCB_WR_Reg(0X54,offy&0XFF);        //设置offy的低八位temp=(vsize>>1)&0X80;temp|=(offy>>4)&0X70;temp|=(hsize>>5)&0X08;temp|=(offx>>8)&0X07; SCCB_WR_Reg(0X55,temp);               //设置H_SIZE/V_SIZE/OFFX,OFFY的高位SCCB_WR_Reg(0X57,(hsize>>2)&0X80);  //设置H_SIZE/V_SIZE/OFFX,OFFY的高位SCCB_WR_Reg(0XE0,0X00);   return 0;
}
//该函数设置图像尺寸大小,也就是所选格式的输出分辨率
//UXGA:1600*1200,SVGA:800*600,CIF:352*288
//width,height:图像宽度和图像高度
//返回值:0,设置成功
//    其他,设置失败
u8 OV2640_ImageSize_Set(u16 width,u16 height)
{ u8 temp; SCCB_WR_Reg(0XFF,0X00);          SCCB_WR_Reg(0XE0,0X04);         SCCB_WR_Reg(0XC0,(width)>>3&0XFF);        //设置HSIZE的10:3位SCCB_WR_Reg(0XC1,(height)>>3&0XFF);        //设置VSIZE的10:3位temp=(width&0X07)<<3;temp|=height&0X07;temp|=(width>>4)&0X80; SCCB_WR_Reg(0X8C,temp);    SCCB_WR_Reg(0XE0,0X00);              return 0;
}
// main.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "usmart.h"
#include "usart2.h"
#include "timer.h"
#include "ov2640.h"
#include "dcmi.h" u8 ov2640_mode=0;                     //工作模式:0,RGB565模式;1,JPEG模式#define jpeg_buf_size 31*1024             //定义JPEG数据缓存jpeg_buf的大小(*4字节)
__align(4) u32 jpeg_buf[jpeg_buf_size]; //JPEG数据缓存buf
volatile u32 jpeg_data_len=0;           //buf中的JPEG有效数据长度
volatile u8 jpeg_data_ok=0;             //JPEG数据采集完成标志 //0,数据没有采集完;//1,数据采集完了,但是还没处理;//2,数据已经处理完成了,可以开始下一帧接收
//JPEG尺寸支持列表
const u16 jpeg_img_size_tbl[][2]=
{176,144,   //QCIF160,120,  //QQVGA352,288, //CIF320,240,   //QVGA640,480,  //VGA800,600,   //SVGA1024,768, //XGA1280,1024, //SXGA1600,1200,    //UXGA
};
const u8*EFFECTS_TBL[7]={"Normal","Negative","B&W","Redish","Greenish","Bluish","Antique"}; //7种特效
const u8*JPEG_SIZE_TBL[9]={"QCIF","QQVGA","CIF","QVGA","VGA","SVGA","XGA","SXGA","UXGA"};   //JPEG图片 9种尺寸 //处理JPEG数据
//当采集完一帧JPEG数据后,调用此函数,切换JPEG BUF.开始下一帧采集.
void jpeg_data_process(void)
{if(ov2640_mode)//只有在JPEG格式下,才需要做处理.{if(jpeg_data_ok==0)    //jpeg数据还未采集完?{ DMA_Cmd(DMA2_Stream1, DISABLE);//停止当前传输 while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE){}//等待DMA2_Stream1可配置  jpeg_data_len=jpeg_buf_size-DMA_GetCurrDataCounter(DMA2_Stream1);//得到此次数据传输的长度jpeg_data_ok=1;               //标记JPEG数据采集完按成,等待其他函数处理}if(jpeg_data_ok==2)    //上一次的jpeg数据已经被处理了{DMA2_Stream1->NDTR=jpeg_buf_size; DMA_SetCurrDataCounter(DMA2_Stream1,jpeg_buf_size);//传输长度为jpeg_buf_size*4字节DMA_Cmd(DMA2_Stream1, ENABLE);           //重新传输jpeg_data_ok=0;                       //标记数据未采集}}
}
//JPEG测试
//JPEG数据,通过串口2发送给电脑.
void jpeg_test(void)
{u32 i; u8 *p;u8 key;u8 effect=0,saturation=2,contrast=2;u8 size=8;     //默认是UXGAu8 msgbuf[15]; //消息缓存区 printf ("OV2640 JPEG Mode");printf ("\r\n\r\n");printf("KEY0:Contrast");            //对比度printf ("\r\n\r\n");printf("KEY1:Saturation");         //色彩饱和度printf ("\r\n\r\n");printf("KEY2:Effects");          //特效 printf ("\r\n\r\n");printf("KEY_UP:Size");             //分辨率设置 printf ("\r\n\r\n");sprintf((char*)msgbuf,"JPEG Size:%s",JPEG_SIZE_TBL[size]);printf ("%d",msgbuf);                 //显示当前JPEG分辨率printf ("\r\n\r\n");OV2640_JPEG_Mode();        //JPEG模式My_DCMI_Init();         //DCMI配置DCMI_DMA_Init((u32)&jpeg_buf,jpeg_buf_size,DMA_MemoryDataSize_Word,DMA_MemoryInc_Enable);//DCMI DMA配置   OV2640_OutSize_Set(jpeg_img_size_tbl[size][0],jpeg_img_size_tbl[size][1]);//设置输出尺寸 DCMI_Start();        //启动传输while(1){if(jpeg_data_ok==1)  //已经采集完一帧图像了{  p=(u8*)jpeg_buf;printf ("Sending JPEG data..."); //提示正在传输数据printf ("\r\n\r\n");for(i=0;i<jpeg_data_len*4;i++)     //dma传输1次等于4字节,所以乘以4.{while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);  //循环发送,直到发送完毕       USART_SendData(USART2,p[i]); key=KEY_Scan(0); if(key)break;} if(key)    //有按键按下,需要处理{  printf ("Quit Sending data   ");//提示退出数据传输switch(key){                   case KEY0_PRES: //对比度设置contrast++;if(contrast>4)contrast=0;OV2640_Contrast(contrast);sprintf((char*)msgbuf,"Contrast:%d",(signed char)contrast-2);printf ("\r\n\r\n");break;case KEY1_PRES:  //饱和度Saturationsaturation++;if(saturation>4)saturation=0;OV2640_Color_Saturation(saturation);sprintf((char*)msgbuf,"Saturation:%d",(signed char)saturation-2);printf ("\r\n\r\n");break;case KEY2_PRES:  //特效设置               effect++;if(effect>6)effect=0;OV2640_Special_Effects(effect);//设置特效sprintf((char*)msgbuf,"%s",EFFECTS_TBL[effect]);printf ("\r\n\r\n");break;case WKUP_PRES:    //JPEG输出尺寸设置   size++;  if(size>8)size=0;   OV2640_OutSize_Set(jpeg_img_size_tbl[size][0],jpeg_img_size_tbl[size][1]);//设置输出尺寸  sprintf((char*)msgbuf,"JPEG Size:%s",JPEG_SIZE_TBL[size]);printf ("\r\n\r\n");break;}printf ("%d",msgbuf);//显示提示内容printf ("\r\n\r\n");delay_ms(800);                   }else printf ("Send data complete!!");//提示传输结束设置 jpeg_data_ok=2;  //标记jpeg数据处理完了,可以让DMA去采集下一帧了.}      }
} int main(void)
{ u8 key;u8 t;NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2delay_init(168);  //初始化延时函数uart_init(115200);        //初始化串口波特率为115200usart2_init(42,115200);        //初始化串口2波特率为115200LED_Init();                   //初始化LED  KEY_Init();                   //按键初始化 TIM3_Int_Init(10000-1,8400-1);//10Khz计数,1秒钟中断一次usmart_dev.init(84);     //初始化USMART      while(OV2640_Init())//初始化OV2640{printf ("OV2640 ERR");printf ("\r\n\r\n");delay_ms(200);}printf("OV2640 OK");
printf ("\r\n\r\n");    printf ("KEY0:RGB565  KEY1:JPEG");  printf ("\r\n\r\n");while(1){   key=KEY_Scan(0);if(key==KEY0_PRES)          //RGB565模式{ov2640_mode=0;   break;}else if(key==KEY1_PRES)  //JPEG模式{ov2640_mode=1;break;}t++;                                    delay_ms(5);    }if(ov2640_mode)jpeg_test();
//  else rgb565_test();
}

总结

OV2640摄像头的知识点比较的多,而且目前只有显示屏,所以不能在电脑端显示图像,后期串口线到了之后再补上。

(40)STM32——OV2640摄像头实验相关推荐

  1. STM32——OV2640摄像头实验

    一.硬件连接原理图 实物图: 二.OV2640驱动代码 1.SCCB_Init函数 //初始化SCCB接口 void SCCB_Init(void) {GPIO_InitTypeDef GPIO_In ...

  2. STM32摄像头实验相关源码分享[一、颜色识别]

    要每天养成写博客的习惯,可是,苦于最近学习没有进展啊. 无奈把自己去年学习STM32以及做相关项目时的代码发出来水水博客吧! 本代码在原子哥摄像头实验基础上做了相关更改. color.c部分[用于设定 ...

  3. STM32 F4 OV2640摄像头学习笔记(一)

    //尝试写作,记录学习过程. 一.OV2640摄像头简介 本节将主要讲解OV2640摄像头基础部分和一些引脚配置. OV2640传感器简介 OV2640是Omni Vision公司生产的一颗1/4寸的 ...

  4. 【STM32】OV2640摄像头学习笔记 转

    [STM32]OV2640摄像头学习笔记 2019年03月03日 13:01:35 淹死的大白鲨 阅读数 4736更多 分类专栏: [STM32] 版权声明:本文为博主原创文章,遵循 CC 4.0 B ...

  5. STM32摄像头实验

    探索者 STM32F4 开发板的摄像头接口与 ALIENTEK OV2640 摄像头模块 的连接.在开发板的左下角的 2*9 的 P8 排座,是摄像头模块/OLED 模块共用接口, 特别注意:DCMI ...

  6. 基于STM32F407摄像头实验(有代码)

    1.OV2640 简介 OV2640 是 OV(OmniVision)公司生产的一颗 1/4 寸的 CMOS UXGA(16321232)图像 传感器.该传感器体积小.工作电压低,提供单片 UXGA ...

  7. STM32F407获取OV2640摄像头图像及上位机解码(一维码二维码)

    STM32F407获取OV2640摄像头图像及上位机解码(一维码&二维码) 1. 目的 针对静止拍摄图像场景,实现STM32F407对200万像素OV2640摄像头进行图像捕获,并通过串口将数 ...

  8. STM32H750获取OV2640摄像头图像及上位机解码(一维码二维码)

    STM32H750获取OV2640摄像头图像及上位机解码(一维码&二维码) 1. 目的 针对静止拍摄图像场景,实现STM32H750对200万像素OV2640摄像头进行图像捕获,并通过串口将数 ...

  9. STM32 OV7725摄像头模块的颜色处理和简单物体识别(串口输出图片)

    目录 前言 一.摄像头采集数据流程 二.如何将图像显示到电脑上 三.图像二值化 1.什么是RGB? 2.RGB565转RGB888 I.RGB565和RGB888的区别 II.代码 3.RGB转HSL ...

最新文章

  1. Android 开发中的多线程编程技术
  2. android 减少布局层级,Android 布局优化
  3. Opencv判断是否加载图片的两种方法
  4. python爬虫天气预报_Python爬虫实例扒取2345天气预报
  5. Java:数列排序 给定一个长度为n的数列,将这个数列按从小到大的顺序排列。1<=n<=200
  6. 1452.接水问题(思维)
  7. SpringSecurity分布式整合之认证服务配置文件编写和测试
  8. 自己遇到oracle的错误记录
  9. C#实现软键盘的几个关键技术实现方法
  10. iphone最新款手机_苹果罕见“跌停”!遭遇6年来最惨淡一夜,2019年全球股市第一颗雷引爆...
  11. am82.top 1.php,Droppy v2.1.3 – PHP在线网盘系统
  12. java socket 浏览器_Socket实现Java和浏览器交互。
  13. 学习python课程_想学习Python吗? 这是我们的免费4小时互动课程
  14. java编码native2ascii下载_使用native2ascii 中文字符与Unicode编码相互转换
  15. 基于智能电网的电力线载波通信研究
  16. Python查询快递订单信息
  17. 初创企业选择阿里云服务器与传统自建服务器的对比与选择
  18. React源码解毒 - 检测开发者是否错误的使用了props属性
  19. 电脑白屏或黑屏解决方法
  20. 发现了一个好用的WEB项目打印控件--四方打印

热门文章

  1. 不能不知道的分布式基础理论
  2. 分布式计算原理之分布式协调与同步(1)——分布式互斥
  3. 谷歌提出Flan-T5,一个模型解决所有NLP任务
  4. 使用printf语句输出名言:“贵有恒,何必三更起五更睡:最无益,只怕一日曝十日寒。“
  5. Android 客户端与服务器端时间校准
  6. 微信H5公众号chooseImg上传图片
  7. 三菱PLC FB库新建和调用-(Gx Work2版本)
  8. Nginx设置图片防盗链(白名单与黑名单)
  9. git恢复某个文件到上一个提交版本
  10. 别了欧阳夏丹、马斌!