STM32开发学习笔记之八【摄像头视频显示】

STM32开发学习笔记之八【摄像头视频显示】

摄像头也是单片机常用外设之一,今天来学学单片机驱动OV5640芯片摄像头并显示在屏幕上的案例。

一、用STM32CubeMX创建STM32工程

本文中不涉及STM32CubeMX的下载及安装,需要的请到 https://www.st.com/en/development-tools/stm32cubemx.html#get-software自行下载安装。

(一)新建工程

新建工程文件项目名称设为"cam",本笔记中项目文件存放在"E:\prj\stm32\cam"路径下。启动Stm32CubeMX程序在File菜单单击【新建项目】进入CPU选择界面

(二)选择芯片型号

开发板CPU型号为STM32H743VIT6,所以在Commercial Part Number编辑框输入型号。如果事先没有确定型号而是通过项目需求筛选的话可以在左侧导航栏筛选。输入型号后右侧会显示具体封装的列表,双击正确CPU进入系统引脚与配置窗口

如果出现下面窗口选"NO",因为本项目不涉及复杂的内核配置。

进入引脚与配置窗口 后如下图:

(三)SD卡接口配置

1、SDMMC1控制器配置

本案例要把SD上的24位bmp图片读出来显示到屏幕上,所以这里要打开SD卡的IO口。选择【SDMMC1】后模式要选4位总线类型,下面的分频系数填4

2、开SDMMC1控制器中断

打开SD卡的中断使能,IO引脚的工作模式用默认值就行

3、FAT32中间件配置

因为SD卡是被格式化成FAT32型文件系统了,所以中间件必须为我们提供FAT格式的解释器。因此选中中间件【FATFS】,模式选择【SD Card】,下面红色框选上对应项

其它选默认值。

(四)LCD接口配置

1、数据接口SPI配置

本LCD屏硬件设计连接到了SPI4口。所以选择【SPI4】后模式要选【Half-Duplex Master】半双工主控制器,数据传输位宽【Data Size】设为8位,SPI时钟分频系数【Prescaler】设为8,使通信速率为15Mhz。

SPI4口是可重定向IO口,默认情况下指向PE2和PE6,如下图:

对应引脚图如下:

但是我们开发板上硬件设计时选用了它的重定向引脚PE12和PE14,所以还得把它们改过来。具体操作是先按住Ctrl键,然后鼠标左键单击【引脚图】绿色的PE2引脚并且按住,等到PE12开始闪烁以后,拖放PE2到PE12即可,PE6改成PE14如法砲制。

接口速度选高速

2、控制接口GPIO口配置

屏幕还有两个辅助接口使用了PE11(片选)和PE13(读写使能),所以搜索两个引脚后设置为【输出】,引脚属性也有改动

3、背光接口PWM口配置

LCD还需要一个PWM信号调节背光亮度,本案例选用定时器提供信号输出,打开【TIM1】定时器,设置通道2为;【PWM Generation CH2N】(调背光用)

IO口仍然需要重定向到PE10,如下图:

基础参数如下:

IO引脚的工作模式用默认值就行。以上5个引脚配置好以后LCD的接口就配置完了。

(五)摄像头接口配置

1、I2C接口配置

I2C接口用来配置OV5640上的各种寄存器,是摄像头的控制接口。首先选择打开I2C,在参数配置这里将上升沿和下降沿建立时间重新配置

这里的I2C引脚也是比较特殊的,GPIO的设置包括重新映射外部引脚,同时引脚工作状态也要重新配置

I2C接口其它配置默认。

2、DCMI接口配置

OV5640视频输出格式可以是标准VGA格式,正好STM32H743芯片上具有硬件DCMI视频解析模块,所以我们需要配置该模块。首先在左侧【多媒体】栏打开DCMI,模式设为【从8位】,PCLK设为上升沿有效

打开DCMI模块的中断

在DCMI模块上添加DMA工作模式,然后选择【DCMI】进入到DMA配置界面

修改DMA的工作模式为【Circular】,其它默认

DCMI的GPIO也需要重新映射,下面是刚进入GPIO的配置界面

下面是将上图GPIO引脚重映射的结果

(六)RCC时钟源配置

1、HSE时钟配置

打开高速时钟源 HSE,设为外部驱动。同时打开【主时钟输出1】它是给摄像头工作的主频时钟,如图所示。

首先打开外部时钟,同时因为本案例使用的SPI要用到外设120Mhz的时钟,需要RCC时钟参数配置这里将工号调整等级设为0(值越大功耗越低)。

2、时钟树配置

进入 【Clock Configuration】 配置栏之后可以看到,界面展现一个完整的 STM32H7 时钟系统框图。从这个时钟树配置图可以看出,配置的主要是外部晶振大小,分频系数,倍频系数以及选择器。在我们配置的工程中,时钟值会动态更新,如果某个时钟值在配置过程中超过允许值,那么相应的选项框会红色提示。

上图时钟配置的关键点是:确定主频、外频和SD卡外设时钟,接下来在时钟树下面进一步配置SPI外设和SD卡外设时钟。

(七)生成工程文件

1、工程设置

接下来我们设置生成一个工程,如下图所示。选择 Project Manager-> Project选项用来配置工程的选项,我们了解一下里面的信息。

Project Name:工程名称,填入工程名称(这里填cam_lcd)(半角,不能有中文字符)

Project Location:工程保存路径,点击 Browse 选择保存的位置(这里填E:\prj\stm32\cam_lcd)(半角,不能有中文字符)

Toolchain Folder Location:工具链文件夹位置,默认即可。

Application Structure:应用的结构,选择 Advanced,不勾选 Do not generate the main(),因为我们要其生成 main 函数。

Toolchain/IDE:工具链/集成开发环境,我们使用 Keil,因此选择 MDK-ARM,Min Version 选择 V5.27,这里根据 CubeMX 的版本可能会有差异,我们默认使用 V5 以上的版本即可。

Linker Settings 链接器设置:

Minimum Heap Size 最小堆大小,调整到2000。

Minimum Stack Size 最小栈大小,调整到2000。

MCU and Firmware Package 是 MCU 及固件包设置:

MCU Reference:目标 MCU 系列名称。

Firmware Package Name and Version:固件包名称及版本。

勾选 Use Default Firmware Location,文本框里面的路径就是固件包的存储地址,我们使用默认地址即可。(这里因为我有两个版本的固件包,所以它默认使用最新的,这个关系不大,就用新的)。

2、代码生成器设置

打开 Project Manager-> Code Generator 选项,Generated files 生成文件选项,勾选 Generate peripheral initialization as a pair of '.c/.h'files per peripheral,勾选这个选项的话将会将每个外设单独分开成一组.c、.h 文件,使得代码结构更加的清晰,如图所示。

由于 CubeMX 默认勾选了复制所有的库,即工程中不使用到的代码也会复制进来,为了节省 CubeMX 生成工程的空间,我们勾选生成工程时只复制用到的库(这一步是可选操作,大家根据自己的实际选择)

3、保存工程

至此工程最基础配置就已经完成,点击蓝色按钮(SENERATE CODE)就可以生成工程。

4、生成工程

如果我们的 CubeMX 工程放置配置路径中没有中文,生成代码后会弹出类似下图的提示窗口,点击 【Open Project】 就打开 MDK 工程(如果是中文路径则会报错)。

到此为止CubeMX任务就结束了。

二、STM32系统C程序设计

打开生成的C代码项目已经假设在你的计算机安装了MDK,如果MDK还没有安装请自行下载并安装。https://www.keil.com/download/product/
注意!不要下载最新版,有可能存在问题!

(一)用MDK打开工程

在MDK的keil编程界面中打开工程后的主界面如下图:

(二)配置LCD驱动BSP程序

LCD模块在出厂时已经提供了相关的BSP(Board Support Package)底层驱动程序。这些代码文件存放在【BSP\ST7735】文件夹中。

1. 拷贝出厂BSP文件到项目文件夹

本案例由cubeMX自动生成的项目代码保存在了【E:\prj\stm32\08_cam_lcd\cam_lcd】中了,因此我们把【BSP文件夹】及里面的文件拷贝到【Drivers】文件夹里。

其中st7735_reg.c定义了LCD内的全部寄存器,st7735.c定义了访问LCD的最顶层函数,在lcd.c中定义了通过调用st7735.c最底层函数实现的显示功能,font.h为英文字库。

2. 出厂BSP文件添加入项目

文件拷贝完后回到程序界面单击【项目文件结构配置】按钮,在弹出界面的中间窗口中单击【新建】,接下来输入【BSP/ST7735】

新建好【BSP/ST7735】项目文件夹后,右侧单击【添加文件】按钮,将刚才拷贝过来的LCD源文件加入到项目当中。

单击【OK】,源码文件就被导入到项目中了

3. 将BSP文件夹加入文件包含路径

文件加入到项目了但是编译器仍然找不到对应文件,还得将BSP文件夹路径加入编译器识别的头文件路径。

单击【项目配置向导】按钮,出现窗口再单击【...】弹出下面界面

在窗口中单击【新建】按钮,将BSP路径加入到列表中,然后单击【OK】

(三)配置CAM驱动BSP程序

CAM模块在出厂时已经提供了相关的BSP(Board Support Package)底层驱动程序。这些代码文件存放在【BSP\ST7735】文件夹中。

1. 拷贝出厂BSP文件到项目文件夹

(二) 描述的类似,我们把【BSP文件夹】及里面的文件拷贝到【Drivers】文件夹里。

【Camera】文件夹里的出厂OV驱动如下:

我们板子上安装的是ov5640摄像头,所以其中的ov5640.c、ov5640.h、ov5640_regs.c、ov5640_regs.h、camera.c和camera.h我们是能用到的,其它摄像头主要是分辨率不同,ov5640好一些。

2. 出厂BSP文件添加入项目

参照(二)...

3. 将BSP文件夹加入文件包含路径

参照(二)...

(四)在项目中添加全局变量

摄像头的数据要想显示到LCD上需要一个数据缓冲区,本案例将数据缓冲区设为全局变量并且把对应的宏等参数保存在【myGlobal.h】文件中并存放在【core\inc】文件夹

在该头文件里加入下面代码:

c 复制代码
//============ myGlobal.h =================
#include "stdint.h"

#ifndef MY_GLOBAL_H
#define MY_GLOBAL_H

#define IMGBUFWIDTH 	120
#define IMGBUFHEIGHT 	80
#define IMGBUFNUM		2
#define TXTBUFWIDTH 	20

extern uint8_t IMGBUF[IMGBUFNUM][IMGBUFHEIGHT*IMGBUFWIDTH*2];	// 图形显存
extern uint8_t IMGIDX;											// 显示缓冲区ID,默认0
extern uint8_t TXTBUF[TXTBUFWIDTH];								// 文本显存

#endif

从代码中看出缓冲区有IMGBUF为图形显存和TXTBUF为文本显存。显示缓冲区(IMGBUF)的宽度为120,高度为80,而且设置了两个(另一个备用)通过IMGIDX变量可以在两个显示缓冲区上选择。文本缓冲区宽度设为20。

(五)改写lcd.c中的代码

在lcd.c中加入头文件包含myGlobal.h

c 复制代码
#include "myGlobal.h"

在lcd.c文件中加入显示初始化代码,该部分主要设定屏幕显示方向、清屏幕和设置背光,这部分和上一个案例一致

c 复制代码
void LCD_Disp_Init(void)
{
	ST7735Ctx.Orientation = ST7735_ORIENTATION_LANDSCAPE_ROT180;// 横屏原点左下
	ST7735Ctx.Panel = HannStar_Panel;			// 瀚宇面板
	ST7735Ctx.Type = ST7735_0_9_inch_screen;	// 0.9寸屏
	
	ST7735_RegisterBusIO(&st7735_pObj,&st7735_pIO);
	ST7735_LCD_Driver.Init(&st7735_pObj,ST7735_FORMAT_RBG565,&ST7735Ctx);
	
	ST7735_LCD_Driver.FillRect(&st7735_pObj, 0, 0, ST7735Ctx.Width,ST7735Ctx.Height, BLACK);	// 涂黑全屏
	LCD_SetBrightness(500);		// 设置背光亮度			

}

lcd.c文件中显示函数改写成如下形式

c 复制代码
void LCD_Disp(void)
{
	ST7735_FillRGBRect(&st7735_pObj, 0, 0, IMGBUF[IMGIDX], IMGBUFWIDTH, IMGBUFHEIGHT);	//刷新到LCD
	LCD_ShowString(130, 30, ST7735Ctx.Width, 16, 16, TXTBUF);
}

从上面改写LCD.C中的代码可以看出,开发板上16080点阵的0.96寸显示器被分成12080和40*80两个区域,左边用于显示图像,右边【130,30】这个坐标的位置用于显示文本。

(六)改写camera.c中的代码

在camera.c中加入头文件包含myGlobal.h

c 复制代码
#include "myGlobal.h"

在camera.c文件中,原有的void Camera_XCLK_Set(uint8_t xclktype)函数用不上被我删掉了,初始化摄像头函数只保留了ov5640部分,初始化摄像头代码如下:

c 复制代码
void Camera_Init_Device(I2C_HandleTypeDef *hi2c, framesize_t framesize)
{
	hcamera.hi2c = hi2c;
	hcamera.addr = OV5640_ADDRESS;
	hcamera.timeout = 100;

	Camera_read_id(&hcamera);	// 读设备ID
	if (hcamera.device_id == 0x5640)
	{
		// 初始化ov5640
		ov5640_init(framesize);
	}
	else
	{
		hcamera.addr = 0;
		hcamera.device_id = 0;
	}
}

(七)编写main.c程序

1、加入头文件和全局变量

在主文件中加入头文件

c 复制代码
#include "myGlobal.h"
#include "camera.h"
#include "lcd.h"

下面再声明全局变量

c 复制代码
uint8_t IMGBUF[IMGBUFNUM][IMGBUFHEIGHT*IMGBUFWIDTH*2];	// 图形显存
uint8_t TXTBUF[TXTBUFWIDTH];							// 文本显存
uint8_t IMGIDX = 0;

2、加入读SD卡函数

读取SD卡上图片的功能保留了,一个是起到显示开机LOGO的作用,另一个是系统设计越来越复杂,用它来验证显示功能是否正确。

c 复制代码
int ReadSDImg(void)
{
	FATFS fs;							// 文件系统指针
	FIL file;							// 文件指针
	FRESULT res;						// 函数返回值
	UINT bytes_read;					// 读字节数
	uint8_t image_buf[IMGBUFWIDTH*3];	// 数据缓冲区
	char *filename = "cat120.bmp";
	uint32_t width = 0;
	
	uint8_t		*p;
	uint8_t 	r,g,b;
	uint16_t	color565;
	
	// 1. 挂载FATFS到SD卡
    res = f_mount(&fs, "0:", 1);
    if(res != FR_OK) 
	{
        return -1;
    }
    
    // 2. 打开文件
    res = f_open(&file, filename, FA_READ);
    if(res != FR_OK) 
	{
        return -1;
    }
    
    // 3. 读文件
    res = f_read(&file, image_buf, 54, &bytes_read);
	if(res == FR_OK && bytes_read == 54) 
	{
		if(image_buf[0] == 'B' && image_buf[1] == 'M') 
		{
			width  = *(uint32_t*)&image_buf[18];
			
			// 读图像数据
			for(int row=IMGBUFHEIGHT-1;row>=0;row--)
			{
				for(int num=0;num<2;num++)
				{
					f_read(&file, image_buf, width*3/2, &bytes_read);
					p = image_buf;
					for(int col=0;col<IMGBUFWIDTH*2;col+=2)
					{
						b = *(p++);
						g = *(p++);
						r = *(p++);
						
						color565 = r>>3&0x1F;
						color565 = color565<<6; 
						color565 |= g>>2&0x3F;
						color565 = color565<<5; 
						color565 |= b>>3&0x1F;
							
						IMGBUF[num][row*IMGBUFWIDTH*2+col] = color565>>8;
						IMGBUF[num][row*IMGBUFWIDTH*2+col+1] = color565;
						
					}
				}	
			}
		} 
		else 
		{
			return -1;
		}
	} 
	else 
	{
		return -1;
	}
 	
	sprintf((char *)&TXTBUF, "Cat");
	
	f_close(&file);
	return 0;
}

3、显示开机LOGO代码

c 复制代码
  LCD_Disp_Init();
  ReadSDImg();
  IMGIDX = 0;
  LCD_Disp();

以上代码如果编译通过了,就可以下载到板子上验证LCD功能了。

4、开启摄像头

之前摄像头已经配置好了,接下来加入初始化摄像头并启动DMA的代码

c 复制代码
  Camera_Init_Device(&hi2c1, FRAMESIZE_HQQVGA);
  HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_CONTINUOUS, (uint32_t)&IMGBUF[IMGIDX], IMGBUFWIDTH * IMGBUFHEIGHT * 2 / 4);

接下来在主循环里加入显示函数

三、编译下载

单击编译按钮等一段时间系统编译完成会有如上图的错误或者警告信息(都是0最好)。编译通过以后,通过项目设置窗口的Debug标签确认程序下载器是否正确连接并安装好驱动程序

如果下载器没问题单击download下载按钮,下载器一边闪烁一边把编好的程序下载到STM32的FLASH当中,按下复位键开发板就可以工作了。

有问题欢迎留言,我再学新东西的时候和刚入门的小伙伴一起分享...

相关推荐
帅次4 小时前
系统分析师-信息物理系统分析与设计
stm32·单片机·嵌入式硬件·mcu·物联网·iot·rtdbs
澜莲Alice4 小时前
STM32 MPLAB X IDE 软件安装-玩转单片机-英文版沉浸式安装
stm32·单片机·嵌入式硬件
lxl13074 小时前
学习C++(5)运算符重载+赋值运算符重载
学习
ruxshui5 小时前
个人笔记: 星环Inceptor/hive普通分区表与范围分区表核心技术总结
hive·hadoop·笔记
慾玄5 小时前
渗透笔记总结
笔记
AutumnorLiuu5 小时前
C++并发编程学习(一)——线程基础
开发语言·c++·学习
CS创新实验室5 小时前
关于 Moltbot 的学习总结笔记
笔记·学习·clawdbot·molbot
峥嵘life5 小时前
Android EDLA CTS、GTS等各项测试命令汇总
android·学习·elasticsearch
千谦阙听5 小时前
数据结构入门:栈与队列
数据结构·学习·visual studio
.小墨迹6 小时前
C++学习——C++中`memcpy`和**赋值拷贝**的核心区别
java·linux·开发语言·c++·学习·算法·机器学习