【正点原子STM32连载】第五十三章 照相机实验摘自【正点原子】STM32F103 战舰开发指南V1.2

news2024/9/19 0:58:52

1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html#

第五十三章 照相机实验

上一章,我们学习了图片解码,本章我们将学习BMP编码,结合前面的摄像头实验,实现一个简单的照相机功能。本章分为如下几个部分:
53.1 BMP编码简介
53.2 硬件设计
53.3 软件设计
53.4 下载验证

53.1 BMP编码简介

前面的章节中,我们学习了各种图片格式的解码。本章,我们介绍最简单的图片编码方法:BMP图片编码。通过前面的了解,我们知道BMP文件是由文件头、位图信息头、颜色信息和图形数据等四部分组成。我们先来了解下这几个部分。
1、BMP文件头(14字节):BMP文件头数据结构含有BMP文件的类型、文件大小和位图起始位置等信息。
这里的__PACKED_STRUCT是强制对齐,这里是把结构体中间的留白空间移除。默认定义的变量是按CUP字长(STM32为32位)对齐的,这样可以增加程序的访问速度,但这样定义一个结构体时如果结构体成员并不全部按CUP的字长去定义,如有uint8_t,uint16_t时,编译器默认按照的长度将占用2个uint32_t类型的长度,而对于嵌入式产品尤其是内存紧张的产品,这样定义的结构体变量就会浪费内存空间。嵌入式的编译器支持通过强制对齐,可以优化结构体变量的空间,大家同样可以在MDK的帮助文件中查找__packed关键字去看这部分的知识点。我们这里用了在MDK下同时兼容AC5和AC6编译器的写法。BMP的文件头定义如下:

/* BMP头文件 */
typedef __PACKED_STRUCT
{
    uint16_t  bfType ;       	/* 文件标志.只对'BM',用来识别BMP位图类型 */
    uint32_t  bfSize ;       	/* 文件大小,占四个字节 */
    uint16_t  bfReserved1 ; 	/* 保留 */
    uint16_t  bfReserved2 ; 	/* 保留 */
    uint32_t  bfOffBits ;   	/* 从文件开始到位图数据(bitmap data)开始之间的的偏移量 */
}BITMAPFILEHEADER ;

2、位图信息头(40字节):BMP位图信息头数据用于说明位图的尺寸等信息。
/* BMP信息头 */

typedef __PACKED_STRUCT
{
    uint32_t biSize ;		/* 说明BITMAPINFOHEADER结构所需要的字数。 */
    long  biWidth ;         	/* 说明图象的宽度,以象素为单位 */
    long  biHeight ;        	/* 说明图象的高度,以象素为单位 */
    uint16_t  biPlanes ;    	/* 为目标设备说明位面数,其值将总是被设为1 */
    uint16_t  biBitCount ;	/* 说明比特数/象素,其值为1、4、8、16、24、或32 */
    uint32_t biCompression;	/* 说明图象数据压缩的类型。其值可以是下述值之一
                      			 * BI_RGB      :没有压缩
                              	 * BI_RLE8     :每个象素8比特的RLE压缩编码,压缩格式由
2字节组成(重复象素计数和颜色索引) 
                            	 * BI_RLE4     :每个象素4比特的RLE压缩编码,压缩格式由
2字节组成 
                            	 * BI_BITFIELDS:每个象素的比特由指定的掩码决定 
                                 */
    uint32_t biSizeImage ;	/*说明图象的大小,字节为单位。当用BI_RGB格式时,可设置为0*/
    long  biXPelsPerMeter ; /* 说明水平分辨率,用象素/米表示 */
    long  biYPelsPerMeter ; /* 说明垂直分辨率,用象素/米表示 */
uint32_t biClrUsed ;  	/* 说明位图实际使用的彩色表中的颜色索引数 */
/* 说明对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要 */
    uint32_t biClrImportant ;   
}BITMAPINFOHEADER ;

3、颜色表:颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD类型的结构,定义一种颜色。
/* 彩色表 */

typedef __PACKED_STRUCT
{
    uint8_t rgbBlue ;       		/* 指定蓝色强度 */
    uint8_t rgbGreen ;          	/* 指定绿色强度 */
    uint8_t rgbRed ;            	/* 指定红色强度 */
    uint8_t rgbReserved ;      	/* 保留,设置为0 */
}RGBQUAD ;

颜色表中RGBQUAD结构数据的个数由biBitCount来确定:当biBitCount=1、4、8时,分别有2、16、256个表项;当biBitCount大于8时,没有颜色表项。
BMP文件头、位图信息头和颜色表组成位图信息(我们将BMP文件头也加进来,方便处理),BITMAPINFO结构定义如下:
/* 位图信息头 */

typedef __PACKED_STRUCT

{ 
    BITMAPFILEHEADER bmfHeader;
    BITMAPINFOHEADER bmiHeader;  
    uint32_t RGB_MASK[3];       /* 调色板用于存放RGB掩码 */
    //RGBQUAD bmiColors[256];
}BITMAPINFO; 

4、位图数据:位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。位图的一个像素值所占的字节数:
当biBitCount=1时,8个像素占1个字节;
当biBitCount=4时,2个像素占1个字节;
当biBitCount=8时,1个像素占1个字节;
当biBitCount=16时,1个像素占2个字节;
当biBitCount=24时,1个像素占3个字节;
当biBitCount=32时,1个像素占4个字节;
biBitCount=1 表示位图最多有两种颜色,缺省情况下是黑色和白色,你也可以自己定义这两种颜色。图像信息头装调色板中将有两个调色板项,称为索引0和索引1。图象数据阵列中的每一位表示一个像素。如果一个位是0,显示时就使用索引0的RGB值,如果位是1,则使用索引1的RGB值。
biBitCount=16 表示位图最多有65536种颜色。每个像素用16位(2个字节)表示。这种格式叫作高彩色,或叫增强型16位色,或64K色。它的情况比较复杂,当biCompression成员的值是BI_RGB时,它没有调色板。16位中,最低的5位表示蓝色分量,中间的5位表示绿色分量,高的5位表示红色分量,一共占用了15位,最高的一位保留,设为0。这种格式也被称作555 16位位图。如果biCompression成员的值是BI_BITFIELDS,那么情况就复杂了,首先是原来调色板的位置被三个DWORD变量占据,称为红、绿、蓝掩码。分别用于描述红、绿、蓝分量在16位中所占的位置。在Windows 95(或98)中,系统可接受两种格式的位域:555和565,在555格式下,红、绿、蓝的掩码分别是:0x7C00、0x03E0、0x001F,而在565格式下,它们则分别为:0xF800、0x07E0、0x001F。你在读取一个像素之后,可以分别用掩码“与”上像素值,从而提取出想要的颜色分量(当然还要再经过适当的左右移操作)。在NT系统中,则没有格式限制,只不过要求掩码之间不能有重叠。(注:这种格式的图像使用起来是比较麻烦的,不过因为它的显示效果接近于真彩,而图像数据又比真彩图像小的多,所以,它更多的被用于游戏软件)。
biBitCount=32 表示位图最多有4294967296(2的32次方)种颜色。这种位图的结构与16位位图结构非常类似,当biCompression成员的值是BI_RGB时,它也没有调色板,32位中有24位用于存放RGB值,顺序是:最高位—保留,红8位、绿8位、蓝8位。这种格式也被成为888 32位图。如果 biCompression成员的值是BI_BITFIELDS时,原来调色板的位置将被三个DWORD变量占据,成为红、绿、蓝掩码,分别用于描述红、绿、蓝分量在32位中所占的位置。在Windows 95(or 98)中,系统只接受888格式,也就是说三个掩码的值将只能是:0xFF0000、0xFF00、0xFF。而在NT系统中,你只要注意使掩码之间不产生重叠就行。(注:这种图像格式比较规整,因为它是DWORD对齐的,所以在内存中进行图像处理时可进行汇编级的代码优化(简单))。
通过以上了解,我们对BMP有了一个比较深入的了解,本章,我们采用16位BMP编码(因为我们的LCD就是16位色的,而且16位BMP编码比24位BMP编码更省空间),故我们需要设置biBitCount的值为16,这样得到新的位图信息(BITMAPINFO)结构体:
/* 位图信息头 */

typedef __packed struct
{ 
    BITMAPFILEHEADER bmfHeader;
    BITMAPINFOHEADER bmiHeader;  
    uint32_t RGB_MASK[3];       /* 调色板用于存放RGB掩码 */
}BITMAPINFO; 

其实就是颜色表由3个RGB掩码代替。最后,我们来看看将LCD的显存保存为BMP格式的图片文件的步骤:
1) 创建BMP位图信息,并初始化各个相关信息
这里,我们要设置BMP图片的分辨率为LCD分辨率、BMP图片的大小(整个BMP文件大小)、BMP的像素位数(16位)和掩码等信息。
2) 创建新BMP文件,写入BMP位图信息
我们要保存BMP,当然要存放在某个地方(文件),所以需要先创建文件,同时先保存BMP位图信息,之后才开始BMP数据的写入。
3) 保存位图数据。
这里就比较简单了,只需要从LCD的GRAM里面读取各点的颜色值,依次写入第二步创建的BMP文件即可。注意:保存顺序(即读GRAM顺序)是从左到右,从下到上。
4) 关闭文件。
使用FATFS,在文件创建之后,必须调用f_close,文件才会真正体现在文件系统里面,否则是不会写入的!这个要特别注意,写完之后,一定要调用f_close。
BMP编码就介绍到这里。
53.2 硬件设计

  1. 例程功能
    开机的时候先检测字库,然后检测SD卡根目录是否存在PHOTO文件夹,如果不存在则创建,如果创建失败,则报错(提示拍照功能不可用)。在找到SD卡的PHOTO文件夹后,开始初始化OV7670,在初始化成功之后,就一直在屏幕显示OV7670拍到的内容。当按下KEY0按键的时候,即进行拍照,此时DS1亮,拍照保存成功之后,蜂鸣器会发出“滴”的一声,提示拍照成功,同时DS1灭。DS0还是用于指示程序运行状态。
  2. 硬件资源
    1 ) LED灯
    DS0(RED) : LED0 - PB5
    DS1(GREEN) : LED1 - PE5
    2 ) 独立按键
    KEY0 - PE4
    KEY1 - PE3
    KEY2 - PE2
    KEY_UP - PA0 (程序中的宏名:WK_UP)
  1. 串口1 (PA9/PA10连接在板载USB转串口芯片CH340上面)
  2. 正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
  3. SD卡:
    SDIO(SDIO_D0D4(PC8PC11),SDIO_SCK(PC12),SDIO_CMD(PD2))连接
  4. norflash(SPI FLASH芯片,本例为W25QXX,连接在SPI2上)
  5. 外部中断8(PA8,用于检测OV7725的帧信号)
  6. 定时器6(用于打印摄像头帧率)
  7. 正点原子 OV7725摄像头模块,连接关系为:
    OV7725模块 ------------ STM32开发板
    OV_D0~D7 ------------ PC0~7
    OV_SCL ------------ PD3
    OV_SDA ------------ PG13
    OV_VSYNC ------------ PA8
    FIFO_RRST ------------ PG14
    FIFO_OE ------------ PG15
    FIFO_WRST ------------ PD6
    FIFO_WEN ------------ PB3
    FIFO_RCLK ------------ PB4
    53.3 程序设计
    53.3.1 程序流程图
    图53.3.1.1 照相机实验程序流程图
    本实验进行摄像头的初始化后,检测SD卡是否存在,然后自动选择摄像头的模式,按下KEY0即可把摄像头捕捉到的图像拍下。
    53.3.2 程序解析
    本实验是在摄像头实验的基础上进行扩展的应用,因此我们复制《实验34 摄像头实验》的工程,在《Middlewares》中加入图片显示实验中的《PICTURE》代码,同时因为我们要用到较大的内存,需要把《MALLOC》的代码也加进来。
  1. PICTURE驱动代码
    这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码,PICTURE的驱动主要包括两个文件:bmp.c和bmp.h。
    bmp.h头文件在53.1.1小节基本讲过,具体请看源码。下面来看到bmp.c文件里面的bmp编码函数:bmp_encode,该函数代码如下:
/**
 * @brief   BMP编码函数
 * @note    将当前LCD屏幕的指定区域截图,存为16位格式的BMP文件 RGB565格式.
 *           保存为rgb565则需要掩码,需要利用原来的调色板位置增加掩码.这里我们已经增加了掩码.
 *           保存为rgb555格式则需要颜色转换,耗时间比较久,所以保存为565是最快速的办法.
 *
 * @param       filename    : 包含存储路径的文件名(.bmp)
 * @param       x, y         : 起始坐标
 * @param       width,height: 区域大小
 * @param       acolor      : 附加的alphablend的颜色(这个仅对32位色bmp有效!!!)
 * @param       mode        : 保存模式
 * @arg                       0, 仅仅创建新文件的方式编码;
 * @arg                       1, 如果之前存在文件,则覆盖之前的文件.如果没有,则创建新的文件; 
 * @retval      操作结果
 * @arg         0   , 成功
 * @arg         其他, 错误码
 */
uint8_t bmp_encode(uint8_t *filename, uint16_t x, uint16_t y, uint16_t width, 
uint16_t height, uint8_t mode)
{
    FIL *f_bmp;
    uint32_t bw = 0;
    uint16_t bmpheadsize; 	/* bmp头大小 */
    BITMAPINFO hbmp;        	/* bmp头 */
    uint8_t res = 0;
    uint16_t tx, ty;       	/* 图像尺寸 */
    uint16_t *databuf;    	/* 数据缓存区地址 */
    uint16_t pixcnt;      	/* 像素计数器 */
    uint16_t bi4width;   	/* 水平像素字节数 */

    if (width == 0 || height == 0)return PIC_WINDOW_ERR;    	/* 区域错误 */
    if ((x + width - 1) > lcddev.width)return PIC_WINDOW_ERR;	/* 区域错误 */
    if ((y + height - 1) > lcddev.height)return PIC_WINDOW_ERR;/* 区域错误 */

#if BMP_USE_MALLOC == 1     /* 使用malloc */
    
/* 开辟至少bi4width大小的字节的内存区域 ,对240宽的屏,480个字节就够了.
最大支持1024宽度的bmp编码 */
databuf = (uint16_t *)piclib_mem_malloc(2048);
    if (databuf == NULL)return PIC_MEM_ERR;     		/* 内存申请失败. */
    f_bmp = (FIL *)piclib_mem_malloc(sizeof(FIL)); /* 开辟FIL字节的内存区域 */
    if (f_bmp == NULL)      /* 内存申请失败 */
    {
        piclib_mem_free(databuf);
        return PIC_MEM_ERR;
    }
#else
    databuf = (uint16_t *)bmpreadbuf;
    f_bmp = &f_bfile;
#endif
    bmpheadsize = sizeof(hbmp);         				/* 得到bmp文件头的大小 */
    my_mem_set((uint8_t *)&hbmp, 0, sizeof(hbmp)); 		/* 置零空申请到的内存 */
    hbmp.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);   /* 信息头大小 */
    hbmp.bmiHeader.biWidth = width;   	/* bmp的宽度 */
    hbmp.bmiHeader.biHeight = height;  	/* bmp的高度 */
    hbmp.bmiHeader.biPlanes = 1;        	/* 恒为1 */
    hbmp.bmiHeader.biBitCount = 16;    	/* bmp为16位色bmp */
    hbmp.bmiHeader.biCompression = BI_BITFIELDS;/*每个象素的比特由指定的掩码决定*/
hbmp.bmiHeader.biSizeImage = hbmp.bmiHeader.biHeight * 
hbmp.bmiHeader.biWidth * hbmp.bmiHeader.biBitCount/8;/* bmp数据区大小 */
hbmp.bmfHeader.bfType = ((uint16_t)'M' << 8) + 'B';		   /* BM格式标志 */
/* 整个bmp的大小 */
    hbmp.bmfHeader.bfSize = bmpheadsize + hbmp.bmiHeader.biSizeImage; 
    hbmp.bmfHeader.bfOffBits = bmpheadsize;	/* 到数据区的偏移 */
    hbmp.RGB_MASK[0] = 0X00F800;        		/* 红色掩码 */
    hbmp.RGB_MASK[1] = 0X0007E0;        		/* 绿色掩码 */
    hbmp.RGB_MASK[2] = 0X00001F;        		/* 蓝色掩码 */

    if (mode == 1)
{/* 尝试打开之前的文件 */
        res = f_open(f_bmp, (const TCHAR *)filename, FA_READ | FA_WRITE);      
    }
    
    if (mode == 0 || res == 0x04)
{/* 模式0,或者尝试打开失败,则创建新文件 */
        res = f_open(f_bmp, (const TCHAR *)filename, FA_WRITE | FA_CREATE_NEW); 
    }
    
    if ((hbmp.bmiHeader.biWidth * 2) % 4)   /* 水平像素(字节)不为4的倍数 */
{/* 实际要写入的宽度像素,必须为4的倍数 */
        bi4width = ((hbmp.bmiHeader.biWidth * 2) / 4 + 1) * 4;  
    }
    else
    {
        bi4width = hbmp.bmiHeader.biWidth * 2;  /* 刚好为4的倍数 */
    }

    if (res == FR_OK)   /* 创建成功 */
    {
     res = f_write(f_bmp, (uint8_t *)&hbmp, bmpheadsize, &bw);/* 写入BMP首部*/
        for (ty = y + height - 1; hbmp.bmiHeader.biHeight; ty--)
        {
            pixcnt = 0;
            for (tx = x; pixcnt != (bi4width / 2);)
            {
                if (pixcnt < hbmp.bmiHeader.biWidth)
                {
                  databuf[pixcnt] = pic_phy.read_point(tx, ty);/* 读取坐标点的值*/
                }
                else
                {
                    databuf[pixcnt] = 0Xffff;   /* 补充白色的像素 */
                }
                pixcnt++;
                tx++;
            }
           hbmp.bmiHeader.biHeight--;
           res = f_write(f_bmp, (uint8_t *)databuf, bi4width, &bw);/* 写入数据*/
        }
        f_close(f_bmp);
    }
#if BMP_USE_MALLOC == 1     /* 使用malloc */
    piclib_mem_free(databuf);
    piclib_mem_free(f_bmp);
#endif
    return res;
}

该函数实现了对LCD屏幕的任意指定区域进行截屏保存,用到的方法就是53.1.1节我们所介绍的方法,该函数实现了将LCD任意指定区域的内容,保存个为16位BMP格式,存放在指定位置(由filename决定)。注意,代码中的BMP_USE_MALLOC是在bmp.h定义的一个宏,用于设置是否使用malloc,本章我们选择使用malloc。
2. main.c代码
main.c函数我们在之前摄像头实验的基本上进行改动,首先我们要为图片分配一个与图片文件夹下名字不重复的文件名,我们复用FATFS的接口,设计如下:

/**
 * @brief       文件名自增(避免覆盖)
 * @note        组合成形如 "0:PHOTO/PIC13141.bmp" 的文件名
 * @param       pname : 有效的文件名
 * @retval      无
 */
void camera_new_pathname(char *pname)
{
    uint8_t res;
    uint16_t index = 0;
    FIL *ftemp;
    ftemp = (FIL *)mymalloc(SRAMIN, sizeof(FIL));  	/* 开辟FIL字节的内存区域 */
    if (ftemp == NULL) return;                      	/* 内存申请失败 */
    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++;
    }
    myfree(SRAMIN, ftemp);
}

通过以上程序,可以生成一个与当前文件夹下图片不重名的文件名字符串,并传给针对应的缓冲区。为了模拟照相机的效果,我们需要把LCD上显示的画像读取出来并用前面的bmp_encode()函数编码成*.bmp格式的图片进行存储,以模拟实时的拍照效果。省去部分与之前实验相同的代码,我们修改整理后的main函数代码如下:

extern uint8_t g_ov7725_vsta;  				/* 在exit.c里 面定义 */
extern uint8_t g_ov7725_frame;      		/* 在timer.c里面定义 */
int main(void)
{
    uint8_t res;
    char *pname;                                	/* 带路径的文件名 */
    uint8_t key;                                	/* 键值 */
    uint8_t i;
    uint8_t sd_ok = 1;   					/* 0, sd卡不正常; 1, SD卡正常 */
    uint8_t vga_mode = 0;     /* 0, QVGA模式(320 * 240); 1, VGA模式(640 * 480) */

    HAL_Init();                       			/* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9); 	/* 设置时钟, 72Mhz */
    delay_init(72);                        		/* 延时初始化 */
    usart_init(115200);                  		/* 串口初始化为115200 */
    usmart_dev.init(72);                 		/* 初始化USMART */
    led_init();                              	/* 初始化LED */
    lcd_init();                             		/* 初始化LCD */
    key_init();                             		/* 初始化按键 */
    sram_init();                           		/* SRAM初始化 */
    beep_init();                             	/* 蜂鸣器初始化 */
    norflash_init();                       		/* 初始化NORFLASH */
    my_mem_init(SRAMIN);                 		/* 初始化内部SRAM内存池 */
    my_mem_init(SRAMEX);                  		/* 初始化外部SRAM内存池 */

    exfuns_init();                           	/* 为fatfs相关变量申请内存 */
    f_mount(fs[0], "0:", 1);               	/* 挂载SD卡 */
    f_mount(fs[1], "1:", 1);              	/* 挂载FLASH */

    piclib_init();                          	/* 初始化画图 */
    while (fonts_init())                   	/* 检查字库 */
    {
        lcd_show_string(30, 50, 200, 16, 16, "Font Error!", RED);
        delay_ms(200);
        lcd_fill(30, 50, 240, 66, WHITE); 	/* 清除显示 */
        delay_ms(200);
    }
    text_show_string(30, 50, 200, 16, "正点原子STM32开发板", 16, 0, RED);
    text_show_string(30, 70, 200, 16, "照相机 实验", 16, 0, RED);
    text_show_string(30, 90, 200, 16, "KEY0:拍照(bmp格式)", 16, 0, RED);
    res = f_mkdir("0:/PHOTO");  				/* 创建PHOTO文件夹 */

    if (res != FR_EXIST && res != FR_OK) 	/* 发生了错误 */
    {
        res = f_mkdir("0:/PHOTO");      		/* 创建PHOTO文件夹 */
        text_show_string(30, 110, 240, 16, "SD卡错误!", 16, 0, RED);
        delay_ms(200);
        text_show_string(30, 110, 240, 16, "拍照功能将不可用!", 16, 0, RED);
        delay_ms(200);
        sd_ok = 0;
    }

    while (ov7725_init() != 0)  				/* 初始化OV7725 失败? */
    {
        lcd_show_string(30, 130, 200, 16, 16, "OV7725 Error!!", RED);
        delay_ms(200);
        lcd_fill(30, 150, 239, 246, WHITE);
        delay_ms(200);
    }
    lcd_show_string(30, 130, 200, 16, 16, "OV7725 Init OK       ", RED);
    delay_ms(1500);
    
    /* 输出窗口大小设置 QVGA / VGA 模式 */
    g_ov7725_wwidth = 320;        		/* 默认窗口宽度为320 */
    g_ov7725_wheight = 240;            	/* 默认窗口高度为240 */
    ov7725_window_set(g_ov7725_wwidth, g_ov7725_wheight, vga_mode);
    ov7725_light_mode(0);             	/* 自动 灯光模式 */
    ov7725_color_saturation(4);    		/* 默认 色彩饱和度 */
    ov7725_brightness(4);            		/* 默认 亮度 */
    ov7725_contrast(4);               		/* 默认 对比度 */
    ov7725_special_effects(0);      		/* 默认 特效 */
    OV7725_OE(0);                      		/* 使能OV7725 FIFO数据输出 */
    pname = mymalloc(SRAMIN, 30);     	/* 为带路径的文件名分配30个字节的内存 */
    btim_timx_int_init(10000,7200 - 1);	/* 10Khz计数频率,1秒钟中断 */
    exti_ov7725_vsync_init();       		/* 使能OV7725 VSYNC外部中断, 捕获帧中断 */
    lcd_clear(BLACK);
    while (1)
    { 
        key = key_scan(0);
        if (key == KEY0_PRES)
        {
            if (sd_ok)
            {
                LED1(0);                    		/* 点亮DS1,提示正在拍照 */
                camera_new_pathname(pname); 	/* 得到文件名 */
                /* 编码成bmp图片 */
                if (bmp_encode((uint8_t *)pname, 
(lcddev.width - g_ov7725_wheight) / 2, 
(lcddev.height - g_ov7725_wwidth) / 2, 
g_ov7725_wheight, g_ov7725_wwidth, 0))
                {
                    text_show_string(40, 110, 240, 12, "写入文件错误!", 12, 0, RED);
                }
                else
                {
                    text_show_string(40, 110, 240, 12, "拍照成功!", 12, 0, BLUE);
                    text_show_string(40, 130, 240, 12, "保存为:", 12, 0, BLUE);
                    text_show_string(40 + 42, 130, 240, 12, pname, 12, 0, BLUE);
                    BEEP(1);        /* 蜂鸣器短叫,提示拍照完成 */
                    delay_ms(100);
                }
            }
            else     /* 提示SD卡错误 */
            {
                text_show_string(40, 110, 240, 12, "SD卡错误!", 12, 0, RED);
                text_show_string(40, 130, 240, 12, "拍照功能不可用!", 12, 0, RED);
            }
            BEEP(0);        	/* 关闭蜂鸣器 */
            LED1(1);        	/* 关闭DS1 */
            delay_ms(1800); 	/* 等待1.8秒钟 */
            lcd_clear(BLACK);
        }
        else
        {
            delay_ms(5);
        }
        ov7725_camera_refresh();	/* 更新显示 */
        i++;
        if (i >= 15)            	/* DS0闪烁 */
        {
            i = 0;
            LED0_TOGGLE();      	/* LED0闪烁 */
        }
    }
}

到这里本实验的代码基本就编写完成了,最后。
53.4 下载验证
将程序下载到开发板后,可以看到LCD首先显示一些实验相关的信息,如图53.4.1所示:
在这里插入图片描述

图53.4.1显示实验相关信息
随后,进入监控界面。此时,我们可以按下KEY0即可进行拍照。拍照得到的照片效果如图53.4.2所示:
在这里插入图片描述

图53.4.2 拍照样图
最后,我们还可以通过USMART调用bmp_encode函数,实现串口控制拍照,还可以拍成各种尺寸哦(不过必须小于 240*320)!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/736915.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【大学生自动化测试基础selenium】pageObjece

目录 目录 base_page层 common层 read_data.py send_email.py test_log.py page层 page_ECShop_Login.py testcase层 conftest.py test_login.py venv层 pytest.ini run.tests.py requirements.txt 目录 base_page层 # basepage 基础页面层&#xff08;基类&am…

算法leetcode|61. 旋转链表(rust重拳出击)

文章目录 61. 旋转链表&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 61. 旋转链表&#xff1a; 给你一个链表的头节点 head &#xf…

Mysql-------SQL:DDL数据定义语言、DDM数据操作语言、DQL数据库查询语言、DQL数据控制语言

MySQL MySQL是一个关系型数据库管理系统&#xff0c;其数据是保存在不同的数据表中&#xff0c;而不是将所有数据放在一个大仓库内&#xff0c;这样就增加了速度并提高了灵活性 SQL MySQL所使用的SQL语言是用于访问数据库的最常用标准化语言&#xff0c; SQL语言可以分为&am…

JDY-31 蓝牙传输模块的使用、调试和传输

JDY-31 蓝牙基于蓝牙 3.0 SPP 设计&#xff0c;这样可以支持 Windows、Linux、android 数据透传&#xff0c; 工作频段 2.4GHZ&#xff0c;调制方式 GFSK&#xff0c;最大发射功率 8db&#xff0c;最大发射距离 30 米&#xff0c;支持用户通过 AT 命令修改设备名、波特率等指令…

java商城 开源java电子商务Spring Cloud+Spring Boot+mybatis+MQ+VR全景+b2b2c

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…

Mybatis动态SQL解析:XML配置如何变成最终的Sql语句?

简介 动态SQL是Mybatis的一项核心功能&#xff0c;通过一份静态的XML配置 外部参数&#xff0c;动态生成最终的SQL语句&#xff0c;可以用很少的理解成本配置复杂条件的动态SQL&#xff0c;摆脱各种处理逗号、空格这些细枝末节的痛苦。 标签说明 要实现动态拼接SQL&#xf…

C++菱形继承

菱形继承 菱形继承&#xff1a;指有一个基类被两个不同的类所继承&#xff0c;且存在一个类继承于这两个类而形成的一种菱形关系&#xff0c;故称菱形继承。如下图所示&#xff1a; 菱形继承存在的问题 二义性&#xff08;ambiguity&#xff09;&#xff1a;当派生类同时继…

memset(addr , 0 , size) 导致Bus error问题分析

导致问题出现的demo代码 #define SH_DEV_MEM "/dev/mem" #define myerror printf // 获取由mmap映射的设备物理内存 static void *mymmap(u32 offset, u32 size, u8 is_rd_only, u8 is_clear) {void *ptr;s32 fd;offset 0x45E00000;size 0x1000;is_rd_only 0;is_…

【Step By Step】VM安装redhat-server7.9搭建Oracle19C RAC(一)环境配置

文章目录 环境规划网络规划文件系统规划rac用户规划grid与oracle用户规划ASM规划 虚拟机设置搭建虚拟机自定义网卡安装操作系统 操作系统设置关闭services修改/etc/hosts创建用户与组创建文件目录设置环境变量设置内核参数资源限制添加 etc/pam.d/login关闭大页关机挂载本地ISO…

【C++初阶】:vector的基本介绍

vector的基本介绍 一.vector(向量)介绍二.vector原型三.构造四.区别reserve和resize五.二维数组 一.vector(向量)介绍 二.vector原型 vector主要分为两个模板参数&#xff0c;一个是T&#xff0c;T是将数据类型进行实例化&#xff08;本质就是一个数组&#xff09;。第二个参数…

idea 搭建 SpringBoot 集成 mybatis

编译器&#xff1a;IDEA 环境&#xff1a;win10&#xff0c;jdk1.8&#xff0c;maven3.5 数据库&#xff1a;mysql 5.7 一、打开IDEA新建项目 1. 如果你是第一次使用IDEA&#xff0c;那么你需要配置你本地的maven&#xff0c;点击右下角 Configure&#xff0c;如已配置请忽…

第三章:Linux简介及面试常问问题

目录 一、Linux发展史 1.Linux前身-Unix 2.Linux诞生 3.开源文化 4.Linux系统特点 5.Linux操作系统优点 6.Linux操作系统发行版 7.Linux内核命名规则 二、Linux系统的安装 三、linux系统分区简介及如何分区 1.自动分区 2.手动分区 3.linux下查看分区信息和剩余空间…

STM32CubeMX安装

一、配置JAVA环境 1.相关资料链接 链接: CubeMX 提取码&#xff1a;13ec 双击改exe文件。 2.更改默认的安装路径 3.在该exe文件同等目录下&#xff0c;新建一个JAVA-Enviroment文件夹。 选择更改安装路径&#xff0c;放在刚才新建的JAVA-Enviroment文件夹中。 4.等待安装完…

RabbitMQ - 单机部署(超详细)

RabbitMQ部署 1.单机部署 我们在Centos7虚拟机中使用Docker来安装。 1.1.下载镜像 方式一&#xff1a;在线拉取 docker pull rabbitmq:3-management方式二&#xff1a;从本地加载 也可以从网上搜索 RabbitMQ 的 tar 包下载下来 上传到虚拟机中后&#xff0c;使用命令加载…

2023高校夏令营 | 第七年,我们又开营啦!

进入7月&#xff0c;成都的天气就不再像之前那样温和&#xff0c;迎接我们的是热浪滔天&#xff0c;以及一场场猝不及防的高温暴雨。如同当下的毕业季&#xff0c;脱离校园庇护的应届生们涌入就业市场&#xff0c;开始应对来自社会、职场的各种风雨考验。 为了加快推进产教融合…

js深拷贝、js使用递归实现深拷贝

相信看到这篇文章之前你应该多多少少了解过什么是深拷贝了为什么要什么拷贝了 这里就不介绍什么是深拷贝、浅拷贝了 最优解深拷贝方法&#xff1a;递归 这里为了方便直接在vue的页面里面演示了&#xff0c;在其他js文件中使用注意下this mounted() {const obj {str: "字…

实战解决百度旋转验证码

1、效果演示 2、如何识别 2.1准备数据集 首先需要使用爬虫&#xff0c;对验证码图片进行采集&#xff0c;尽量每一种类型都要采集到。 2.2图像矫正 接下来对采集的数据进行人工校正 2.3数据清洗 &#xff08;1&#xff09;对数据进行进行旋转&#xff0c;达到增加数据量的目…

44. 日期交叉问题

文章目录 题目需求思路一实现一原理二实现二学习链接题目来源 题目需求 现有各品牌优惠周期表&#xff08;promotion_info&#xff09;如下&#xff0c;其记录了每个品牌的每个优惠活动的周期&#xff0c;其中同一品牌的不同优惠活动的周期可能会有交叉。 现要求统计每个品牌…

聊一聊Java中的Steam流 | 京东物流技术团队

1 引言 在我们的日常编程任务中&#xff0c;对于集合的制造和处理是必不可少的。当我们需要对于集合进行分组或查找的操作时&#xff0c;需要用迭代器对于集合进行操作&#xff0c;而当我们需要处理的数据量很大的时候&#xff0c;为了提高性能&#xff0c;就需要使用到并行处…

Golang内存分配及垃圾回收

为什么需要垃圾回收&#xff1f; 自动释放不需要的对象&#xff0c;让出存储器资源&#xff0c;无需程序员手动执行 Go V1.3之前是标记-清除算法 具体步骤 缺点&#xff1a;程序卡顿、扫描整个heap、数据清除会产生heap碎片 V1.3之后&#xff0c;做了简单的优化 V1.5之后&a…