STM32单片机实验手册

内容简介

实验手册将由浅入深，带领大家学习STM32F429的各个功能，开启STM32F429的学习之旅，本手册总共分为四篇：

1.软件篇。主要介绍STM32F429常用开发软件Keil的安装使用以及一些下载调试程序的技巧，并详细介绍了几个常用的系统文件（程序）。

2.硬件篇（接收端）。主要介绍了接收端STM32F429开发板的硬件平台并附带了一些操作例程，带领大家一步步深入了解STM32F429。

3.硬件篇（发送端）。主要介绍了发送端的颗粒传感器PMS5003、温湿度传感器DHT11、甲烷传感器MQ-2、一氧化碳传感器MQ-7、有机物及CO2浓度传感器SGP-30。

4.联调篇。主要介绍了发送端和接收端联调的过程，使其组成一个完整的环境质量动态监测预警系统。

实验注意事项

一、小心拔插杜邦线

杜邦线包括塑料头和导线两部分，接头不牢靠，插拔时定要抓住坚硬的塑料头部分，不能抓住软的导线，如图I所示。一次拔不掉不要着急，抓住塑料头耐心拔几次就拔掉了。拔硬的塑料头不会损伤导线，拔软的导线很容易拔断杜邦线，这是长养耐心和爱心的过程。

图I 小心插拔杜邦线

二、要在断电时插拔线和元器件

带电插拔时，有很强的冲击电流，损伤实验设备。一定要在实验设备断电的情况下，插拔杜邦线和电子元器件。在USB线连着实验设备和电脑时，有些情况下，比如实验板上接着普中仿真器时，USB线给实验设备供电，关掉总电源时实验仪仍然带电，这时先拔掉电脑端的USB线，给实验仪断电，再插拔杜邦线和元器件。

目录

内容简介	2

实验注意事项	3

第一篇软件篇	6

Keil的安装	6

一、Keil简介	6

二、Keil安装步骤	7

三、使用Keil建立一个工程	12

Keil的使用技巧	37

一、 代码编辑/查看技巧	37

二、 代码提示及语法检测	39

三、 文本美化	41

第二篇 硬件篇（接收端）	44

跑马灯实验	44

一、STM32F4IO简介	44

二、硬件设计	53

三、软件设计	53

四、下载验证	62

五、STM32CubeMX配置IO口输入	63

按键输入实验	67

一、STM32F4IO口简介	67

二、硬件设计	68

三、软件设计	68

四、下载验证	72

五、STM32CubeMX配置IO口输出	73

LTDCLCD（RGB屏）实验	76

一、RGBLCD&LTDC简介	76

二、硬件设计	102

三、软件设计	103

四、下载验证	119

摄像头实验	120

一、OV5640&DCMI简介	120

2.STM32F429DCMI接口简介	127

二、硬件设计	136

三、软件设计	138

四、下载验证	155

第三篇硬件篇（发送端）	158

传感器介绍	158

一、DHT11数字式温湿度传感器	158

二、MQ-2可燃气体传感器	160

三、MQ-131臭氧传感器	162

四、PMS5003颗粒物浓度传感器	163

五、SGP30可燃性气体传感器	166

ESP8266无线模块介绍	167

第四篇联调篇	171

产品功能描述	171

系统联调	179

第一篇软件篇

Keil的安装

一、Keil简介

Keil支持汇编语言、C和C++语言的开发。汇编语言代码冗余小，执行效率高；C语言和C++语言开发效率高、文件的可读性高、系统的维护性强。

本实验使用的单片机型号为STM32F429TGT6，采用ARM Cortex-M4内核。使用的开发软件是Keil5MDK版，MDK版即KeilforARM,是专门为开发ARM系列所推出的软件。

目前STM32已经推出基本型系列、增强型系列等许多系列产品，可以通过功能、存储器、性能和引脚数量来进行产品型号的选择，以满足个性化的应用需求。

以STM32F051R8T6xxx这个型号的芯片为例，该芯片的名称可划分成如图7个部分：

因此从图中可读出本课题所用的STM32F429IGT6各个字符的含义分别是：

STM32:代表ST品牌Cortex-Mx系列内核的32位MCU。

F:指产品类别为基础级。

429:代表特定功能，指该型号使用ARM Cortex-M4内核。

I:指共有176+25个引脚。

G:指闪存容量为1024KB。

T:指封装方法为QFP封装

6:指工作温度为-40~+85℃

二、Keil安装步骤

打开安装资料包“keil5”，找到软件安装包MDK529并以管理员身份运行，弹出如图1-1安装界面，点击“Next”。（有些MDK可能是518等，这个关系不大）

图 1-1 建议安装前关闭所有程序

弹出如图1-2界面，勾选“Iagree”复选框，单击“Next”。

图 1-2 同意许可协议

例如图1-3，选择安装路径，默认安装在C盘，也可以点击“Browse”放在其他磁盘。最好在空间大一点的盘符新建一个“Keil5”文件夹，然后两个路径都选择指向新建的“Keil5”文件夹。

图 1-3 选择安装路径

如图1-4，填写姓名、公司名称，可随意填写。

图 1-4 填写用户信息

填写完后，单击“Next”,开始安装，如图1-5。

图 1-5 Keil安装中

如图1-6，点击“安装”。

图 1-6 安装控制器

如图1-7，等待。

图 1-7 等待

如图1-8，取消勾选，然后点击“Finish”。

图 1-8 Keil安装完成

如图1-9，告诉我们接下来可以选择下载一些固件包来使用，单击“OK”。

图 1-9 退出

如图1-10，我们先点击软件右上角的关闭按钮退出软件，随用随下。

图 1-10 退出

评估版的功能会收到限制，注册后可以无限制的运行，首先以管理员身份打开下载好的Keil，点击“File”菜单栏下面的“LicenseManagenent”，如图1-11。

图 1-11 打开LicenseManagenent

打开安装资料包“keil5”，找到注册机keygen并以管理员身份运行，如图1-12。

图 1-12 打开注册机

两个界面全部打开如图1-13，然后，(1)将CID号复制到注册机中。(2)下拉选择ARM。(3)下拉选择Professional，即专业版。(4)点击Generate确定。(5)自动出现ID号并将ID号复制到LIC中。(6)点击AddLIC添加。(7)出现这行黑体字，说明破解成功，可以使用到2032年12月。若注册过程中出错，注意keil和注册机都要右键管理员身份打开。

图 1-13 注册过程

现在来补充下载第十步没有下载的固件包。打开安装资料包“keil5”，找到后缀为“.pack”的固件包，双击打开后点击“Next”，等待，然后点击“Finish”安装完成。

要将调试好的程序下载到开发板中，可以使用ISP串口，优点是占用的引脚少但只能下载程序不能单步调试。也可以使用S-TLINK，优点是下载的程序可单步调试但占用引脚多。实验室电脑已经安装好驱动，若还想自己了解两种方法的驱动安装方法，请参考下面两个文件。

ISP串口驱动安装

打开安装资料包“驱动程序”，找到并打开文件夹“CH340驱动”，点击“readme”，参考教程安装。

S-TLINK驱动安装

打开安装资料包“驱动程序”，找到并打开文件夹“STLINK驱动及教程”，打开pdf文件“STLINK调试补充教程”，参考教程安装。

注：所有安装工作完成后打开Keil，打开一个工程例子编译一下，若编译出错，参考Word文档“编译报错解决办法”，需要配置系统环境变量。

三、使用Keil建立一个工程

新建应用于STM32开发板的工程只需直接利用其现成的工程模板即可，该模板位于“程序源码\标准例程-库函数版本\实验0-1Template工程模板-新建工程章节使用”。

当然我们也可以自己建立工程模板，在此我们介绍一下两种方法。

第一种：手动配置文件夹

在新建模板之前之前，首先我们要准备如下资料：

HAL库开发包：STM32Cube_FW_F4_V1.11.0

这是ST官网下载的STM32CubeF4包完整版，保存在我们的资料目录（压缩包）：

“\8，STM32参考资料\1，STM32CubeF4固件包\en.stm32cubef4.zip”。

安装好Keil5MDK版，这个我们已经和大家一起完成过了。

做好准备工作后，我们按下面的步骤一步步操作：

在建立工程之前，我们建议在电脑的某个目录下面建立一个文件夹，后面所建立的工程都可以放在这个文件夹下面，这里我们建立一个文件夹为Template。这是工程的根目录文件夹。然后为了方便我们存放工程需要的一些其他文件，这里我们还新建下面4个子文件夹：CORE，HALLIB，OBJ和USER。至于这些文件夹名字，实际上是可以任取的，我们这样取名只是为了方便识别。对于这些文件夹用来存放什么文件，我们后面的步骤会一一提到。新建好的目录结构如图1-14。

图 1-14 新建文件夹

接下来，打开MDK，点击菜单Project–>NewUvisionProject，然后将目录定位到刚才建立的文件夹Template之下的USER子目录，工程取名为Template之后点击保存，工程文件就都保存到USER文件夹下面。操作过程如下图1-15和1-16所示：

图 1-15 新建工程

图 1-16 定义工程名称

如图1-17，接下来会出现一个选择Device的界面，就是选择我们的芯片型号，这里我们定位到STMicroelectronics下面的STM32F429IGT6。这里我们选择STMicroelectronicsSTM32F4SeriesSTM32F429STM32F429IGSTM32F429IGTx（如果使用的是其他系列的芯片，选择相应的型号就可以了,但是一定要安装对应的器件pack才会显示这些内容）。

图 1-17 选择芯片型号

点击OK，MDK会弹出ManageRun-TimeEnvironment对话框，如图1-18所示：

图 1-18 ManageRun-TimeEnvironment界面

这是MDK5新增的一个功能，在这个界面，我们可以添加自己需要的组件，从而方便构建开发环境，不过这里我们不做介绍。所以在图1-18所示界面，我们直接点击Cancel，即可得到如图1-19所示界面：

图 1-19 工程初步建立

现在我们看看USER目录下面内容，如下图1-20：

图 1-20 工程USER目录文件

这里我们说明一下，Template.uvprojx是工程文件，非常关键，不能轻易删除，MDK生成的工程文件是以.uvprojx为后缀。DebugConfig，Listings和Objects三个文件夹是MDK自动生成的文件夹。其中DebugConfig文件夹用于存储一些调试配置文件，Listings和Objects文件夹用来存储MDK编译过程的一些中间文件。这里，我们把Listings和Objects文件夹删除，我们自己配置一些文件夹来存放相关文件。

我们会在下一步骤中新建一个OBJ文件夹，用来存放编译中间文件。当然，我们不删除Listings和Objects也没有关系，只是我们不用它而已。

接下来我们将从官方stm32cubeF4包里面复制一些我们新建工程需要的关键文件到我们的工程目录中。首先，我们要将STM32CubeF4包里的源码文件复制到我们的工程目录文件夹下面。打开官方STM32CubeF4包，定位到我们之前准备好的HAL库包的目录:

\STM32Cube_FW_F4_V1.10.0\Drivers\STM32F4xx_HAL_Driver下面，将目录下面的Src,Inc文件夹复制到我们刚才建立的HALLIB文件夹下面。Src存放的是固件库的.c文件，Inc存放的是对应的.h文件，我们不妨打开这两个文件目录过目一下里面的文件，每个外设对应一个.c文件和一个.h头文件。操作完成后工程HALLIB目录内容如图1-21

图 1-21 官方库源码文件夹

接下来，我们要将STM32CubeF4包里面相关的启动文件以及一些关键头文件复制到我们的工程目录CORE之下。打开STM32CubeF4包，定位到目录

\STM32Cube_FW_F4_V1.11.0\Drivers\CMSIS\Device\ST\STM32F4xx\Source\Templates\arm下面，将文件startup_stm32f429xx.s复制到CORE目录下面。然后定位到目录

\STM32Cube_FW_F4_V1.11.0\Drivers\CMSIS\Include，将里面的五个头文件：cmsis_armcc.h，core_cm4.h，core_cmFunc.h，core_cmInstr.h，core_cmSimd.h同样复制到CORE目录下面。现在看看我们的CORE文件夹下面的文件，如下图1-22：

图 1-22 CORE文件夹文件

接下来我们要复制工程模板需要的一些其他头文件和源文件到我们工程。首先定位到目录：

\STM32Cube_FW_F4_V1.11.0\Drivers\CMSIS\Device\ST\STM32F4xx\Include将里面的3个文件stm32f4xx.h，system_stm32f4xx.h和stm32f429xx.h复制到USER目录之下。这三个头文件是STM32F4工程非常关键的头文件。然后进入目录\STM32Cube_FW_F4_V1.11.0\Projects\STM32F429I-Discovery\Templates目录下，这个目录下面有好几个文件夹，如下图1-23，我们需要从Src和Inc文件夹下面复制我们需要的文件到USER目录。

图 1-23 固件库包Template目录下面文件一览

首先我们打开Inc目录，将目录下面的3个头文件stm32f4xx_it.h，stm32f4xx_hal_conf.h和main.h全部复制到USER目录下面。然后我们打开Src目录，将下面的四个源文件system_stm32f4xx.c，stm32f4xx_it.c，stm32f4xx_hal_msp.c和main.c同样全部复制到USER目录下面。相关文件复制到USER目录之后USER目录文件如下图1-24：

图 1-24 USER目录文件浏览

前面6个步骤，我们将需要的文件复制到了我们的工程目录下面了。接下来，我们还需要复制ALIENTEK编写的SYSTEM文件夹内容到工程目录中。首先，我们需要解释一下，这个SYSTEM文件夹内容是ALIENTEK为开发板用户编写的一套非常实用的函数库，比如系统时钟初始化，串口打印，延时函数等，这些函数被很多工程师运用到自己的工程项目中。当然，大家也可以根据自己需求决定是否需要SYSTEM文件夹，对于STM32F429的工程模板，如果没有加入SYSTEM文件夹，那么大家需要自己定义系统时钟初始化。SYSTEM文件夹对于库函数版本程序和寄存器版本程序是有所区别的，这里我们新建的是库函数工程模板，所以大家从光盘程序源码目录之下的库函数版本的任何一个实验中复制过来即可。这里我们打开光盘的“4，程序源码\标准例程-库函数版本\实验0-1Template工程模板-新建工程章节使用”工程目录，从里面复制SYSTEM文件夹到我们的Template工程模板根目录即可。操作过程如下图1-25和图1-26所示：

图 1-25 复制实验0-1的SYSTEM文件夹到工程根目录

图 1-26 复制SYSTEM文件夹之后的Template根目录文件夹结构

到这里，工程模板所需要的所有文件都已经复制进去。接下来，我们将在MDK中将这些文件添加到工程。

下面我们将前面复制过来的文件加入我们的工程中。右键点击Target1，选择ManageProjectItems，如下图1-27所示：

图 1-27 点击ManagementProjectItmes

ProjectTargets一栏，我们将Target名字修改为Template,然后在Groups一栏删掉一个SourceGroup1，建立四个Groups：USER，SYSTEM，CORE，和HALLIB。然后点击OK，可以看到我们的Target名字以及Groups情况如下图1-28和图1-29所示:

图 1-28 新建GROUP

图 1-29 查看工程Group情况

下面我们往Group里面添加我们需要的文件。我们按照步骤9的方法，右键点击点击Tempate，选择ManageProjectItems.然后选择需要添加文件的Group，这里第一步我们选择HALLIB，然后点击右边的AddFiles,定位到我们刚才建立的目录\HALLIB\Src下面，将里面所有的文件选中(Ctrl+A)，然后点击Add，然后Close.可以看到Files列表下面包含我们添加的文件，如下图1-30。这里需要说明一下，对于我们写代码，如果我们只用到了其中的某个外设，我们就可以不用添加没有用到的外设的库文件。例如我只用GPIO，我可以只用添加stm32f4xx_gpio.c而其他外设相关的可以不用添加。这里我们全部添加进来是为了后面方便，不用每次添加，当然这样的坏处是工程太大，编译起来速度慢，用户可以自行选择。

图 1-30 添加文件到HALLIB分组

这里有几个文件比较特殊,例如stm32f4xx_hal_dsc.c，stm32f4xx_hal_iptim.c和stm32f4xx_hal_msp_template.c三个文件不需要引入工程。stm32f4xx_hal_dsc.c是mipi接口相关函数，STM32F429没有这个接口，所以这个文件可以不用引入。stm32f4xx_hal_iptim.c文件是低功耗定时器相关函数，STM32F429也没有这个功能，也不需要引入。stm32f4xx_hal_msp_template.c文件内容是一些空函数，一般也不需要引入。删除某个方法如下图1-31所示：

图 1-31 删掉HALLIB分组中不需要的源文件

使用同样的方法删除文件stm32f4xx_hal_iptim.c和stm32f4xx_hal_msp_template.c即可。

用上面同样的方法，将Groups定位到CORE，USER和SYSTEM分组之下，添加需要的文件。CORE分组下面需要添加的文件为一些头文件以及启动文件startup_stm32f429xx.s(注意，默认添加的时候文件类型为.c，添加.h头文件和startup_stm32f429xx.s启动文件的时候，你需要选择文件类型为Allfiles才能看得到这些文件)。USER分组下面需要添加的文件USER目录下面所有的.c文件：main.c，stm32f4xx_hal_msp.c，stm32f4xx_it.c和system_stm32f4xx.c四个文件。SYSTEM分组下面需要添加SYSTEM文件夹下所有子文件夹内的.c文件，包括sys.c，usart.c和delay.c三个源文件。添加完必要的文件到工程之后，最后点击OK，回到工程主界面。操作过程如下图1-32~1-37：

图 1-32 添加文件到USER分组

图 1-33 文件添加到USER分组完成

使用同样的方法，选中CORE分组，点击AddFiles按钮，添加需要的文件到CORE分组。

图 1-34 添加.h头文件和启动文件到CORE分组

图 1-35 添加启动文件和头文件到CORE分组完成

最后添加文件到SYSTEM分组，这里需要注意，SYSTEM文件夹包含三个子文件夹sys,delay和usart。在添加文件的时候，需要分别定为到三个子文件夹内部，依次添加下面的.c文件即可。添加完成后如下图1-36所示：

图 1-36 添加文件到SYSTEM分组

添加完所有文件到工程中之后，我们点击OK按钮，回到MDK工程主界面，如下图1-37所示：

图 1-37 工程分组情况

接下来我们要在MDK里面设置头文件存放路径。也就是告诉MDK到那些目录下面去寻找包含了的头文件。这一步骤非常重要。如果没有设置头文件路径，那么工程会出现报错头文件路径找不到。具体操作如下图3.3.1.25和3.3.1.26所示，5步之后添加相应的头文件路径。

图 1-38 进入PATH配置界面

图 1-39 添加头文件路径到PATH

这里大家需要注意，这里添加的路径必须添加到头文件所在目录的最后一级。比如在SYSTEM文件夹下面有三个子文件夹下面都有.h头文件，这些头文件在工程中都需要使用到，所以我们必须将这三个子目录都包含进来。这里我们需要添加的头文件路径包括：\CORE，\USER\，\SYSTEM\delay，\SYSTEM\usart，SYSTEM\sys以及\HALLIB\Inc。这里还需要提醒大家，HAL库存放头文件子目录是\HALLIB\Inc，不是HALLIB\Src，其次很多时候都是这里弄错导致报很多奇怪的错误。添加完成之后如下图1-40所示。

图 1-40 添加头文件路径

接下来对于STM32F429系列的工程，还需要添加全局宏定义标识符，所谓全局宏定义标识符，就是在工程中任何地方都可见。添加方法是点击魔术棒之后，进入C/C++选项卡，然后在Define输入框连输入：USE_HAL_DRIVER,STM32F429xx。注意这里是两个标识符USE_HAL_DRIVER和STM32F429xx，他们之间是用逗号隔开的，请大家注意。这个字符串大家可以直接打开我们光盘的新建好的工程模板，从里面复制。模板存放目录为：4，程序源码\标准例程-库函数版本\实验0-1Template工程模板-新建工程章节使用。本步骤操作过程如下图1-41所示：

图 1-41 添加全局宏定义标识符

接下来我们要编译工程，在编译之前我们首先要选择编译中间文件编译后存放目录。前面我们讲过，MDK默认编译后的中间文件存放目录为USER目录下面的Listings和Objects子目录，这里为了和我们ALIENTEK工程结构保持一致，我们重新选择存放到目录OBJ目录之下。操作方法是点击魔术棒，然后选择“Output”选项下面的“Selectfolderforobjects…”,然后选择目录为我们上面新建的OBJ目录，然后依次点击OK即可。操作过程如下图1-42和1-43所示：

图 1-42 点击按钮“SelectFolderforObjects…”

图 1-43 选择OBJ目录为中间文件存放目录

选择完OBJ目录为编译中间文件存放目录之后，点击OK回到Output选项卡。这里我们还要勾上“CreateHEXFile”选项和BrowseInformation选项。CreateHEXFile选项选上是要求编译之后生成HEX文件。而BrowseInformation选项选上是方便我们查看工程中的一些函数变量定义等。具体操作方法如下图1-44所示：

图 1-44 勾选上CreateHEXfile和BrowseInformation选项

接下来在编译之前，我们先把main.c文件里面的内容替换为如下内容：

下面这段代码，大家如果不方便自己编写，可以直接打开我们光盘库函数源码目录“4，程序源码\标准例程-库函数版本\实验0-1Template工程模板-新建工程章节使用”找到我们已经新建好的工程模板USER目录下面的main.c文件，直接复制过来即可。

下面我们点击编译按钮编译工程，可以看到工程编译通过没有任何错误和警告。

图 1-45 编译工程

这里大家可能会遇到编译之后会有一个警告，警告的内容是：“warning:#1-D:lastlineoffileendswithoutanewline”。我们只需要在main.c函数结尾加一个回车即可解决，这个是MDK自身的BUG。

到这里，一个基于HAL库的工程模板就建立完成，同时在工程的OBJ目录下面生成了对应的hex文件。大家可以参考后面我们后面的内容，将hex文件下载到开发板，会发现两个led灯不停的闪烁现象。

这里还一个地方需要大家修改一下，那就是关于系统初始化之后的中断优先级分组组号的设置。默认情况下调用HAL初始化函数HAL_Init之后，会设置分组为组4，这里我们正点原子所有实验使用的是分组2，所以我们修改HAL_Init函数内部，重新设置分组为组2即可。具体方法是:打开HALLIB分组之下的stm32f4xx_hal.c文件，搜索函数HAL_Init，找到函数体，里面默认有这样一行代码：

HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

我们将入口参数NVIC_PRIORITYGROUP_4修改为NVIC_PRIORITYGROUP_2即可。

刚才，我们新建了一个基于HAL库的STM32F429工程模板，现在，我们将给大家讲解工程模板中的一些关键文件的作用以及整个工程模板程序运行流程。通过对本节内容的学习，大家将对STM32F429工程有一个比较全面的了解，为后面实验学习打下良好的基础。

在讲解之前我们需要说明一点，任何一个MDK工程，不管它有多复杂，无非就是一些.c源文件和.h头文件，还有一些类似.s的启动文件或者lib文件等。在工程中，他们通过各种包含关系组织在一起，被我们用户代码最终调用或者引用。所以我们必须了解这些文件的作用以及他们之间的包含关系，从而理解这个工程的运行流程，这样我们才能在项目开发中得心应手。

HAL库关键文件介绍如下表1-1所示：

表 1-1 HAL库文件介绍

文件	描述
stm32f4xx_hal_ppp.c/.h	基本外设的操作API,ppp代表任意外设。其中stm32f4xx_hal_cortex.c/.h比较特殊，它是一些Cortex内核通用函数声明和定义，例如中断优先级NVIC配置，系统软复位以及Systick配置等。
stm32f4xx_hal_ppp_ex.c/.h	拓展外设特性的API。
sm32f4xx_hal.c	包含HAL通用API（比如 HAL_Init,HAL_DeInit,HAL_Delay等）。
stm32f4xx_hal.h	HAL的头文件，它应被客户代码所包含。
stm32f4xx_hal_conf.h	HAL的配置文件，主要用来选择使能何种外设以及一些时钟相关参数设置。其本身应该被客户代码所包含。
stm32f4xx_hal_def.h	包含HAL的通用数据类型定义和宏定义
stm32f4xx_II_ppp.c/.h	在一些复杂外设中实现底层功能，它们在stm32f4xx_hal_ppp.c中被调用

stm32f4xx_it.c/stm32f4xx_it.h文件

这两个文件非常简单，也非常好理解。stm32f4xx_it.h中主要是一些中断服务函数的申明。stm32f4xx_it.h中是这些中断服务函数定义，而这些函数定义除了Systick中断服务函数SysTick_Handler外基本都是空函数，没有任何控制逻辑。一般情况下，我们可以去掉这两个文件，然后把中断服务函数写在工程中的任何一个可见文件中。

stm32f4xx.h头文件

头文件stm32f4xx.h内容看似非常少，却非常重要，它是所有stm32f4系列的顶层头文件。使用STM32F4任何型号的芯片，都需要包含这个头文件。同时，因为stm32f4系列芯片型号非常多，ST为每种芯片型号定义了一个特有的片上外设访问层头文件，比如STM32F429系列，ST定义了一个头文件stm32f429xx.h，然后stm32f4xx.h顶层头文件会根据工程芯片型号，来选择包含对应芯片的片上外设访问层头文件。我们可以打开stm32f4xx.h头文件可以看到，里面有如下几行代码：

这几行代码非常好理解，我们以stm3f429为例，如果定义了宏定义标识符STM32F429xx，那么头文件stm32f4xx.h将会包含头文件stm32f429xx.h。实际上，在我们上一节新建工程的时候，我们在C/C++选项卡里面输入的全局宏定义标识符中就包含了标识符STM32F429xx

（请参考图3.3.1.28）。所以头文件stm32f429xx.h一定会被整个工程所引用。

stm32f429xx.h头文件

根据前面的讲解，stm32f429xx.h是stm32f429系列芯片通用的片上外设访问层头文件，只要我们进行STM32F429开发，就必然要使用到该文件。打开该文件我们可以看到里面主要是一些结构体和宏定义标识符。这个文件的主要作用是寄存器定义声明以及封装内存操作。在后面寄存器地址名称映射分析小节我们会给大家详细讲解。

system_stm32f4xx.c/system_stm32f4xx.h文件

头文件system_stm32f4xx.h和源文件system_stm32f4xx.c主要是声明和定义了系统初始化函数SystemInit以及系统时钟更新函数SystemCoreClockUpdate。SystemInit函数的作用是进行时钟系统的一些初始化操作以及中断向量表偏移地址设置，但它并没有设置具体的时钟值，这是与标准库的最大区别，在使用标准库的时候，SystemInit函数会帮我们配置好系统时钟配置相关的各个寄存器。在启动文件startup_stm32f429xx.s中会设置系统复位后，直接调用SystemInit函数进行系统初始化。SystemCoreClockUpdate函数是在系统时钟配置进行修改后，调用这个函数来更新全局变量SystemCoreClock的值，变量SystemCoreClock是一个全局变量，开放这个变量可以方便我们在用户代码中直接使用这个变量来进行一些时钟运算。

stm32f4xx_hal_msp.c文件

MSP，全称为MCUsupportpackage,关于怎么理解MSP，我们后面在讲解程序运行流程的时候会给大家举例详细讲解，这里大家只需要知道，函数名字中带有MspInit的函数，它们的作用是进行MCU级别硬件初始化设置，并且它们通常会被上一层的初始化函数所调用，这样做的目的是为了把MCU相关的硬件初始化剥夺出来，方便用户代码在不同型号的MCU上移植。stm32f4xx_hal_msp.c文件定义了两个函数HAL_MspInit和HAL_MspDeInit。这两个函数分别被文件stm32f4xx_hal.c中的HAL_Init和HAL_DeInit所调用。HAL_MspInit函数的主要作用是进行MCU相关的硬件初始化操作。例如我们要初始化某些硬件，我们可以硬件相关的初始化配置写在HAL_MspDeinit函数中。这样的话，在系统启动后调用了HAL_Init之后，会自动调用硬件初始化函数。实际上，我们在工程模板中直接删掉stm32f4xx_hal_msp.c文件也不会对程序运行产生任何影响。对于这个文件存在的意义，我们在后面讲解完程序运行流程之后，大家会有更加清晰的理解。

startup_stm32f429xx.s启动文件

STM32系列所有芯片工程都会有一个.s启动文件。对于不同型号的stm32芯片启动文件也是不一样的。我们的开发板是STM32F429系列，所以我们需要使用与之对应的启动文件startup_stm32f429xx.s。启动文件的作用主要是进行堆栈的初始化，中断向量表以及中断函数定义等。启动文件有一个很重要的作用就是系统复位后引导进入main函数。打开启动文件startup_stm32f429xx.s，可以看到下面几行代码：

Reset_Handler在我们系统启动的时候会执行，这几行代码的作用是在系统启动之后，首先调用SystemInit函数进行系统初始化，然后引导进入main函数执行用户代码。

接下来我们看看HAL库工程模板中各个文件之间的包含关系，如下图1-46所示：

图 1-46 HAL库工程文件包含关系

从上面工程文件包含关系图可以看出，顶层头文件stm32f4xx.h直接或间接包含了其他所有工程必要头文件，所以在我们的用户代码中，我们只需要包含顶层头文件stm32f4xx.h即可。这里我们还需要说明一下，在我们ALIENTEK提供的SYSTEM文件夹内部的sys.h头文件中，我们默认包含了stm32f4xx.h头文件，所以在我们用户代码中，只需要包含sys.h头文件即可，当然也可以直接包含顶层头文件stm32f4xx.h。关于工程模板中关键文件内容我们就给大家介绍到这里。

第二种方法：使用CUbe

Keil的使用技巧

代码编辑/查看技巧

用Tab键规范代码

MDK的TAB键支持块操作，选中一段内容按Tab键可以让一片代码整体右移固定的几个位，也可以通过SHIFT+TAB键让它们整体左移固定的几个位。通过TAB键，我们可以快速将代码修改为比较规范的格式。

如图1-14，这样的代码很不规范，当工程量较大时，我们读代码会非常吃力。

图 1-14 无法快速区分各函数

当我们用Tab键对一段段函数调整后，代码的可读性就变得很高。

图 1-15 规范代码

快速定位到函数或变量被定义的地方

在编写或调试代码的时候，有时需要查看某个函数、变量或数组是在哪个地方定义的，具体内容是什么。当代码量比较大的时候，如果用滑轮慢慢找，要花很久的时间。MDK提供了快速定位功能，右键想要查看的函数或变量，然后弹出如图所示的菜单栏。

图 1-16 右击出现菜单栏

如图1-16，我们点击GotoDefinitionOf‘STM32_Clock_Init’，就可以快速跳到STM32_Clock_Init的函数定义（如果无法定位，请在OptionsforTarget的Output选项卡里面勾选BrowseInformation选项，再编译一下）。对于变量或数组，该操作同样适用，我们只需点击它的变量或数组名。

返回键

返回键可以让我们快速定位到上一次操作过的位置，比如我们利用刚才的GotoDefinition看完函数/变量的定义后，又想返回之前的代码继续看，此时我们可以通过IDE上的按钮（Backtopreviousposition）快速返回之前的位置，这个按钮非常好用！

快速打开头文件

在将光标放到要打开的引用头文件上，然后右键选择OpenDocument“XXX”，就可以快速打开这个文件了（XXX是你要打开的头文件名字）。如图1-17所示：

图 1-17 快速打开头文件

查找替换功能

该功能简单强大，和WORD等很多文档操作的替换功能是差不多的，请自己摸索。

我们点这个望远镜的图标或用快捷键“CTRL+H”，就会调出如图1-18所示界面，我们可以查找并替换。

图 1-18 查找替换

通过右边的Find、FindinFiles，我们可以具体在某个文件查找我们想要的字段或在整个工程查找我们想要的字段。

代码提示及语法检测

代码提示

MDK5可以在写函数或变量名时提示名称，十分便捷，我们简单说一下如何设置。点击这个扳手，打开配置对话框，选择TextCompletion选项卡，如图1-19。

图 1-19 TextCompletion选项卡设置

Strut/ClassMembers，用于开启结构体/类成员提示功能。

FunctionParameters，用于开启函数参数提示功能。

Symbolsafterxxcharacters，用于开启代码提示功能，即在输入多少个字符以后，提示匹配的内容（比如函数名字、结构体名字、变量名字等），这里默认设置3个字符以后，就开始提示。如图1-20所示：

图 1-20 代码提示

语法检测

图1-19中的DynamicSyntaxChecking，则用于开启动态语法检测，比如编写的代码存在语法错误的时候，会在对应行前面出现图标，如出现警告，则会出现图标，将鼠标光标放图标上面，则会提示产生的错误/警告的原因，如图1-21所示。

图 1-21 语法动态检测功能

文本美化

文本美化，主要是设置一些关键字、注释、数字等的颜色和字体。我们在打开工程模板后，可以看到其中的关键字和注释等字体的颜色很漂亮，而MDK提供了我们自定义字体颜色的功能。我们可以在工具条上点击(配置对话框)弹出如图1-22所示界面：

图 1-22 配置框

在该对话框中，先设置Encoding为:ChineseGB2312(Simplified)，然后设置Tabsize为：4。以更好的支持简体中文（否则，拷贝到其他地方的时候，中文可能是一堆的问号），同时TAB间隔设置为4个单位。然后，选择：Colors&Fonts选项卡，在该选项卡内，我们就可以设置自己的代码的子体和颜色了。由于我们使用的是C语言，故在Window下面选择：C/C++EditorFiles在右边就可以看到相应的元素了。如图1-23所示：

图 1-23 Colors&Fonts选项卡

然后点击各个元素修改为你喜欢的颜色（注意双击，且有时候可能需要设置多次才生效，MDK的bug），当然也可以在Font栏设置你字体的类型，以及字体的大小等。设置成之后，点击OK，就可以在主界面看到你所修改后的结果，例如我修改后的代码显示效果如图1-24所示：

图 1-24 设置完后显示效果

字体大小，则可以直接按住：ctrl+鼠标滚轮，进行放大或者缩小，或者也可以在刚刚的配置界面设置字体大小。

细心的同学可能会发现，上面的代码里面有一个u8，还是黑色的，这是一个用户自定义的关键字，为什么不显示蓝色（假定刚刚已经设置了用户自定义关键字颜色为蓝色）呢？这就又要回到我们刚刚的配置对话框了，单这次我们要选择UserKeywords选项卡，同样选择：C/C++EditorFiles，在右边的UserKeywords对话框下面输入你自己定义的关键字，如图1-25所示：

图 1-25 用户自定义关键字

图1-25中我定义了u8、u16、u32等3个关键字，这样在以后的代码编辑里面只要出现这三个关键字，肯定就会变成蓝色。点击OK，再回到主界面，可以看到u8变成了蓝色了，如图1-26所示：

图 1-26 设置完后显示效果

其实这个编辑配置对话框里面，还可以对其他很多功能进行设置，请同学们自己摸索，我们只介绍几个最常用的。

硬件篇（接收端）

跑马灯实验

任何一个单片机，最简单的操作莫过于IO口的高低电平控制了，本章将通过一个经典的跑马灯程序，带大家开启STM32F429之旅，通过本章的学习，你将了解到STM32F429的IO口作为输出使用的方法。在本章中，我们将通过代码控制ALIENTEK阿波罗STM32开发板上的两个LED灯DS0和DS1交替闪烁，实现类似跑马灯的效果。本章分为如下五个小节：

STM32F4IO口简介

硬件设计

软件设计

下载验证

STM32CubeMX配置IO口输入

一、STM32F4IO简介

本章将要实现的是控制ALIENTEK阿波罗STM32开发板上的两个LED实现一个类似跑马灯的效果，该实验的关键在于如何控制STM32F429的IO口输出。了解了STM32F429的IO口如何输出的，就可以实现跑马灯了。通过这一章的学习，你将初步掌握STM32F429基本IO口的使用，而这是迈向STM32F429的第一步。

这一章节因为是第一个实验章节，所以我们在这一章将讲解一些知识为后面的实验做铺垫。为了小节标号与后面实验章节一样，这里我们不另起一节来讲。

在讲解STM32F429的GPIO之前，首先打开我们光盘的第一个HAL库版本实验工程跑马灯实验工程（光盘目录为:“4，程序源码\标准例程-库函数版本\实验1跑马灯/USER/LED.uvproj”）,可以看到我们的实验工程目录如下图6-1所示：

图 2-1 跑马灯实验目录结构

接下来我们逐一讲解一下我们的工程目录下面的组以及重要文件。

①组HALLIB下面存放的是ST官方提供的HAL库文件，每一个源文件stm32f4xx_hal_ppp.c都对应一个头文件stm32f4xx_hal_ppp.h。分组内的源文件我们可以根据工程需要添加和删除。这里对于跑马灯实验，我们需要添加11个源文件。

②组CORE下面存放的是固件库必须的核心头文件和启动文件。这里面的文件用户不需要修改。大家可以根据自己的芯片型号选择对应的启动文件。

③组SYSTEM是ALIENTEK提供的共用代码，这些代码在第五章都有详细讲解。

④组HARDWARE下面存放的是每个实验的外设驱动代码，他的实现是通过调用HALLIB下面的HAL库文件函数实现的，比如led.c中函数调用stm32f4xx_hal_gpio.c内定义的函数对led进行初始化，这里面的函数是讲解的重点。后面的实验中可以看到会引入多个源文件。

⑤组USER下面存放的主要是用户代码。但是system_stm32f4xx.c文件用户不需要修改，同时stm32f4xx_it.c里面存放的是中断服务函数，这两个文件的作用在3.3节有讲解。main.c函数主要存放的是主函数了。

工程分组情况我们就讲解到这里，接下来我们就要进入我们跑马灯实验的讲解部分了。这里需要说明一下，我们在讲解HAL库之前会首先对重要寄存器进行一个讲解，这样是为了大家对寄存器有个初步的了解。大家学习HAL库，并不需要记住每个寄存器的作用，而只是通过了解寄存器来对外设一些功能有基本的了解，这样对以后的学习也很有帮助。

相对于STM32F1来说，STM32F4的GPIO设置显得更为复杂，也更加灵活，尤其是复用功能部分，比STM32F1改进了很多，使用起来更加方便。

STM32F4每组通用I/O端口包括4个32位配置寄存器（MODER、OTYPER、OSPEEDR和PUPDR）、2个32位数据寄存器（IDR和ODR）、1个32位置位/复位寄存器(BSRR)、

1个32位锁定寄存器(LCKR)和2个32位复用功能选择寄存器（AFRH和AFRL）等。这样，STM32F4每组IO有10个32位寄存器控制，其中常用的有4个配置寄存器+2个数

据寄存器+2个复用功能选择寄存器，共8个，如果在使用的时候，每次都直接操作寄存器配置IO，代码会比较多，也不容易记住，所以我们在讲解寄存器的同时会讲解是用库函数配置IO的方法。

同STM32F1一样，STM32F4的IO可以由软件配置成如下8种模式中的任何一种：

1、输入浮空

2、输入上拉

3、输入下拉

4、模拟输入

5、开漏输出

6、推挽输出

7、推挽式复用功能

8、开漏式复用功能

关于这些模式的介绍及应用场景，我们这里就不详细介绍了，感兴趣的朋友，可以看看这个帖子了解下：http://www.openedv.com/posts/list/32730.htm。接下来我们详细介绍IO配置常用的8个寄存器：MODER、OTYPER、OSPEEDR、PUPDR、ODR、IDR、AFRH和AFRL。

同时讲解对应的HAL库配置方法。

首先看MODER寄存器，该寄存器是GPIO端口模式控制寄存器，用于控制GPIOx（STM32F4最多有9组IO，分别用大写字母表示，即x=A/B/C/D/E/F/G/H/I，下同）的工作模式，该寄存器各位描述如表表2-1所示：

表 2-1 GPIOxMODER寄存器各位描述

该寄存器各位在复位后，一般都是0（个别不是0，比如JTAG占用的几个IO口），也就是默认条件下一般是输入状态的。每组IO下有16个IO口，该寄存器共32位，每2个位控制1个IO，不同设置所对应的模式见表2-1描述。

然后看OTYPER寄存器，该寄存器用于控制GPIOx的输出类型，该寄存器各位描述见表表2-2所示：

表2-2 GPIOxOTYPER寄存器各位描述

该寄存器仅用于输出模式，在输入模式（MODER[1:0]=00/11时）下不起作用。该寄存器低16位有效，每一个位控制一个IO口。设置为0是推挽输出，设置为1是开漏输出。复位后，该寄存器值均为0，也就是在输出模式下IO口默认为推挽输出。

然后看OSPEEDR寄存器，该寄存器用于控制GPIOx的输出速度，该寄存器各位描述见表表2-3所示：

表2-3 GPIOxOSPEEDR寄存器各位描述

该寄存器也仅用于输出模式，在输入模式（MODER[1:0]=00/11时）下不起作用。该寄存器每2个位控制一个IO口，复位后，该寄存器值一般为0。

然后看PUPDR寄存器，该寄存器用于控制GPIOx的上拉/下拉，该寄存器各位描述见表表2-4所示：

表2-4 GPIOxPUPDR寄存器各位描述

该寄存器每2个位控制一个IO口，用于设置上下拉，这里提醒大家，STM32F1是通过ODR寄存器控制上下拉的，而STM32F4则由单独的寄存器PUPDR控制上下拉，使用起来更加灵活。复位后，该寄存器值一般为0。

前面，我们讲解了4个重要的配置寄存器。顾名思义，配置寄存器就是用来配置GPIO的相关模式和状态，接下来我们讲解怎么在HAL库中初始化GPIO配置。

GPIO相关的函数和定义分布在HAL库文件stm32f4xx_hal_gpio.c和头文件stm32f4xx_hal_gpio.h文件中。

在HAL库中，操作四个配置寄存器初始化GPIO是通过HAL_GPIO_Init函数完成：

该函数有两个参数，第一个参数是用来指定需要初始化的GPIO对应的GPIO组，取值范围为GPIOA~GPIOK。第二个参数为初始化参数结构体指针，结构体类型为GPIO_InitTypeDef。下面我们看看这个结构体的定义。首先我们打开我们光盘的跑马灯实验，然后找到HALLIB组下面的stm32f4xx_hal_gpio.c文件，定位到HAL_GPIO_Init函数体处，双击入口参数类型GPIO_InitTypeDef后右键选择“Gotodefinitionof…”可以查看结构体的定义如下：

结构体有5个成员变量，关于怎么来确定这5个成员变量的取值范围，请参考4.7小节内容。下面我们通过一个GPIO初始化实例来讲解这个结构体的成员变量的含义。

初始化GPIO的常用格式是：

上面代码的意思是设置PB0端口为推挽输出模式，输出速度为高速，上拉。

从上面初始化代码可以看出，结构体GPIO_Initure的第一个成员变量Pin用来设置是要初始化哪个或者哪些IO口。第二个成员变量Mode是用来设置对应IO端口的输出输入端口模式，这个变量实际配置的是我们前面讲解的GPIOx的MODER寄存器。第三个成员变量Pull是用来设置上拉还是下拉，配置的是GPIOxPUPDR寄存器。第四个成员变量Speed用来设置输出速度，配置的是GPIOxOSPEEDR寄存器。第五个成员变量Alternate，我们在4.4小节引脚复用器和映射已经讲解，它是用来设置引脚的复用映射的。

这些入口参数的取值范围怎么定位，怎么快速定位到这些入口参数取值范围的枚举类型，在我们上面章节4.7的“快速组织代码”章节有讲解，不明白的朋友可以翻回去看一下，这里我们就不重复讲解，在后面的实验中，我们也不会再重复讲解定位每个参数取值范围的方法。看完了GPIO的参数配置寄存器，接下来我们看看GPIO输入输出电平控制相关的寄存器。

首先我们看ODR寄存器，该寄存器用于控制GPIOx的输出电平，该寄存器各位描述见表2-5所示：

表2-5 GPIOxODR寄存器各位描述

该寄存器用于设置某个IO输出低电平(ODRy=0)还是高电平(ODRy=1)，该寄存器也仅在输出模式下有效，在输入模式（MODER[1:0]=00/11时）下不起作用。该寄存器在HAL库中使用不多，操作这个寄存器的库函数主要是HAL_GPIO_TogglePin函数：

该函数是通过操作ODR寄存器，达到取反IO口输出电平的功能。

接下来我们看看另一个非常重要的寄存器BSRR，它叫置位/复位寄存器。该寄存器和ODR寄存器具有类似的作用，都可以用来设置GPIO端口的输出位是1还是0。寄存器描述如下：

表2-6 BSRR寄存器各位描述

对于低16位（0-15），我们往相应的位写1，那么对应的IO口会输出高电平，往相应的位写0，对IO口没有任何影响。高16位（16-31）作用刚好相反，对相应的位写1会输出低电平，写0没有任何影响。也就是说，对于BSRR寄存器，你写0的话，对IO口电平是没有任何影响的。我们要设置某个IO口电平，只需要相关位设置为1即可。而ODR寄存器，我们要设置某个IO口电平，我们首先需要读出来ODR寄存器的值，然后对整个ODR寄存器重新赋值来达到设置某个或者某些IO口的目的，而BSRR寄存器，我们就不需要先读，而是直接设置即可，这在多任务实时操作系统中作用很大。

BSRR寄存器使用方法如下：

库函数操作BSRR寄存器来设置IO电平的函数为：

该函数用来设置一组IO口中的一个或者多个IO口的电平状态。比如我们要设置GPIOB.5输出高，方法为：

设置GPIOB.5输出低电平，方法为：

接下来我们看看IDR寄存器，该寄存器用于读取GPIOx的输入数据，该寄存器各位描述见表6.1.8所示：

表2-7 GPIOxIDR寄存器各位描述

该寄存器用于读取某个IO的电平，如果对应的位为0(IDRy=0)，则说明该IO输入的是低电平，如果是1(IDRy=1)，则表示输入的是高电平。HAL库操作该寄存器读取IO输入数据相关函数：

该函数用来读取一组IO下一个或者多个IO口电平状态。比如我们要读取GPIOF.5的输入电平，方法为：

该函数返回值就是IO口电平状态。

最后我们来看看2个32位复用功能选择寄存器（AFRH和AFRL），这两个寄存器是用来设置IO口的复用功能的。实际上，在我们调用函数HAL_GPIO_Init的时候，如果我们设置了初始化结构体成员变量Mode为复用模式，同时设置了Alternate的值，那么会在该函数内部自动设置这两个寄存器的值，达到设置端口复用映射的目的。关于这两个寄存器的详细配置以及相关库函数的使用，在我们前面4.4小节IO引脚复用和映射有详细讲解，这里我们只是简要说明一下。

GPIO相关的函数我们先讲解到这里。虽然IO操作步骤很简单，这里我们还是做个概括性的总结，操作步骤为：

使能IO口时钟，调用函数为HAL_RCC_GPIOX_CLK_ENABLE(其中X=A~K)。

初始化IO参数。调用函数HAL_GPIO_Init();

操作IO输入输出。操作IO的方法就是上面我们讲解的方法。

上面我们讲解了STM32F4IO口的基本知识以及HAL库操作GPIO的一些函数方法，下面我们来讲解我们的跑马灯实验的硬件和软件设计。

二、硬件设计

本章用到的硬件只有LED（DS0和DS1）。其电路在ALIENTEK阿波罗STM32开发板上默认是已经连接好了的。DS0接PB1，DS1接PB0。所以在硬件上不需要动任何东西。其连接原理图如图2-2下：

图2-2 LED与STM32F429连接原理图

三、软件设计

这是我们学习的第一个实验，所以我会手把手教大家怎么从我们前面讲解的Template工程模板一步一步加入HAL库以及led相关的驱动函数到我们工程，使之跟我们光盘的跑马灯实验工程一模一样。首先大家打开我们3.3小节新建的HAL库工程模板。如果您还没有新建，也可以直接打开我们光盘已经新建好了的工程模板，路径为：“\4，程序源码\标准例程-库函数版本\实验0-1Template工程模板-新建工程章节使用”（注意，是直接点击工程下面的USER目录下面的Tempate.uvprojx。）。

大家可以看到，我们模板里面的HALLIB分组下面，我们引入了所有的HAL库源文件和对应的头文件，如下图2-3：

图2-3 Template模板工程结构

实际上，这些大家可以根据工程需要添加，比如跑马灯实验并没有用到ADC，我们可以在工程中删掉文件stm32f4xx_hal_adc.c，这样可以大大减少工程编译时间。跑马灯实验我们一共使用到HAL库中9个源文件，具体哪9个请直接参考我们跑马灯实验工程，其他不用的源文件大家可以直接在工程中删除。在工程的ManageProjectItems页面，选择要删除文件所在的分组，然后选中文件点击删除按钮即可。具体操作方法如下图2-4所示：

图2-4 删除工程分组中的文件

接下来我们进入我们工程的目录，在工程根目录文件夹下面新建一个HARDWARE的文件夹，用来存储以后与硬件相关的代码。然后在HARDWARE文件夹下新建一个LED文件夹，用来存放与LED相关的代码。如图2-5所示：

图2-5 新建HARDWARE文件夹

接下来，我们回到我们的工程(如果是使用的上面新建的工程模板，那么就是Template.uvproj，大家可以将其重命名为LED.uvproj)，按按钮新建一个文件，然后按保存在HARDWARE->LED文件夹下面，保存为led.c，操作步骤如下图2-6和2-7：

图2-6 新建文件

图2-7 保存led.c

然后在led.c文件中输入如下代码(代码大家可以直接打开我们光盘的实验1跑马灯实验，从led.c文件内复制过来)，输入后保存即可：

该代码里面就包含了一个函数voidLED_Init(void)，该函数通过调用函数HAL_GPIO_Init实现配置PB0和PB1为推挽输出。关于函数HAL_GPIO_Init的使用方法在6.1小节有详细讲解。这里需要注意的是：在配置STM32外设的时候，任何时候都要先使能该外设的时钟。使能GPIOB时钟方法为：

在设置完时钟之后，LED_Init调用HAL_GPIO_Init函数完成对PB0和PB1的初始化配置，然后调用函数HAL_GPIO_WritePin控制LED0和LED1输出1（LED灭）。至此，两个LED的初始化完毕。这样就完成了对这两个IO口的初始化。这段代码的具体含义，大家可以看前面第一小节，我们有详细的讲解。

保存led.c代码，然后我们按同样的方法，新建一个led.h文件，也保存在LED文件夹下面。在led.h中输入如下代码：

这段代码里面最关键就是2个宏定义：

这里使用的是位带操作来实现操作某个IO口，关于位带操作前面第五章5.2.1已经有详细介绍，这里不再多说。需要说明的是，这里同样可以使用HAL库操作来实现IO口操作。如下：

有兴趣的朋友不妨修改我们的位带操作为库函数直接操作，这样也有利于学习。

将led.h也保存一下。接着，我们在ManageProjectItmes管理里面新建一个HARDWARE的组，并把led.c加入到这个组里面，如图2-8所示：

图2-8 给工程新增HARDWARE组

单击OK，回到工程，然后你会发现在ProjectWorkspace里面多了一个HARDWARE组，在该组下面有一个led.c文件。如图2-9所示：

图2-9 工程主界面

然后用之前介绍的方法（在3.3节介绍的）将led.h头文件的路径加入到工程里面，然后点击OK回到主界面，如下图2-10所示：

图2-10 添加LED目录到PATH

回到主界面后，修改main.c文件内容如下（具体内容请参考跑马灯实验main.c文件）：

代码包含了#include"led.h"这句，使得LED0、LED1、LED_Init等能在main()函数里被调用。main()函数非常简单，先调用HAL_Init函数初始化HAL库，然后调用Stm32_Clock_Init进行时钟系统配置，然后调用delay_init()函数进行延时初始化。接着就是调用LED_Init()来初始化PB0和PB1为推挽输出模式，最后在while死循环里面实现LED0和LED1交替闪烁，间隔为500ms。

上面是通过库函数来实现的IO操作，我们也可以修改main()函数，直接通过位带操作达到同样的效果，大家不妨试试。位带操作的代码如下：

当然我们也可以通过直接操作相关寄存器的方法来设置IO，我们只需要将主函数修改为如下内容：

将主函数替换为上面代码，然后重新执行，可以看到，结果跟库函数操作和位带操作一样的效果。大家可以对比一下。这个代码在我们跑马灯实验的main.c文件中有注释掉，大家可以替换试试。

然后按，编译工程，得到结果如图2-11所示：

图2-11编译结果

可以看到没有错误，也没有警告。从编译信息可以看出，我们的代码占用FLASH大小为：

5484字节（4038+478），所用的SRAM大小为：1928个字节（1896+32）。这里我们解释一下，编译结果里面的几个数据的意义：

Code：表示程序所占用FLASH的大小（FLASH）。

RO-data：即ReadOnly-data，表示程序定义的常量（FLASH）。

RW-data：即ReadWrite-data，表示已被初始化的变量（SRAM）

ZI-data：即ZeroInit-data，表示未被初始化的变量(SRAM)

有了这个就可以知道你当前使用的flash和sram大小了，所以，一定要注意的是程序的大小不是.hex文件的大小，而是编译后的Code和RO-data之和。

接下来，大家就可以下载验证了。如果有ST-LINK，则可以用ST-LINK进行在线调试（需要先下载代码），单步查看代码的运行，STM32F4的在线调试方法介绍请参见相关小节。

四、下载验证

这里我们使用flymcu下载（也可以通过ST-LINK等仿真器下载，具体方法请参考3.4.2小节），如图2-11所示：

图2-11 利用flymcu下载代码

下载完之后，运行结果如图6.4.2所示，LED0和LED1循环闪烁：

图2-12程序运行结果

至此，我们第一章关于使用HAL操作GPIO口的知识就给大家讲解到这里，本章作为STM32F4的入门第一个例子，介绍了STM32F4的IO口的使用及注意事项，同时巩固了前面的学习，希望大家好好理解一下。

五、STM32CubeMX配置IO口输入

在讲解完使用HAL库操作GPIO口之后，本小节我们教大家怎么使用STM32CubeMX图形化配置工具配置GPIO初始化过程。关于STM32CubeMX工具的入门使用在前面4.8小节我们有手把手教大家入门该工具，本小节我们就不重复讲解入门部分，我们直接讲解在STM32CubeMX工具中怎么来配置GPIO口的相关参数。

首先大家打开STM32CubeMX工具，参考4.8小节内容进行RCC相关配置。这里大家也可以直接打开4.8小节的STM32CubeMX工程直接在工程上面修改，该工程保存的光盘目录为：“4，程序源码\标准例程-库函数版本\实验0-3Template工程模板-使用STM32CubeMX配置”。

这里大家会发现，我们在4.8小节实际上已经讲解了GPIO的配置，并且同样是以PB0和PB1为例。这里我们将详细解析在STM32CubeMX中配置IO口详细参数的过程。使用STM32CubeMX配置GPIO口的步骤如下：

第一步，打开STM32CubeMX工具，在引脚图中选择要配置的IO口。这里我们选择PB0为例，在弹出的下拉菜单中选择要配置的IO口模式，如下图2-13所示：

图2-13 选择IO口模式

从上图可以看出，这里我们除了配置IO口为输入输出之外，还可以选择IO口的复用功能或者作为外部中断引脚功能，比如我们要选择IO口复用为ADC1的通道8引脚，那么我们只需要选选项ADC1_IN8即可。对于本章跑马灯实验，PB0是作为输出，所以我们选GPIO_Output即可。

进入Configuration->GPIO，在弹出的界面配置IO口的详细参数。如下图2-14和图2-15所示：

图2-14 进入GPIO详细参数配置界面

图2-15配置IO口详细参数

在IO口详细参数配置界面，点击我们要配置的IO口，会在窗口下方显示该IO口配置的详细参数表，下面我们依次来解释这些配置项的含义：

①选项GPIOoutputlevel用来设置IO口初始化电平状态为High(高电平)还是Low(低电平)。本实验我们设置为默认输出高High。

②选项GPIOmode用来设置输出模式为OutputPushPull(推挽)还是OutputOpenDrain（开漏）。本实验我们设置为推挽输出OutputPushPull。

③选项GPIOPull-up/Pull-down用来设置IO口是上拉/下拉/没有上下拉。本实验我们设置为上拉（Pull-up）。

④选项Mzximumouputspeed用来设置输出速度为高速(Hign)/快速(Fast)/中速(Medium)/低速(Low)。本实验我们设置为高速High。

⑤选项UserLabel是用来设置初始化的IO口Pin值为我们自定义的宏，一般情况我们可以不用设置，有兴趣的同学可以自由设置后查看生成后的代码就很容易明白其含义。

配置完PB0后，PB1配置方法和参数都一模一样，这里我们就不重复配置。

然后我们参考4.8下节方法，生成工程源码。接下来打开工程的main.c文件可以看到，该文件内部由STM32CubeMX生成了函数MX_GPIO_Init，内容如下：

该函数的作用跟前面讲解的跑马灯实验中LED_Init函数作用一模一样，有兴趣的同学可以直接修改跑马灯实验工程源码，把LED_Init函数内容修改为MX_GPIO_Init内容，大家会发现实验效果一模一样。

一般情况下，STM32CubeMX的主要作用是配置时钟系统和外设初始化函数。所以我们对外设进行配置之后，生成外设初始化代码，然后把该代码应用到我们工程即可。关于本章中使用STM32CubeMX配置GPIO方法，我们就给大家讲解到这里。

按键输入实验

上一章，我们介绍了STM32F4的IO口作为输出的使用，这一章，我们将向大家介绍如何使用STM32F4的IO口作为输入用。在本章中，我们将利用板载的4个按键，来控制板载的两个LED的亮灭。通过本章的学习，你将了解到STM32F4的IO口作为输入口的使用方法。本章分为如下几个小节：

STM32F4IO口简介

硬件设计

软件设计

下载验证

STM32CubeMX配置IO口输出

一、STM32F4IO口简介

STM32F4的IO口在上两章已经有了比较详细的介绍，这里我们不再多说。STM32F4的IO口做输入使用的时候，是通过调用函数HAL_GPIO_ReadPin()来读取IO口的状态的。了解了这点，就可以开始我们的代码编写了。

这一章，我们将通过ALIENTEK阿波罗STM32开发板上载有的4个按钮（KEY_UP、KEY0、KEY1和KEY2），来控制板上的2个LED（DS0和DS1），其中KEY_UP控制DS0，DS1互斥点亮；KEY2控制DS0，按一次亮，再按一次灭；KEY1控制DS1，效果同KEY2；KEY0则同时控制DS0和DS1，按一次，他们的状态就翻转一次。

二、硬件设计

本实验用到的硬件资源有：

指示灯DS0、DS1。

4个按键：KEY0、KEY1、KEY2、和KEY_UP。

DS0、DS1和STM32F429的连接在上一章已经介绍过了，在阿波罗STM32开发板上的按键KEY0连接在PH3上、KEY1连接在PH2上、KEY2连接在PC13上、KEY_UP连接在PA0上。如图7.2.1所示：

图2-16 按键与STM32F429连接原理图

这里需要注意的是：KEY0、KEY1和KEY2是低电平有效的，而KEY_UP是高电平有效的，并且外部都没有上下拉电阻，所以，需要在STM32F429内部设置上下拉。

三、软件设计

从这章开始，我们的软件设计主要是通过直接打开我们光盘的实验工程，而不再讲解怎么加入文件和头文件目录。工程中添加相关文件的方法在我们前面两个实验已经讲解非常详细。

打开按键实验工程可以看到，工程引入了key.c文件以及头文件key.h。下面我们首先打开key.c文件，关键代码如下：

这段代码包含2个函数，voidKEY_Init(void)和u8KEY_Scan(u8mode)，KEY_Init是用来初始化按键输入的IO口的。实现PA0、PC13、PH2和PH3的输入设置，这里和第六章的输出配置差不多，只是这里用来设置成的是输入而第六章是输出。

KEY_Scan函数，则是用来扫描这4个IO口是否有按键按下。KEY_Scan函数，支持两种扫描方式，通过mode参数来设置。

当mode为0的时候，KEY_Scan函数将不支持连续按，扫描某个按键，该按键按下之后必须要松开，才能第二次触发，否则不会再响应这个按键，这样的好处就是可以防止按一次多次触发，而坏处就是在需要长按的时候比较不合适。

当mode为1的时候，KEY_Scan函数将支持连续按，如果某个按键一直按下，则会一直返回这个按键的键值，这样可以方便的实现长按检测。

有了mode这个参数，大家就可以根据自己的需要，选择不同的方式。这里要提醒大家，因为该函数里面有static变量，所以该函数不是一个可重入函数，在有OS的情况下，这个大家要留意下。同时还有一点要注意的就是，该函数的按键扫描是有优先级的，最优先的是KEY0，第二优先的是KEY1，接着KEY2，最后是KEY_UP按键。该函数有返回值，如果有按键按下，则返回非0值，如果没有或者按键不正确，则返回0。

接下来我们看看头文件key.h里面的代码：

这段代码里面最关键就是4个宏定义：

PHin(3) //KEY0 按键PH3 PHin(2) //KEY1 按键PH2 PCin(13)//KEY2 按键PC13 PAin(0) //WKUP 按键 PA0 #define KEY0 #define KEY1 #define KEY2 #define WK_UP 这里使用的是调用HAL库函数HAL_GPIO_ReadPin来实现读取某个IO口的1个位的。同输出一样，上面的功能也同样可以通过位带操作来简单的实现：

用HAL库函数实现的好处是在各个STM32芯片上面的移植性非常好，不需要修改任何代码。用位带操作的好处是简洁，至于使用哪种方法，看各位的爱好了。

在key.h中，我们还定义了KEY0_PRES/KEY1_PRES/KEY2_PRES/WKUP_PRESS等4个宏定义，分别对应开发板四个按键（KEY0/KEY1/KEY2/KEY_UP）按键按下时KEY_Scan返回的值。通过宏定义的方式判断返回值，方便大家记忆和使用。

最后，我们看看main.c里面编写的主函数代码如下：

LED1=!LED1; LED0=!LED1; break;	case	KEY2_PRES:	//控制 LED0 翻转 LED0=!LED0; break;	case	KEY1_PRES:	//控制 LED1 翻转 LED1=!LED1; break;	case	KEY0_PRES:	//同时控制 LED0,LED1 翻转 LED0=!LED0; LED1=!LED1; break; //控制 LED0,LED1 互斥点亮 WKUP_PRES: //按键扫描 case //初始化 HAL 库 //设置时钟,180Mhz //初始化延时函数 //初始化 USART //初始化 LED //初始化按键 int main(void) { u8 key; HAL_Init(); Stm32_Clock_Init(360,25,2,8); delay_init(180); uart_init(115200); LED_Init(); KEY_Init(); while(1) { key=KEY_Scan(0); switch(key) {

} delay_ms(10); } }

主函数代码比较简单，先进行一系列的初始化操作，然后在死循环中调用按键扫描函数KEY_Scan()扫描按键值，最后根据按键值控制LED的状态。

四、下载验证

同样，我们还是通过flymcu下载代码，在下载完之后，我们可以按KEY0、KEY1、KEY2

和KEY_UP来看看DS0和DS1以及蜂鸣器的变化，是否和我们预期的结果一致？

五、STM32CubeMX配置IO口输出

上一章我们讲解了使用STM32CubeMX工具配置GPIO的一般方法。本章我们主要教大家配置IO口为输入模式，操作方法和配置IO口为输出模式基本一致。这里我们就直接列出IO口配置截图，具体方法请参考4.8小节和上一章跑马灯实验。

根据7.2小节讲解，阿波罗开发板上有4个按键，分别连接四个IO口PA0，PC13，PH2和PH3。其中WK_UP按键按下后对应的PA0输入为高电平，所以默认情况下，该IO口（PA0）要初始化为下拉输入，其他IO口初始化为上拉输入即可。

使用STM32CubeMX打开光盘工程模板（双击工程目录的Template.ioc），目录为“4，程序源码\标准例程-库函数版本\实验0-3Template工程模板-使用STM32CubeMX配置”。我们首先在IO口引脚图上，依次设置四个IO口为输入模式GPIO_Input。这里我们以PA0为例，操作方法如下图2-17所示：

图2-17 配置PA0为输入模式

同样的方法，我们依次配置PC13，PH2和PH3为输入模式。然后我们进入Configuration->GPIO配置界面，配置四个IO口详细参数。在配置界面点击PA0可以发现，当我们在前面设置IO口为输入GPIO_Input之后，其配置参数只剩下模式GPIOMode和上下拉GPIOPull-up/Pull-down，并且模式值中只有输入模式InputMode可选。这里，我们配置PA0为下拉输入，其他三个IO口配置为上拉输入即可。配置方法如下图2-18所示：

图2-18配置IO口详细参数

void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; /* GPIO Ports Clock Enable */ HAL_RCC_GPIOC_CLK_ENABLE(); HAL_RCC_GPIOH_CLK_ENABLE(); HAL_RCC_GPIOA_CLK_ENABLE(); /*Configure GPIO pin : PC13 */ GPIO_InitStruct.Pin = GPIO_PIN_13; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); 配置完成IO口参数之后，接下来我们同样生成工程。打开生成的工程会发现，main.c文件中添加了函数MX_GPIO_Init函数，内容如下：

/*Configure GPIO pin : PA0 */ GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLDOWN; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /*Configure GPIO pins : PH2 PH3 */ GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOH, &GPIO_InitStruct); }

该函数实现的功能和按键输入实验中KEY_Init函数实现的功能一模一样。有兴趣的同学可以直接复制该函数内容替换按键输入实验中的KEY_Init函数内容，替换后会发现实现现象完全一致。

使用STM32CubeMX配置IO口为输入模式方法就给大家介绍到这里。

LTDCLCD（RGB屏）实验

在之前，我们介绍了TFTLCD模块（MCU屏）的使用，但是高分辨率的屏（超过800*480），一般都没有MCU屏接口，而是使用RGB接口的，这种接口的屏，就需要用到STM32F429的LTDC来驱动了。在本章中，我们将使用阿波罗STM32F429开发板核心板上的LCD接口（仅支持RGB屏，本章介绍RGB屏的使用），来点亮LCD，并实现ASCII字符和彩色的显示等功能，并在串口打印LCDID，同时在LCD上面显示。本章分为如下几个部分：

RGBLCD&LTDC简介

硬件设计

软件设计

下载验证

一、RGBLCD&LTDC简介

本章我们将通过STM32F429的LTDC接口来驱动RGBLCD的显示，另外，STM32F429的LTDC还有DMA2D图形加速，我们也顺带进行介绍。本节分为三个部分，分别介绍RGBLCD、LTDC和DMA2D。

1.RGBLCD简介

在第之前的章节中，我们已经介绍过TFTLCD液晶了，实际上RGBLCD也是TFTLCD，只是接口不同而已。接下来我们简单介绍一下RGBLCD的驱动。

（1）RGBLCD的信号线

RGBLCD的信号线如表2-8所示：

表2-8 RGBLCD信号线

信号线	说明
R[0:7]	红色数据线，一般为8位
G[0:7]	绿色数据线，一般为8位
B[0:7]	蓝色数据线，一般为8位
DE	数据使能线
VS	垂直同步信号线
HS	水平同步信号线
DCLK	像素时钟信号线

一般的RGB屏都有如表19.1.1.1所示的信号线，有24根颜色数据线（RGB各站8根，即RGB888格式），这样可以表示最多1600W色，DE、VS、HS和DCLK，用于控制数据传输。

（2）RGBLCD的驱动模式

RGB屏一般有2种驱动模式：DE模式和HV模式。DE模式使用DE信号来确定有效数据（DE为高/低时，数据有效），而HV模式，则需要行同步和场同步，来表示扫描的行和列。

DE模式和HV模式的行扫描时序图（以800*480的LCD面板为例），如图2-18所示：

图2-18 DE/HV模式行扫描时序图

从图中可以看出，DE和HV模式，时序基本一样，DEN模式需要提供DE信号（DEN），而HV模式，则无需DE信号。图中的HSD即HS信号，用于行同步，注意：在DE模式下面，是可以不用HS信号的，即不接HS信号，液晶照样可以正常工作。

图中的thpw为水平同步有效信号脉宽，用于表示一行数据的开始；thb为水平后廊，表示从水平有效信号开始，到有效数据输出之间的像素时钟个数；thfp为水平前廊，表示一行数据结束后，到下一个水平同步信号开始之前的像素时钟个数；这几个时间非常重要，在配置LTDC的时候，需要根据LCD的数据手册，进行正确的设置。

图2-18仅是一行数据的扫描，输出800个像素点数据，而液晶面板总共有480行，这

就还需要一个垂直扫描时序图，如图2-19所示：

图2-19垂直扫描时序图

图中的VSD就是垂直同步信号，HSD就是水平同步信号，DE为数据使能信号。由图可知，一个垂直扫描，刚好就是480个有效的DE脉冲信号，每一个DE时钟周期，扫描一行，总共扫描480行，完成一帧数据的显示。这就是800*480的LCD面板扫描时序，其他分辨率的LCD面板，时序类似。

图中的tvpw为垂直同步有效信号脉宽，用于表示一帧数据的开始；tvb为垂直后廊，表示垂直同步信号以后的无效行数，tvfp为垂直前廊，表示一帧数据输出结束后，到下一个垂直同步信号开始之前的无效行数；这几个时间同样在配置LTDC的时候，需要进行设置。

（3）ALIENTEKRGBLCD模块

ALIENTEK目前提供大家三款RGBLCD模块：ATK-4342（4.3寸，480*272）、ATK-7084（7寸，800*480）和ATK-7016（7寸，1024*600），这里我们以ATK-7084为例，给大家介绍。该模块的接口原理图如图2-10所示：

图2-20 ATK-7084模块对外接口原理图

图中J1就是对外接口，是一个40PIN的FPC座（0.5mm间距），通过FPC线，可以连接到阿波罗STM32F429开发板的核心板上面，从而实现和STM32F429的连接。该接口十分完善，采用RGB888格式，并支持DE&HV模式，还支持触摸屏（电阻/电容）和背光控制。右侧的几个电阻，并不是都焊接的，而是可以用户自己选择。默认情况，R1和R6焊接，设置LCD_LR和LCD_UD，控制LCD的扫描方向，是从左到右，从上到下（横屏看）。而LCD_R7/G7/B7则用来设置LCD的ID，由于RGBLCD没有读写寄存器，也就没有所谓的ID，这里我们通过在模块上面，控制R7/G7/B7的上/下拉，来自定义LCD模块的ID，帮助MCU判断当前LCD面板的分辨率和相关参数，以提高程序兼容性。这几个位的设置关系如表2-9所示：

表2-9 ALIENTEKRGBLCD模块ID对应关系

M2LCD_G7	M1LCD_G7	M0LCD_R7	LCDID	说明
0	0	0	4342	ATK-4342RGBLCD模块，分辨率：480*272
0	0	1	7084	ATK-7084RGBLCD模块，分辨率：800*480
0	1	0	7016	ATK-7016，RGBLCD模块，分辨率：1024*600
0	1	1	7018	ATK-7018，RGBLCD模块，分辨率：1280*800
1	0	0	8016	ATK-8016，RGBLCD模块，分辨率：1024*600
X	X	X	NC	暂时未用到

ATK-7084模块，就设置M2:M0=001即可。这样，我们在程序里面，读取LCD_R7/G7/B7，得到M0:M2的值，从而判断RGBLCD模块的型号，并执行不同的配置，即可实现不同LCD模块的兼容。

RGBLCD我们就给大家介绍到这里，接下来，我们介绍LTDC。

（2）LTDC简介

STM32F429xx系列芯片都带有TFTLCD控制器，即LTDC，通过这个LTDC，STM32F429可以直接外接RGBLCD屏，实现液晶驱动。STM32F429的LTDC具有如下特点：

24位RGB并行像素输出；每像素8位数据(RGB888)

2个带有专用FIFO的显示层（FIFO深度64x32位）

支持查色表(CLUT)，每层高达256种颜色（256x24位）

可针对不同显示面板编程时序

可编程背景色

可编程HSync、VSync和数据使能(DE)信号的极性

每层有多达8种颜色格式可供选择：ARGB8888、RGB888、RGB565、ARGB1555、

ARGB4444、L8（8位Luminance或CLUT）、AL44（4位alpha+4位luminance）和AL88（8位alpha+8位luminance）

每通道的低位采用伪随机抖动输出（红色、绿色、蓝色的抖动宽度为2位）

使用alpha值（每像素或常数）在两层之间灵活混合

色键（透明颜色）

可编程窗口位置和大小

支持薄膜晶体管(TFT)彩色显示器

AHB主接口支持16个字的突发

高达4个可编程中断事件

LTDC控制器主要包含：信号线、图像处理单元、AHB接口、配置和状态寄存器以及时钟部分，其框图如图2-21所示：

图2-21 LTDC控制器框图

①信号线

这里就包含了我们前面提到的RGBLCD驱动所需要的所有信号线，这些信号线通过STM32F429核心板板载的LCD接口引出，其信号说明和IO连接关系，见表2-10：

表2-10 LTDC信号线及IO连接关系说明

LTDC信号线	对应IO	说明
LCD_CLK	PG7	像素时钟输出
LCD_HSYNC	PI10	水平同步
LCD_VSYNC	PI9	垂直同步
LCD_DE	PF10	数据使能
LCD_R[7:3]	PG6、PH12、PH11、PH10、PH9	红色数据线，LCD_R[2:0]未用到
LCD_G[7:2]	PI2、PI1、PI0、PH15、PH14、PH13	绿色数据线，LCD_G[1:0]未用到
LCD_B[7:3]	PI7、PI6、PI5、PI4、PG11	蓝色数据线，LCD_B[2:0]未用到

LTDC总共有24位数据线，支持RGB888格式，但是我们为了节省IO，并提高图片显示速度，使用RGB565颜色格式，这样的话，只需要16个IO口，当使用RGB565格式的时候，LCD面板的数据线，必须连接到LTDC数据线的MSB，即：LTDC的LCD_R[7:3]接RGBLCD的R[7:3]，LTDC的LCD_G[7:2]接RGBLCD的G[7:2]，LTDC的LCD_B[7:3]接RGBLCD的B[7:3]，这样，RGB数据线分别是5:6:5，即RGB565格式。表中对应IO就是我们STM32F429核心板上面，LCD接口所连接的IO。

②图像处理单元

此部分先从AHB接口获取显存中的图像数据，然后经过层FIFO（有2个，对应2个层）缓存，每个层FIFO具有64*32位存储深度，然后经过像素格式转换器（PFC），把从层的所选输入像素格式转换为ARGB8888格式，再通过混合单元，把两层数据合并，混合得到单层要显示的数据，最后经过抖动单元处理（可选）后，输出给LCD显示。

这里的ARGB8888，即带8位透明通道，即最高8位为透明通道参数，表示透明度，值越大，则约不透明，值越小，越透明。比如A=255时，表示完全不透明，而A=0时，表示完全透明。RGB888就表示R、G、B各8位，可表示的颜色深度为1600W色。

STM32F429的LTDC总共有三个层：背景层、第一层和第二层，其中，背景层只可以是纯色（即单色），而第一层和第二层都可以用来显示信息，混合单元会将三个层混合起来，进行显示，显示关系如图2-22所示：

图2-22 三个层混合关系

从图中可以看出，第二层位于最顶端，背景层位于最低端，混合单元首先将第一层与背景层进行混合，随后，第二层与第一层和第二层的混合颜色结果再次混合，完成混合后，送给LCD显示。

③AHB接口

由于LTDC驱动RGBLCD的时候，需要有很多内存来做显存，比如一个800*480的屏幕，按一般的16位RGB565模式，一个像素需要2个字节的内存，总共需要：800*480*2=768K字节内存，STM32内部是没有这么多内存的，所以必须借助外部SDRAM，而SDRAM是挂在AHB总线上的，LTDC的AHB接口，就是用来将显存数据，从SDRAM存储器传输到FIFO里面。

④配置和状态寄存器

此部分包含了LTDC的各种配置寄存器以及状态寄存器，用于控制整个LTDC的工作参数，主要有：各信号的有效电平、垂直/水平同步时间参数、像素格式、数据使能等等。LTDC的同步时序（HV模式）控制框图，如图2-23所示：

图2-23 LTDC同步时序框图

图中有效显示区域，就是我们RGBLCD面板的显示范围（即分辨率），有效宽度*有效高度，就是LCD的分辨率。另外，这里还有的参数包括：HSYNC的宽度（HSW）、VSYNC的宽度（VSW）、HBP、HFP、VBP和VFP等，这些参数的说明，见表2-11：

表2-11 LTDC驱动时序参数

参数	说明
HSW (horizontalsyncwidth)	水平同步脉宽，单位为相素时钟（CLK）个数
VSW (verticalsyncwidth)	垂直同步脉宽，单位为行周期个数
HBP (horizontalbackporch)	水平后廊，表示水平同步信号开始到行有效数据开始之间的相素时钟（CLK）个数
HFP (horizontalfrontporch)	水平前廊，表示行有效数据结束到下一个水平有效信号开始之前的相素时钟（CLK）个数
VBP (verticalbackporch)	垂直后廊，表示垂直同步信号后，无效行的个数
VFP (verticalfrontporch)	垂直前廊，表示一帧数据输出结束后，到下一 个垂直同步信号开始之前的无效行数

如果RGBLCD使用的是DE模式，LTDC也只需要设置表19.1.2.2所示的参数，然后LTDC会根据这些设置，自动控制DE信号。这些参数通过相关寄存器来配置，接下来，我们介绍一下LTDC的一些相关寄存器。首先，我们来看LTDC全局控制寄存器：LTDC_GCR，该寄存器各位描述如图2-24所示：

图2-24 LTDC_GCR寄存器各位描述

该寄存器我们在本章用到的设置有：LTDCEN、PCPOL、DEPOL、VSPOL和HSPOL这几个设置，我们将逐个介绍。

LTDCEN：TFTLCD控制器使能位，也就是LTDC的开关，该位需要设置为1

PCPOL：像素时钟极性。控制像素时钟的极性，根据LCD面板的特性来设置，我们所用的LCD一般设置为0即可，表示低电平有效。

DEPOL：数据使能极性。控制DE信号的极性，根据LCD面板的特性来设置，我们所用的LCD一般设置为0即可，表示低电平有效。

VSPOL：垂直同步极性。控制VSYNC信号的极性，根据LCD面板的特性来设置，我们所用的LCD一般设置为0即可，表示低电平有效。

HSPOL：水平同步极性。控制HSYNC信号的极性，根据LCD面板的特性来设置，我们所用的LCD一般设置为0即可，表示低电平有效。

接下来，我们看看LTDC同步大小配置寄存器：LTDC_SSCR，该寄存器各位描述如图2-25所示：

图2-25 LTDC_SSCR寄存器各位描述

该寄存器用于设置垂直同步高度（VSH）和水平同步宽度（HSW），其中：

VSH：表示垂直同步高度(以水平扫描行为单位），表示垂直同步脉宽减1，即VSW-1

HSW：表示水平同步宽度(以像素时钟为单位），表示水平同步脉宽减1，即HSW-1。

接下来，我们看看LTDC后沿配置寄存器：LTDC_BPCR，该寄存器各位描述如图2-26所示：

图2-26 LTDC_BPCR寄存器各位描述

该寄存器我们需要配置AVBP和AHBP：

AVBP：累加垂直后沿(以水平扫描行为单位），表示：VSW+VBP-1（见表19.1.2.2）。

AHBP：累加水平后沿(以像素时钟为单位），表示HSW+HBP-1（见表19.1.2.2，下同）。

接下来，我们看看LTDC有效宽度配置寄存器：LTDC_AWCR，该寄存器各位描述如图2-17所示：

图2-27 LTDC_AWCR寄存器各位描述该寄存器

我们需要配置AAH和AAW：

AAH：累加有效高度（以水平扫描行为单位），表示：VSW+VBP+有效高度-1

AAW：累加有效宽度（以像素时钟为单位），表示：HSW+HBP+有效宽度-1。

这里所说的有效高度和有效宽度，是指LCD面板的宽度和高度（构成分辨率，下同）。接下来，我们看看LTDC总宽度配置寄存器：LTDC_TWCR，该寄存器各位描述如图2-28所示：

图2-28 LTDC_TWCR寄存器各位描述

该寄存器我们需要配置TOTALH和TOTALW：

TOTALH：总高度（以水平扫描行为单位），表示：VSW+VBP+有效高度+VFP-1

TOTALW：总宽度（以像素时钟为单位），表示：HSW+HBP+有效宽度+HFP-1。

接下来，我们看看LTDC背景色配置寄存器：LTDC_BCCR，该寄存器各位描述如图2-29所示：

图2-29 LTDC_BCCR寄存器各位描述

该寄存器定义背景层的颜色（RGB888），通过低24位配置，我们一般设置为全0即可。接下来，我们看看LTDC的层颜色帧缓冲区地址寄存器：LTDC_LxCFBAR（x=1/2），该寄存器各位描述如图2-30所示：

图2-30 LTDC_LxCFBAR寄存器各位描述

该寄存器用来定义一层显存的起始地址。STM32F429的LTDC支持2个层，所以总共有两个寄存器，分别设置层1和层2的显存起始地址。

接下来，我们看看LTDC的层像素格式配置寄存器：LTDC_LxPFCR（x=1/2），该寄存器只有最低3位有效，用于设置层颜色的像素格式：000：ARGB8888；001：RGB888；010：RGB565；011：ARGB1555；100：ARGB4444；101：L8（8位Luminance）；110：AL44（4位Alpha，4位Luminance）；111：AL88（8位Alpha，8位Luminance）。我们一般使用RGB565格式，即该寄存器设置为：010即可。

接下来，我们看看LTDC的层恒定Alpha配置寄存器：LTDC_LxCACR（x=1/2），该寄存器各位描述如图2-31所示：

图2-31 LTDC_LxCACR寄存器各位描述

该寄存器低8位（CONSTA）有效，这些位配置混合时使用的恒定Alpha。恒定Alpha由硬件实现255分频。关于这个恒定Alpha的使用，我们将在介绍LTDC_LxBFCR寄存器的时候进行讲解。

接下来，我们看看LTDC的层默认颜色配置寄存器：LTDC_LxDCCR（x=1/2），该寄存器各位描述如图2-32所示：

图2-32 LTDC_LxDCCR寄存器各位描述

该寄存器定义采用ARGB8888格式的层的默认颜色。默认颜色在定义的层窗口外使用或在层禁止时使用。一般情况下，用不到，所以该寄存器一般设置为0即可。

接下来，我们看看LTDC的层混合系数配置寄存器：LTDC_LxBFCR（x=1/2），该寄存器各位描述如图2-33所示：

图2-33 LTDC_LxBFCR寄存器各位描述

该寄存器用于定义混合系数：BF1和BF2。BF1=100的时候，使用恒定的Alpha混合系数

（由LTDC_LxCACR寄存器设置恒定Alpha值），BF1=110的时候，使用像素Alpha*恒定Alpha。像素Alpha即ARGB格式像素的A值（Alpha值），仅限ARGB颜色格式时使用。在RGB565格式下，我们设置BF1=100即可。BF2同BF1类似，BF2=101的时候，使用恒定的Alpha混合系数，BF2=111的时候，使用像素Alpha*恒定Alpha。在RGB565格式下，我们设置BF2=101即可。

通用的混合公式为：

BC=BF1*C+BF2*Cs

其中：BC=混合后的颜色；BF1=混合系数1；C=当前层颜色，即我们写入层显存的颜色值；BF2=混合系数2；Cs=底层混合后的颜色，对于层1来说，Cs=背景层的颜色，对于层2来说，

Cs=背景层和层1混合后的颜色。

以使用恒定的Alpha值，并仅使能第一层为例，给大家讲解一下混色的计算方式。恒定Alpha的值由LTDC_LxCACR寄存器设置，恒定Alpha=LTDC_LxCACR设置值/255。假设：LTDC_LxCACR=240；C=128；Cs（背景色）=48；那么恒定Alpha=240/255=0.94，则：

BC=0.94*128+(1-0.94)*48=123

则混合后，颜色值变成了123。另外，需要注意的是：BF1和BF2的恒定Alpha值互补，他们之和为1，且BF1使用的是恒定Alpha值，BF2使用的是互补值。一般情况下，我们设置LTDC_LxCACR的值为255，这样，在使用恒定Alpha值的时候，可以得到BC=C，即混合后的颜色，就是显存里面的颜色（不进行混色）。

LTDC的层支持窗口设置功能，通过LTDC_LxWHPCR和LTDC_LxWVPCR这两个寄存器设置，可以调整显示区域的大小，如图2-34所示：

图2-34 LTDC层窗口设置关系图

上图中，层中的第一个和最后一个可见像素通过配置LTDC_LxWHPCR寄存器中的WHSTPOS[11:0]和WHSPPOS[11:0]进行设置。层中的第一个和最后一个可见行通过配置LTDC_LxWVPCR寄存器中的WHSTPOS[11:0]和WHSPPOS[11:0]进行设置，配置完成后，即可确定窗口的大小。

接下来，我们来介绍这两个寄存器，首先是LTDC的层窗口水平位置配置寄存器：LTDC_LxWHPCR（x=1/2），该寄存器各位描述如图2-35所示：

图2-35 LTDC_LxWHPCR寄存器各位描述

该寄存器定义第1层或第2层窗口的水平位置（第一个和最后一个像素），其中：WHSTPOS：窗口水平起始位置，定义层窗口的一行的第一个可见像素，见图19.1.2.14。WHSPPOS：窗口水平停止位置，定义层窗口的一行的最后一个可见像素，见图19.1.2.14。然后，我们介绍LTDC的层窗口垂直位置配置寄存器：LTDC_LxWVPCR（x=1/2），该寄存器各位描述如图2-36所示：

图2-36 LTDC_LxWVPCR寄存器各位描述

该寄存器定义第1层或第2层窗口的垂直位置（第一行或最后一行），其中：WVSTPOS：窗口垂直起始位置，定义层窗口的第一个可见行，见图19.1.2.14。WVSPPOS：窗口垂直停止位置，定义层窗口的最后一个可见行，见图19.1.2.14。

接下来，我们看看LTDC的层颜色帧缓冲区长度寄存器：LTDC_LxCFBLR（x=1/2），该寄存器各位描述如图2-37所示：

图2-37 LTDC_LxCFBLR寄存器各位描述

该寄存器定义颜色帧缓冲区的行长和行间距。其中：

CFBLL：这些位定义一行像素的长度（以字节为单位）+3。行长的计算方法为：有效宽度

*每像素的字节数+3。比如，LCD面板的分辨率为800*480，有效宽度为800，采用RGB565格式，那么CFBLL需要设置为：800*2+3=1603。

CFBP：这些位定义从像素某行的起始处到下一行的起始处的增量（以字节为单位）。这个设置，其实同样是一行像素的长度，对于800*480的LCD面板，RGB565格式，设置CFBP为：800*2=1600即可。

最后，我们看看LTDC的层颜色帧缓冲区行数寄存器：LTDC_LxCFBLNR（x=1/2），该寄存器各位描述如图2-38所示：

图2-38 LTDC_LxCFBLNR寄存器各位描述

该寄存器定义颜色帧缓冲区中的行数。CFBLNBR用于定义帧缓冲区行数，比如，LCD面板的分辨率为800*480，那么帧缓冲区的行数为480行，则设置CFBLNBR=480即可。

至此，LTDC相关的寄存器，基本就介绍完了，通过这些寄存器的配置，我们就可以完成对LTDC的初始化，控制LCD显示了。关于LTDC的详细介绍，和寄存器描述，请看《STM32F4xx中文参考手册-扩展章节.pdf》第16章。

⑤时钟域

LTDC有三个时钟域：AHB时钟域（HCLK）、APB2时钟域（PCLK2）和像素时钟域（LCD_CLK），AHB时钟域用于驱动AHB接口，读取存储器的数据到FIFO里面，APB2时钟域用于配置寄存器，像素时钟域则用于生成LCD接口信号，LCD_CLK的输出应按照LCD面板要求进行配置。

接下来，我们重点介绍下LCD_CLK的配置过程。LCD_CLK的时钟来源，如图2-39所示：

图2-39 LCD_CLK时钟图

由图可知，LCD_CLK的来源，为外部晶振（假定外部晶振作为系统时钟源），经过分频器分频（/M），然后经过PLLSAI倍频器倍频（xN）后，经R分频因子输出分频后的时钟，得到PLLLCDCLK，然后在经过DIV分频和时钟使能后，得到LCD_CLK。接下来，我们简单介绍下配置LCD_CLK需要用到的一些寄存器。

首先是RCCPLLSAI配置寄存器：RCC_PLLSAICFGR，该寄存器的各位描述如图2-40所示：

图2-40 RCC_PLLSAICFGR寄存器各位描述

这个寄存器主要对PLLSAI倍频器的：N、Q和R等参数进行配置，他们的设置关系（假定使用外部HSE作为时钟源）为：

f(VCOclock)=f(hse)×(PLLSAIN/PLLM)

f(PLLSACLK)=f(VCOclock)/PLLSAIQ

f(PLLLCDCLK)=f(VCOclock)/PLLSAIR

f(hse)为我们外部晶振的频率，PLLM就是M分频因子，PLLSAIN为PLLSAI的倍频数，取值范围为：49~432；PLLSAIQ为PLLSAI的Q分频系数，取值范围为：2~15；PLLSAIR为PLLSAI的R分频系数，取值范围为：2~7；阿波罗STM32F429核心板所用的HSE晶振频率为25Mhz，一般我们设置PLLM为25，那么输入PLLSAI的时钟频率就是1Mhz，然后可得：

f(PLLLCDCLK)=1Mhz*PLLSAIN/PLLSAIR

在f(PLLLCDCLK)之后，还有一个分频器（DIV），分频后得到最终的LCD_CLK频率，该分频由RCC专用时钟配置寄存器：RCC_DCKCFGR配置，该寄存器各位描述如图2-41所示：

图2-41 RCC_DCKCFGR寄存器各位描述

在本章，该寄存器我们只关心PLLSAIDIVR的配置，这两个位用于配置f(PLLLCDCLK)之后的分频，设置范围为：0~2，表示：2^(PLLSAIDIVR+1)分频。因此，我们最终得到LCD_CLK的频率计算公式为(前提：HSE=25Mhz，PLLM=25)：

f(LCD_CLK)=1Mhz*PLLSAIN/PLLSAIR/2^(PLLSAIDIVR+1)

以群创AT070TN92面板为例，查其数据手册，可知DCLK的频率典型值为：33.3Mhz，我们需要设置：PLLSAIN=396，PLLSAIR=3，PLLSAIDIVR=1，得到：

f(LCD_CLK)=1Mhz*396/3/2^(1+1)=33Mhz

最后，我们来看看实现LTDC驱动RGBLCD，需要对LTDC进行哪些配置。LTDC相关HAL库操作分布在函数stm32f4xx_hal_ltdc.c和stm32f4xx_hal_ltdc_ex.c以及他们对应的头文件中。操作步骤如下：

1）使能LTDC时钟，并配置LTDC相关的IO及其时钟使能。

HAL_RCC_LTDC_CLK_ENABLE();	//使能 LTDC 时钟 要使用LTDC，当然首先得开启其时钟。然后需要把LCD_R/G/B数据线、LCD_HSYNC和LCD_VSYNC等相关IO口，全部配置为复用输出，并使能各IO组的时钟。GPIO配置这里我们就不做讲解，LTDC时钟使能方法为：

2）设置LCD_CLK时钟。

此步需要配置LCD的像素时钟，根据LCD的面板参数进行设置，LCD_CLK由PLLSAI进行配置，前面我们已经讲解非常详细，配置使用到的HAL库函数为：

HAL_StatusTypeDef HAL_RCCEx_PeriphCLKConfig(	RCC_PeriphCLKInitTypeDef	*PeriphClkInit);

该函数是HAL库提供的用来配置扩展外设时钟通用函数。LCD_CLK前面讲解过，它来自PLLSAI，根据前面讲解的LCD_CLK计算公式：

f(LCD_CLK)=1Mhz*PLLSAIN/PLLSAIR/2^(PLLSAIDIVR+1);

可知，我们需要配置PLLSAIN，PLLSAIR和PLLSAIDIVR等参数，具体使用实例如下：

3）设置RGBLCD的相关参数，并使能LTDC。

这一步，我们需要完成对LCD面板参数的配置，包括：LTDC使能、时钟极性、HSW、VSW、HBP、HFP、VBP和VFP等（见表19.1.2.2），通过LTDC_GCR、LTDC_SSCR、LTDC_BPCR、LTDC_AWCR和LTDC_TWCR等寄存器配置。HAL库配置LTDC参数并使能LTDC的函数为：

该函数只有一个入口参数就是hltdc,为LTDC_HandleTypeDef结构体指针类型。接下来我们看看LTDC_HandleTypeDef结构体定义如下：

该结构体有5个成员变量。成员变量Lock和State，他们是HAL库用来标识一些状态过程的变量，这里就不做过多讲解。Instance变量是LTDC_TypeDef结构体指针类型，和其他初始化结构体成员变量Instance一样都是用来设置配置寄存器的基地址，这在HAL库中已经通过宏定义定好好了，设置值为LTDC即可。成员变量LayerCfg是一个数组，它是用来保存LTDC层配置参数，下一步骤我们会讲解。最后我们重点看看成员变量Init，它是真正用来初始化LTDC的结构体变量，结构体LTDC_InitTypeDef定义如下：

这些参数含义我们都在结构体成员变量之后注释了，具体含义大家可以参考前面第四点配置和状态寄存器讲解。

LTDC_Handler.Init.HSPolarity=LTDC_HSPOLARITY_AL;	//水平同步极性	LTDC_Handler.Init.VSPolarity=LTDC_VSPOLARITY_AL;	//垂直同步极性LTDC_Handler.Init.DEPolarity=LTDC_DEPOLARITY_AL;	//数据使能极性LTDC_Handler.Init.PCPolarity=LTDC_PCPOLARITY_IPC;	//像素时钟极性LTDC_Handler.Init.HorizontalSync=10-1;	//水平同步宽度LTDC_Handler.Init.VerticalSync=2-1;	//垂直同步宽度LTDC_Handler.Init.AccumulatedHBP=10+20-1;	//水平同步后沿宽度LTDC_Handler.Init.AccumulatedVBP=2+2-1;	//垂直同步后沿高度LTDC_Handler.Init.AccumulatedActiveW=10+20+480-1; //有效宽度LTDC_Handler.Init.AccumulatedActiveH=2+2+272-1;	//有效高度LTDC_Handler.Init.TotalWidth=10+20+480+10-1;	//总宽度LTDC_Handler.Init.TotalHeigh=2+2+272+4-1;	//总高度LTDC_Handler.Init.Backcolor.Red=0;	//屏幕背景层红色部分	LTDC_Handler.Init.Backcolor.Green=0;	//屏幕背景层绿色部分	LTDC_Handler.Init.Backcolor.Blue=0;	//屏幕背景色蓝色部分H //LTDC 句柄	LTDC_HandleTypeDef	LTDC_Handler; LTDC_Handler.Instance=LTDC;

和其他外设或接口初始化一样，HAL同样提供了LTDC初始化MSP回调函数，HAL_LTDC_MspInit，该函数一般用来使能时钟和初始化IO口等于MCU相关操作：

4）设置LTDC层参数。

此步，我们需要设置LTDC某一层的相关参数，包括：帧缓存首地址、颜色格式、混合系数和层默认颜色等。通过LTDC_LxCFBAR、LTDC_LxPFCR、LTDC_LxCACR、LTDC_LxDCCR和LTDC_LxBFCR等寄存器配置。HAL库提供的LTDC层参数配置函数为：

基于篇幅考虑，该函数具体的入口参数定义这里我们就不做过多讲解，具体使用方法请参考19.3小节函数讲解以及实验工程。

5）设置LTDC层窗口，并使能层。

这一步，完成对LTDC某个层的显示窗口设置（一般设置为整层显示，不开窗），通过LTDC_LxWHPCR、LTDC_LxWVPCR、LTDC_LxCFBLR和LTDC_LxCFBLNR等寄存器配置。层使能通过配置LTDC_LxCR寄存器的最低位实现，使能层以后，RGBLCD就可以正常工作了。

HAL库提供的LTDC层窗口配置函数为：

这两个函数使用方法非常简单，这里我们就不累赘了。

通过以上几个步骤，我们就完成了LTDC的配置，可以控制RGBLCD显示了。LTDC我们就给大家介绍到这里，接下来，我们介绍DMA2D。

3.DMA2D简介

为了提高STM32F429的图像处理能力，ST公司设计了一个专用于图像处理的专业DMA：Chrom-ArtAccelerator™，即：DMA2D，通过DMA2D对图像进行填充和搬运，可以完全不用CPU干预，从而提高效率，减轻CPU负担。它可以执行下列操作：

用特定颜色填充目标图像的一部分或全部（可用于快速单色填充）

将源图像的一部分（或全部）复制到目标图像的一部分（或全部）中（可用于快速图像填充）

通过像素格式转换将源图像的一部分（或全部）复制到目标图像的一部分（或全部）中

将像素格式不同的两个源图像部分和/或全部混合，再将结果复制到颜色格式不同的部分或整个目标图像中。

DMA2D有四种工作模式，通过DMA2D_CR寄存器的MODE[1:0]位选择工作模式：

1，寄存器到存储器

2，存储器到存储器

3，存储器到存储器并执行PFC

4，存储器到存储器并执行PFC和混合

本章，我们仅介绍前两种工作模式，后两种工作模式，请大家参考《STM32F4xx中文参考手册-扩展章节.pdf》第十一章。

寄存器到存储器

寄存器到存储器模式用于以预定义颜色填充用户自定义区域，也就是可以实现快速的单色填充显示，比如清屏操作。

在该模式下：颜色格式在DMA2D_OPFCCR中设置，DMA2D不从任何源获取数据，它只是将DMA2D_OCOLR寄存器中定义的颜色写入通过DMA2D_OMA寻址以及DMA2D_NLR和DMA2D_OOR定义的区域。

存储器到存储器

该模式下，DMA2D不执行任何图形数据转换。前景层输入FIFO充当缓冲区，数据从DMA2D_FGMAR中定义的源存储单元传输到DMA2D_OMAR寻址的目标存储单元，可用于快速图像填充。DMA2D_FGPFCCR寄存器的CM[3:0]位中编程的颜色模式决定输入和输出的每像素位数。对于要传输的区域大小，源区域大小由DMA2D_NLR和DMA2D_FGOR寄存器定义，目标区域大小则由DMA2D_NLR和DMA2D_OOR寄存器定义。

以上两个工作模式，LTDC在层帧缓存里面的开窗关系都一样，如图2-42所示：

图2-42 层帧缓冲开窗示意图

窗口显示区域的显存首地址由DMA2D_OMAR寄存器指定，窗口宽度和高度由DMA2D_NRL寄存器的PL和NL指定，行偏移（确定下一行的起始地址）由DMA2D_OOR寄存器指定，经过这三个寄存器的配置，就可以确定窗口的显示位置和大小。

在寄存器到存储器模式下，在开窗完成后，DMA2D可以将DMA2D_OCOLR指定的颜色，自动填充到开窗区域，完成单色填充。

在存储器到存储器模式下，需要完成两个开窗：前景层和显示层，完成配置后，图像数据从前景层拷贝到显示层（仅限窗口范围内），从而显示到LCD上面。显示层的开窗，如图19.1.3.1所示，而前景层的开窗，则和图19.1.3.1所示相似，只是DMA2D_OMAR寄存器变成了DMA2D_FGMAR，DMA2D_OOR寄存器变成了DMA2D_FGOR，DMA2D_NRL则两个层共用，然后就可以完成对前景层的开窗，确定好两个窗口后，DMA2D就将前景层窗口内的数据，拷贝到显示层窗口，完成快速图像填充。

接下来，我们介绍一下DMA2D的一些相关寄存器。

首先，我们来看DMA2D控制寄存器：DMA2D_CR，该寄存器各位描述如图2-43所示：

图2-43 DMA2D_CR寄存器各位描述

该寄存器，我们主要关心MODE和START这两个设置，其中：

MODE：表示DMA2D的工作模式，00：存储器到存储器模式；01：存储器到存储器模式并执行PFC；10：存储器到存储器并执行混合；11，寄存器到存储器模式；本章我们需要用到的设置为：00或者11。

START：该位控制DMA2D的启动，在配置完成后，设置该位为1，启动DMA2D传输。接下来，我们介绍DMA2D输出PFC控制寄存器：DMA2D_OPFCCR，该寄存器各位描述如图2-44所示：

图2-44 DMA2D_OPFCCR寄存器各位描述

该寄存器用于设置寄存器到存储器模式下的颜色格式，只有最低3位有效（CM[2:0]），表示的颜色格式有：000，ARGB8888；001：RGB888；010：RGB565；011：ARGB1555；100：ARGB1444。我们一般使用的是RGB565格式，所以设置CM[2:0]=010即可。同样的，还有前景层PFC控制寄存器：DMA2D_FGPFCCR，该寄存器各位描述如图2-45所示：

图2-45 DMA2D_FGPFCCR寄存器各位描述

该寄存器，我们只关心最低4位：CM[3:0]，用于设置存储器到存储器模式下的颜色格式，这四个位表示的颜色格式为：0000：ARGB8888；0001：RGB888；0010：RGB565；0011：ARGB1555；0100：ARGB4444；0101：L8；0110：AL44；0111：AL88；1000：L4；1001：A8；1010：A4；我们一般使用RGB565格式，所以设置CM[3:0]=0010即可。

接下来，我们介绍DMA2D输出偏移寄存器：DMA2D_OOR，该寄存器各位描述如图2-46所示：

图2-46 DMA2D_OOR寄存器各位描述

该寄存器仅最低14位有效（LO[13:0]），用于设置输出行偏移，作用于显示层，以像素为单位表示。此值用于生成地址。行偏移将添加到各行末尾，用于确定下一行的起始地址，参见图19.1.3.1。

同样的，还有前景层偏移寄存器：DMA2D_FGOR，该寄存器同DMA2D_OOR一样，也是低14位有效，用于控制前景层的行偏移，也是用于生成地址，添加到各行末尾，从而确定下一行的起始地址。

接下来，我们介绍DMA2D输出存储器地址寄存器：DMA2D_OMAR，该寄存器各位描述如图2-47所示：

图2-47 DMA2D_OMAR寄存器各位描述

该寄存器设置由MA[31:0]设置输出存储器地址，也就是输出FIFO所存储的数据地址，该地址需要根据开窗的起始坐标来进行设置。以800*480的LCD屏为例，行长度为800像素，假定帧缓存数组为：ltdc_framebuf，我们设置窗口的起始地址为：sx（<800），sy（<480），颜色格式为RGB565，每个像素2个字节，那么MA的设置值应该为：

MA[31:0]=framebuf+2*(800*sy+sx)

同样的，还有前景层偏移寄存器：DMA2D_FGMAR，该寄存器同DMA2D_OMAR一样，不过是用于控制前景层的存储器地址，计算方法同DMA2D_OMAR。

接下来，我们介绍DMA2D行数寄存器：DMA2D_NLR，该寄存器各位描述如图2-48所示：

图2-48 DMA2D_NLR寄存器各位描述

该寄存器用于控制每行的像素和行数，该寄存器的设置对前景层和显示层均有效，通过该寄存器的配置，就可以设置开窗的大小。其中：

NL[15:0]：设置待传输区域的行数，用于确定窗口的高度。

PL[13:0]：设置待传输区域的每行像素数，用于确定窗口的宽度。

接下来，我们介绍DMA2D输出颜色寄存器：DMA2D_OCOLR，该寄存器各位描述如图2-49所示：

图2-49 DMA2D_OCOLR寄存器各位描述

该寄存器用于配置在寄存器到存储器模式下，填充时所用的颜色值，该寄存器是一个32位寄存器，可以支持ARGB8888格式，也可以支持RGB565格式。我们一般使用RGB565格式，比如要填充红色，那么直接设置DMA2D_OCOLR=0XF800就可以了。

接下来，我们介绍DMA2D中断状态寄存器：DMA2D_ISR，该寄存器各位描述如图2-50所示：

图2-50 DMA2D_ISR寄存器各位描述

该寄存器表示了DMA2D的各种状态标识，这里我们只关心TCIF位，表示DMA2D的传输完成中断标志。当DMA2D传输操作完成（仅限数据传输）时此位置1，表示可以开始下一次DMA2D传输了。

另外，还有一个DMA2D中断标志清零寄存器：DMA2D_IFCR，用于清除DMA2D_ISR寄存器对应位的标志。通过向该寄存器的第1位（CTCIF）写1，可以用于清除DMA2D_ISR寄存器的TCIF位标志。

最后，我们来看看利用DMA2D完成颜色填充，需要哪些步骤。这里需要说明一下，使用官方提供的HAL库DMA2D相关库函数进行颜色填充效率极为低下，大量时间浪费在函数的入栈出栈以及过程处理，所以在项目开发中一般都不会使用DMA2D库函数进行颜色填充，包括ST官方提供的STEMWIN实例关于DMA2D部分，均采用的寄存器操作。具体操作步骤如下：

1）使能DMA2D时钟，并先停止DMA2D。

要使用DMA2D，先得开启其时钟。然后DMA2D在配置其相关参数的时候，需要先停止DMA2D传输。使能DMA2D时钟和停止DMA2D方法为：

HAL_RCC_DMA2D_CLK_ENABLE();//使能DM2D时钟

DMA2D->CR&=~DMA2D_CR_START;	//停止DMA2D

2）设置DMA2D工作模式。

通过DMA2D_CR寄存器，配置DMA2D的工作模式。我们用了寄存器到存储器模式和存储器到存储器这两个模式。

寄存器到存储器模式设置：

DMA2D->CR=DMA2D_R2M;	//寄存器到存储器模式

存储器到存储器模式设置：

DMA2D->CR= DMA2D_M2M;	//存储器到存储器模式

3）设置DMA2D的相关参数。

这一步，我们需要设置：颜色格式、输出窗口、输出存储器地址、前景层地址（仅存储器到存储器模式需要设置）、颜色寄存器（仅寄存器到存储器模式需要设置）等，由：DMA2D_OPFCCR、DMA2D_FGPFCCR、DMA2D_OOR、DMA2D_FGOR、DMA2D_OMAR、DMA2D_FGMAR和DMA2D_NLR等寄存器进行配置。具体配置过程请参考实验源码。

4）启动DMA2D传输。

通过DMA2D_CR寄存器配置开启DMA2D传输，实现图像数据的拷贝填充，方法为：

DMA2D->CR|=DMA2D_CR_START;	//启动 DMA2D

5）等待DMA2D传输完成，清除相关标识。

最后，在传输过程中，不要再次设置DMA2D，否则会打乱显示，所以一般在启动DMA2D后，需要等待DMA2D传输完成（判断DMA2D_ISR），在传输完成后，清除传输完成标识（设置DMA2D_IFCR），以便启动下一次DMA2D传输。方法为：

while((DMA2D->ISR&DMA2D_FLAG_TC)==0) ; //等待传输完成 DMA2D->IFCR|=DMA2D_FLAG_TC;	//清除传输完成标志

通过以上几个步骤，我们就完成了DMA2D填充，DMA2D的简介，我们就介绍完了，详细的介绍请大家参考《STM32F4xx中文参考手册-扩展章节.pdf》第十一章。

二、硬件设计

本实验用到的硬件资源有：

指示灯DS0

SDRAM

LTDC

RGBLCD接口

这里的1~3，我们在前面的介绍，都已经讲解完毕。所以，我们仅介绍RGBLCD接口，RGBLCD接口在STM32F429核心板上，原理图如图2-51所示：

图2-51 RGBLCD接口原理图

图中RGBLCD接口的接线关系见表19.1.2.1。这些线的连接，阿波罗STM32F429核心板的内部已经连接好了，我们只需要将RGBLCD模块通过40PIN的FPC线连接这个RGBLCD接口即可。实物连接（7寸RGBLCD模块）如图2-52所示：

图2-52 RGBLCD与开发板连接实物图

三、软件设计

打开本章实验工程可以看到，在USER分组下面添加了源文件ltdc.c并且包含了对应的头文件ltdc.h，用来存放我们编写的LTDC相关驱动函数。

ltdc.c代码比较多，这里就不贴一一出来了，只针对几个重要的函数进行讲解。完整版的代码见光盘→4，程序源码→标准例程-库函数→实验14LTDCLCD（RGB屏）实验的ltdc.c文件。

本实验，我们用到LTDC驱动RGBLCD，通过前面的介绍，我们知道不同的RGB屏，驱动参数有一些差异，为了方便兼容不同的RGBLCD，我们定义如下LTDC参数结构体（在ltdc.h里面定义）：

该结构体用于保存一些RGBLCD重要参数信息，比如LCD面板的长宽、水平后廊和垂直后廊等参数。这个结构体虽然占用了几十个字节的内存，但是却可以让我们的驱动函数支持不同尺寸的LCD，同时可以实现LCD横竖屏切换等重要功能，所以还是利大于弊的。

接下来，我们来看两个很重要的数组：

其中，ltdc_lcd_framebuf的大小是LTDC一帧图像的显存大小，STM32F429的LTDC最大可以支持1280*800的RGB屏，该数组根据我们选择的颜色格式（ARGB8888/RGB565），自动确定数组类型。另外，我们采用attribute关键字，将数组的地址定向到LCD_FRAME_BUF_ADDR，它在ltdc.h里面定义，其值为：0XC0000000，也就是SDRAM的首地址。这样，我们就把ltdc_lcd_framebuf数组定义到了SDRAM的首地址，大小为800*1280*2字节（RGB565格式时）。

而ltdc_framebuf则是LTDC的帧缓存数组指针，LTDC支持2个层，所以数组大小为2。该指针为32位类型，必须指向对应的数组，才可以正常使用。在实际使用的时候，我们编写代码：

ltdc_framebuf[0]=(u32*)&ltdc_lcd_framebuf;

就将LTDC第一层的帧缓存，指向了ltdc_lcd_framebuf数组。往ltdc_lcd_framebuf里面写入不同的数据，就可以修改RGBLCD上面显示的内容。

首先，我们来看画点函数：LTDC_Draw_Point，该函数代码如下：

//画点函数

//x,y:写入坐标

//color:颜色值

voidLTDC_Draw_Point(u16x,u16y,u32color)

{

#if LCD_PIXFORMAT==LCD_PIXFORMAT_ARGB8888||

LCD_PIXFORMAT==LCD_PIXFORMAT_RGB888

if(lcdltdc.dir)	//横屏

{

*(u32*)((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize*

(lcdltdc.pwidth*y+x))=color; }else	//竖屏 { *(u32*)((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize* (lcdltdc.pwidth*(lcdltdc.pheight-x-1)+y))=color; } #else if(lcdltdc.dir) //横屏 { *(u16*)((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize* (lcdltdc.pwidth*y+x))=color;	}else	//竖屏 { *(u16*)((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize* (lcdltdc.pwidth*(lcdltdc.pheight-x-1)+y))=color; } #endif }

该函数实现往RGBLCD上面画点的功能，根据LCD_PIXFORMAT定义的颜色格式以及横竖屏状态，执行不同的操作。RGBLCD的画点，实际上就是往指定坐标的显存里面写数据，以7寸800*480的屏幕，RGB565格式，竖屏模式为例，画某个点对应到屏幕上面的关系如图2-53所示：

图2-53 画点与LCD显存对应关系

注意图中的LTDC扫描方向（LTDC在显存ltdc_framebuf里面读取GRAM数据的顺序也是这个方向），是从上到下，从右到左，而竖屏的时候，原点在左上角，所以有一个变换过程，经过变换后的画点函数为：

*(u16*)((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize*(lcdltdc.pwidth*(lcdltdc.pheight-x-1)+y))=color;

其中ltdc_framebuf，就是层帧缓冲的首地址；lcdltdc.activelayer表示层编号：0/1代表第1/2层；lcdltdc.pixsize表示每个像素的字节数，对于RGB565，它的值为2；lcdltdc.pwidth和lcdltdc.pheight为LCD面板的宽度和高度，lcdltdc.pwidth=800，lcdltdc.pheight=480；x，y就是要写入显存的坐标（也就是显示在LCD上面的坐标）；color为要写入的颜色值。

有画点函数，就有读点函数，LTDC的读点函数代码如下：

//读点函数

//x,y:读取点的坐标

//返回值:颜色值

u32LTDC_Read_Point(u16x,u16y)

{

#if	LCD_PIXFORMAT==LCD_PIXFORMAT_ARGB8888||

LCD_PIXFORMAT==LCD_PIXFORMAT_RGB888

if(lcdltdc.dir)//横屏

{

return*(u32*)((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize*

(lcdltdc.pwidth*y+x));

}else	//竖屏

{

}

#else

if(lcdltdc.dir)//横屏

{

return*(u16*)((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize*

(lcdltdc.pwidth*y+x));

}else	//竖屏

{

return*(u16*)((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize*

(lcdltdc.pwidth*(lcdltdc.pheight-x-1)+y));

}

#endif

}

画点函数和读点函数十分类似，只是过程反过来了而已，坐标的计算，也是在ltdc_framebuf

数组内，根据坐标计算偏移量，完全和读点函数一模一样。

第三个介绍的函数是LTDC单色填充函数：LTDC_Fill，该函数使用了DMA2D操作，使得填充速度大大加快，该函数代码如下：

//LTDC 填充矩形,DMA2D 填充 //(sx,sy),(ex,ey):填充矩形对角坐标,区域大小为:(ex-sx+1)*(ey-sy+1) //注意:sx,ex,不能大于 lcddev.width-1;sy,ey,不能大于 lcddev.height-1!!! //color:要填充的颜色 void LTDC_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u32 color)

该函数使用DMA2D完成矩形色块的填充，其操作步骤，就是按19.1.3节最后的介绍来进行的，我们这了就不多说了，详见19.1.3接。另外，还有一个LTDC彩色填充函数，也是采用的DMA2D填充，函数名为LTDC_Color_Fill，该函数代码同LTDC_Fill非常接近，我们这里就不介绍了，请大家参考本例程源码。

第四个介绍的函数是清屏函数：LTDC_Clear，该函数代码如下：

该函数代码非常简单，清屏操作调用了我们前面介绍的LTDC_Fill函数，采用DMA2D完成对LCD的清屏，提高了清屏速度。

第五个介绍的函数是LCD_CLK频率设置函数：LTDC_Clk_Set，该函数代码如下：

//LTDC 时钟(Fdclk)设置函数 //Fvco=Fin*pllsain; //Fdclk=Fvco/pllsair/2*2^pllsaidivr=Fin*pllsain/pllsair/2*2^pllsaidivr; //Fvco:VCO 频率 //Fin:输入时钟频率一般为 1Mhz(来自系统时钟 PLLM 分频后的时钟,见时钟树图) //pllsain:SAI 时钟倍频系数N,取值范围:50~432. //pllsair:SAI 时钟的分频系数 R,取值范围:2~7 //pllsaidivr:LCD 时钟分频系数,取值范围:0~3,对应分频 2^(pllsaidivr+1) //假设:外部晶振为 25M,pllm=25 的时候,Fin=1Mhz. //例如:要得到 20M 的 LTDC 时钟,则可以设置:pllsain=400,pllsair=5,pllsaidivr=1 //Fdclk=1*396/3/2*2^1=396/12=33Mhz //返回值:0,成功;1,失败。 u8 LTDC_Clk_Set(u32 pllsain,u32 pllsair,u32 pllsaidivr) { RCC_PeriphCLKInitTypeDef PeriphClkIniture; //LTDC 输出像素时钟，需要根据自己所使用的 LCD 数据手册来配置！ PeriphClkIniture.PeriphClockSelection=RCC_PERIPHCLK_LTDC; //LTDC 时钟PeriphClkIniture.PLLSAI.PLLSAIN=pllsain; PeriphClkIniture.PLLSAI.PLLSAIR=pllsair; PeriphClkIniture.PLLSAIDivR=pllsaidivr;	if(HAL_RCCEx_PeriphCLKConfig(&PeriphClkIniture)==HAL_OK)	//配置像素时钟 {	return 0;	//成功 }	else return 1;	//失败 }

该函数完成对PLLSAI的配置，最终控制输出LCD_CLK的频率，LCD_CLK的频率设置方法，我们在19.1.2节进行了介绍，请大家参考前面的介绍进行学习。

第六个介绍的函数是LTDC层参数设置函数：LTDC_Layer_Parameter_Config，该

函数代码如下：

//LTDC,基本参数设置. //注意:此函数,必须在 LTDC_Layer_Window_Config 之前设置. //layerx:层值,0/1. //bufaddr:层颜色帧缓存起始地址 //pixformat:颜色格式.0,ARGB8888;1,RGB888;2,RGB565;3,ARGB1555;	//	4,ARGB4444;5,L8;6;AL44;7;AL88 //alpha:层颜色 Alpha 值,0,全透明;255,不透明 //alpha0:默认颜色 Alpha 值,0,全透明;255,不透明

该函数中主要调用HAL库函数HAL_LTDC_ConfigLayer设置LTDC层的基本参数，包括：层帧缓冲区首地址、颜色格式、Alpha值、混合系数和层默认颜色等，这些参数都需要根据大家的实际需要来进行设置。

第七个介绍的函数是LTDC层窗口设置函数：LTDC_Layer_Window_Config，该函数代码如下：

//LTDC,层窗口设置,窗口以LCD面板坐标系为基准

//layerx:层值,0/1.

//sx,sy:起始坐标

//width,height:宽度和高度

//LTDC,层颜窗口设置,窗口以LCD面板坐标系为基准

//注意:此函数必须在LTDC_Layer_Parameter_Config之后再设置.

//layerx:层值,0/1.

//sx,sy:起始坐标

//width,height:宽度和高度

voidLTDC_Layer_Window_Config(u8layerx,u16sx,u16sy,u16width,u16height)

{

HAL_LTDC_SetWindowPosition(&LTDC_Handler,sx,sy,layerx);	//设置窗口的位置

HAL_LTDC_SetWindowSize(&LTDC_Handler,width,height,layerx);//设置窗口大小

}

该函数依次调用HAL库LTDC窗口位置设置函数HAL_LTDC_SetWindowPositio和窗口大小设置函数HAL_LTDC_SetWindowSizel来控制LTDC在某一层（1/2）上面的开窗操作，这个我们在19.1.2节也介绍过了，请参考前面的内容进行学习。这里我们一般设置层窗口为整个LCD的分辨率，也就是不进行开窗操作。注意：此函数必须在LTDC_Layer_Parameter_Config之后再设置。另外，当设置的窗口值不等于面板的尺寸时，对层GRAM的操作(读/写点函数)，也要根据层窗口的宽高来进行修改，否则显示不正常（本例程就未做修改）。

第八个介绍的函数是LTDCLCDID获取函数：LTDC_PanelID_Read，该函数代码如下：

}

因为RGBLCD屏并没有读的功能，所以，一般情况，外接RGB屏的时候，MCU是无法获取屏幕的任何信息的。但是ALIENTEK在RGBLCD模块上面，利用数据线（R7/G7/B7）做了一个巧妙的设计，可以让MCU读取到RGBLCD模块的ID，从而执行不同的初始化，实现对不同分辨率的RGBLCD模块的兼容。详细原理见：本章19.1.1节，第（3）部分：ALIENTEKRGBLCD模块的说明。

LTDC_PanelID_Read函数，就是用这样的方法来读取M[2:0]的值，并将结果（转换成屏型号了）返回给上一层。

最后要介绍的函数是LTDC初始化函数：LTDC_Init，该函数的简化代码如下：

LTDC_Init的初始化步骤，是按照19.1.2节最后介绍的步骤来进行的，该函数先读取RGBLCD的ID，然后根据不同的RGBLCD型号，执行不同的面板参数初始化，然后调用HAL_LTDC_Init函数来设置RGBLCD的相关参数并使能LTDC，最后配置层参数和层窗口完成对LTDC的初始化。注意，代码里面的lcdltdc.hsw、lcdltdc.vsw、lcdltdc.hbp等参数的值，均是来自对应RGBLCD屏的数据手册，其中lcdid=0X7084的配置参数，来自：AT070TN92.pdf。

接下来我们看看头文件ltdc.h关键内容：

这段代码主要定义了_ltdc_dev结构体，用于保存LCD相关参数，另外，LCD_PIXFORMAT定义了颜色格式，我们一般使用RGB565格式，LCD_FRAME_BUF_ADDR定义了帧缓存的首地址，我们定义在SDRAM的首地址，其他的就不多说了。

以上，就是ltdc驱动部分的代码，因为阿波罗STM32F429开发板还有MCU屏接口，为了可以同时兼容MCU屏和RGB屏，我们对第17章介绍的lcd.c部分代码做了小改，添加对RGB屏的支持，由于篇幅所限，这里我们只挑几个重点的函数给大家介绍下。

首先读点函数，改为了：

当lcdltdc.pwidth!=0的时候，说明接入的是RGB屏，所以调用LTDC_Read_Point函数，实现读点操作，其他情况，说明是MCU屏，执行MCU屏的读点操作（代码省略）。

然后是画点函数，改为了：

//画点 //x,y:坐标 //POINT_COLOR:此点的颜色void LCD_DrawPoint(u16 x,u16 y) { if(lcdltdc.pwidth!=0)//如果是 RGB 屏 { LTDC_Draw_Point(x,y,POINT_COLOR); }else

……//省略部分代码

}

当lcdltdc.pwidth!=0的时候，说明接入的是RGB屏，所以调用LTDC_Draw_Point函数，实现画点操作，其他情况，说明是MCU屏，执行MCU屏的画点操作（代码省略）。同样的，lcd.c里面的快速画点函数：LCD_Fast_DrawPoint，在使用RGB屏的时候，也是使用LCD_Fast_DrawPoint来实现画点操作的

最后，是LCD初始化函数，改为：

首先通过LTDC_PanelID_Read函数，读取RGBLCD模块的ID值，如果合法，则说明接入了RGB屏，调用LTDC_Init函数，完成对LTDC的初始化。否则的话，执行MCU屏的初始化。在lcd.c里面，其他还有一些函数进行了兼容RGB屏的修改，这里我们就不一一列举了，请大家参考本例程源码。在完成修改后，我们的例程就可以同时兼容MCU屏和RGB屏了，且RGB屏的优先级较高。

接下来，我们看看主函数内容：

该部分代码与第17章几乎一摸一样，显示一些固定的字符，字体大小包括24*12、16*8和12*6等三种，同时显示LCD的型号，然后不停的切换背景颜色，每1s切换一次。而LED0也会不停的闪烁，指示程序已经在运行了。其中我们用到一个sprintf的函数，该函数用法同printf，只是sprintf把打印内容输出到指定的内存区间上，sprintf的详细用法，请百度。

另外特别注意：uart_init函数，不能去掉，因为在LCD_Init函数里面调用了printf，所以一旦你去掉这个初始化，就会死机了!实际上，只要你的代码有用到printf，就必须初始化串口，否则都会死机，即停在usart.c里面的fputc函数，出不来。

在编译通过之后，我们开始下载验证代码。

四、下载验证

将程序下载到阿波罗STM32后，可以看到DS0不停的闪烁，提示程序已经在运行了。同时可以看到RGBLCD模块的显示如图2-54所示：

图2-54 RGBLCD显示效果图

我们可以看到屏幕的背景是不停切换的，同时DS0不停的闪烁，证明我们的代码被正确的执行了，达到了我们预期的目的。最后，需要注意的是：本例程兼容MCU屏，所以，当插入MCU屏的时候（不插RGB屏），也可以显示同样的结果。

摄像头实验

ALIENTEK阿波罗STM32F429开发板具有DCMI接口，并板载了一个摄像头接口（P7），该接口可以用来连接ALIENTEKOV5640/OV2640等摄像头模块。本章，我们将使用STM32驱动ALIENTEKOV5640摄像头模块，实现摄像头功能。本章分为如下几个部分：

OV5640&DCMI简介

硬件设计

软件设计

下载验证

一、OV5640&DCMI简介

本节将分为两个部分，分别介绍OV5640和STM32F429的DCMI接口。另外，所有OV5640的相关资料，都在光盘：A盘→7，硬件资料→OV5640资料文件夹里面。

1.OV5640简介

OV5640是OV（OmniVision）公司生产的一颗1/4寸的CMOSQSXGA（2592*1944）图像传感器，提供了一个完整的500W像素摄像头解决方案，并且集成了自动对焦（AF）功能，具有非常高的性价比。

该传感器体积小、工作电压低，提供单片QSXGA摄像头和影像处理器的所有功能。通过SCCB总线控制，可以输出整帧、子采样、缩放和取窗口等方式的各种分辨率8/10位影像数据。该产品QSXGA图像最高达到15帧/秒（1080P图像可达30帧，720P图像可达60帧，QVGA分辨率时可达120帧）。用户可以完全控制图像质量、数据格式和传输方式。所有图像处理功能过程包括伽玛曲线、白平衡、对比度、色度等都可以通过SCCB接口编程。OmmiVision图像传感器应用独有的传感器技术，通过减少或消除光学或电子缺陷如固定图案噪声、拖尾、浮散等，提高图像质量，得到清晰稳定的彩色图像。

OV5640的特点有：

采用1.4μm*1.4μm像素大小，并且使用OmniBSI技术以达到更高性能（高灵敏度、低串扰和低噪声）

自动图像控制功能：自动曝光（AEC）、自动白平衡（AWB）、自动消除灯光条纹、自动黑电平校准（ABLC）和自动带通滤波器（ABF）等。

支持图像质量控制：色饱和度调节、色调调节、gamma校准、锐度和镜头校准等

标准的SCCB接口，兼容IIC接口

支持RawRGB、RGB(RGB565/RGB555/RGB444)、CCIR656、YUV(422/420)、YCbCr

（422）和压缩图像（JPEG）输出格式

支持QSXGA（500W）图像尺寸输出，以及按比例缩小到其他任何尺寸

支持闪光灯

支持图像缩放、平移和窗口设置

支持图像压缩，即可输出JPEG图像数据

支持数字视频接口（DVP）和MIPI接口

支持自动对焦

自带嵌入式微处理器

OV5640的功能框图图如图2-55所示：

图2-55 OV5640功能框图

其中imagearray部分的尺寸，OV5640的官方数据并没有给出具体的数字，其最大的有效输出尺寸为：2592*1944，即500W像素，我们根据官方提供的一些应用文档，发现其设置的imagearray最大为：2632*1951，所以，在接下来的介绍，我们设定其imagearray最大为2632*1951。

（1）DVP接口说明

OV5640支持数字视频接口（DVP）和MIPI接口，因为我们的STM32F429使用的DCMI接口，仅支持DVP接口，所以，OV5640必须使用DVP输出接口，才可以连接我们的阿波罗STM32开发板。

OV5640提供一个10位DVP接口(支持8位接法)，其MSB和LSB可以程序设置先后顺序，ALIENTEKOV5640模块采用默认的8位连接方式，如图2-56所示：

图2-56 OV5640默认8位连接方式

OV5640的寄存器通过SCCB时序访问并设置，SCCB时序和IIC时序十分类似，在本章我们不做介绍，请大家参考光盘《OmniVisionTechnologiesSerilCameraControlBus (SCCB)Specification》这个文档。

（2）窗口设置说明

接下来，我们介绍一下OV5640的：ISP（ImageSignalProcessor）输入窗口设置、预缩放窗口设置和输出大小窗口设置，这几个设置与我们的正常使用密切相关，有必要了解一下。他们的设置关系，如图2-57所示：

图2-57 OV5640各窗口设置关系

ISP输入窗口设置（ISPinputsize）

该设置允许用户设置整个传感器区域（physicalpixelsize，2632*1951）的感兴趣部分，也就是在传感器里面开窗（X_ADDR_ST、Y_ADDR_ST、X_ADDR_END和Y_ADDR_END），开窗范围从0*0~2632*1951都可以设置，该窗口所设置的范围，将输入ISP进行处理。

ISP输入窗口，通过：0X3800~0X3807等8个寄存器进行设置，这些寄存器的定义请看：

OV5640_CSP3_DS_2.01_Ruisipusheng.pdf这个文档（下同）。

预缩放窗口设置（pre-scalingsize）

该设置允许用户在ISP输入窗口的基础上，再次设置将要用于缩放的窗口大小。该设置仅在ISP输入窗口内进行x/y方向的偏移（X_OFFSET/Y_OFFSET）。通过：0X3810~0X3813等4个寄存器进行设置。

输出大小窗口设置（dataoutputsize）

该窗口是以预缩放窗口为原始大小，经过内部DSP进行缩放处理后，输出给外部的图像窗口大小。它控制最终的图像输出尺寸（X_OUTPUT_SIZE/Y_OUTPUT_SIZE）。通过：0X3808~0X380B等4个寄存器进行设置。注意：当输出大小窗口与预缩放窗口比例不一致时，图像将进行缩放处理（会变形），仅当两者比例一致时，输出比例才是1:1（正常）。

图42.1.4中，右侧dataoutputsize区域，才是OV5640输出给外部的图像尺寸，也就是显示在LCD上面的图像大小。输出大小窗口与预缩放窗口比例不一致时，会进行缩放处理，在LCD上面看到的图像将会变形。

（3）输出时序说明

接下来，我们介绍一下OV5640的图像数据输出时序。首先我们简单介绍一些定义：QSXGA，这里指：分辨率为2592*1944的输出格式，类似的还有：QXGA(2048*1536)、UXGA(1600*1200)、SXGA(1280*1024)、WXGA+(1440*900)、WXGA(1280*800)、XGA(1024*768)、SVGA(800*600)、VGA(640*480)、QVGA(320*240)和QQVGA(160*120)等。

PCLK，即像素时钟，一个PCLK时钟，输出一个像素(或半个像素)。

VSYNC，即帧同步信号。

HREF/HSYNC，即行同步信号。

OV5640的图像数据输出（通过Y[9:0]）就是在PCLK，VSYNC和HREF/HSYNC的控制下进行的。首先看看行输出时序，如图2-58所示：

图2-58 OV5640行输出时序

从上图可以看出，图像数据在HREF为高的时候输出，当HREF变高后，每一个PCLK时钟，输出一个8位/10位数据。我们采用8位接口，所以每个PCLK输出1个字节，且在RGB/YUV输出格式下，每个tp=2个Tpclk，如果是Raw格式，则一个tp=1个Tpclk。比如我们采用QSXGA时序，RGB565格式输出，每2个字节组成一个像素的颜色（低字节在前，高字节在后），这样每行输出总共有2592*2个PCLK周期，输出2592*2个字节。

再来看看帧时序（QSXGA模式），如图2-59所示：

图2-59 OV5640帧时序

上图清楚的表示了OV5640在QSXGA模式下的数据输出。我们按照这个时序去读取OV5640的数据，就可以得到图像数据。

（4）自动对焦（AutoFocus）说明

OV5640由内置微型控制器完成自动对焦，并且VCM（VoiceCoilMotor，即音圈马达）驱动器也已集成在传感器内部。微型控制器的控制固件（firmware）从主机下载。当固件运行后，内置微型控制器从OV5640传感器读得自动对焦所需的信息，计算并驱动VCM马达带动镜头到达正确的对焦位置。主机可以通过IIC命令控制微型控制器的各种功能。

OV5640的自动对焦命令（通过SCCB总线发送），如表2-12所示：

表42.1.1.1OV5640自动对焦命令

地址	寄存器名	描述	值
0X3022	CMD_MAIN	AF主命令寄存器	0X03：触发单次自动对焦过程 0X04：启动持续自动对焦过程 0X06：暂停自动对焦过程 0X08：释放马达回到初始状态 0X12：设置对焦区域 0X00：命令完成
0X3023	CMD_ACK	命令确认	0X00：命令完成 0X01：命令执行中
0X3029	FW_STATUS	对焦状态	0X7F：固件下载完成，但未执行，可能是：固件有问题/微控制器关闭0X7E：固件初始化中 0X70：释放马达，回到初始状态 0X00：正在自动对焦 0X10：自动对焦完成

OV5640内部的微控制器收到自动对焦命令后会自动将CMD_MAIN（0X3022）寄存器数据清零，当命令完成后会将CMD_ACK（0X3023）寄存器数据清零。

自动对焦（AF）过程

①在第一次进入图像预览的时候（图像可以正常输出时），下载固件（firmware）

②拍照前，自动对焦，对焦完成后，拍照

③拍照完毕，释放马达到初始状态接下来，我们分别说明。

1）下载固件

OV5640初始化完成后，就可以下载AF自动对焦固件了，其操作和下载初始化参数类似，AF固件下载地址为：0X8000，初始化数组由厂家提供（本例程该数组保存在ov5640af.h里面），下载固件完成后，通过检查0X3029寄存器的值，来判断固件状态（等于0X70，说明正常）。

2）自动对焦

OV5640支持单次自动对焦和持续自动对焦，通过0X3022寄存器控制。单次自动对焦过程如下：

将0X3022寄存器写为0X03，开始单点对焦过程。

读取寄存器0X3029，如果返回值为0X10，代表对焦已完成。

写寄存器0X3022为0X06，暂停对焦过程，使镜头将保持在此对焦位置。

其中，前两步是必须的，第三步，可以不要，因为单次自动对焦完成以后，就不会继续自动对焦了，镜头也就不会动了。

持续自动对焦过程如下：

将0X22寄存器写为0X08，释放马达到初始位置（对焦无穷远）。

将0X3022寄存器写为0X04，启动持续自动对焦过程。

读取寄存器0X3023，等待命令完成。

当OV5640每次检测到失焦时，就会自动进行对焦（一直检测）。

3）释放马达，结束自动对焦

最后，在拍照完成，或者需要结束自动对焦的时候，我们对在寄存器0X3022写入0X08，即可释放马达，结束自动对焦。

最后说一下OV5640的图像数据格式，我们一般用2种输出方式：RGB565和JPEG。当输出RGB565格式数据的时候，时序完全就是上面两幅图介绍的关系。以满足不同需要。而当输出数据是JPEG数据的时候，同样也是这种方式输出（所以数据读取方法一模一样），不过PCLK数目大大减少了，且不连续，输出的数据是压缩后的JPEG数据，输出的JPEG数据以：0XFF,0XD8开头，以0XFF,0XD9结尾，且在0XFF,0XD8之前，或者0XFF,0XD9之后，会有不定数量的其他数据存在（一般是0），这些数据我们直接忽略即可，将得到的0XFF,0XD8~0XFF,0XD9之间的数据，保存为.jpg/.jpeg文件，就可以直接在电脑上打开看到图像了。

OV5640自带的JPEG输出功能，大大减少了图像的数据量，使得其在网络摄像头、无线视频传输等方面具有很大的优势。OV5640我们就介绍到这，关于OV5640更详细的介绍，请大家参考：A盘→7，硬件资料→OV5640资料→OV5640_CSP3_DS _2.01_Ruisipusheng.pdf。

2.STM32F429DCMI接口简介

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

STM32F429DCM接口特点：

8位、10位、12位或14位并行接口

内嵌码/外部行同步和帧同步

连续模式或快照模式

裁剪功能

支持以下数据格式：

8/10/12/14位逐行视频：单色或原始拜尔（Bayer）格式

YCbCr4:2:2逐行视频

RGB565逐行视频

压缩数据：JPEG

DCMI接口包括如下一些信号：

数据输入（D[0:13]），用于接摄像头的数据输出，接OV5640我们只用了8位数据。

水平同步（行同步）输入（HSYNC），用于接摄像头的HSYNC/HREF信号。

垂直同步（场同步）输入（VSYNC），用于接摄像头的VSYNC信号。

像素时钟输入（PIXCLK），用于接摄像头的PCLK信号。

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

DCMI接收到的摄像头数据被放到一个32位数据寄存器(DCMI_DR)中，然后通过通用DMA进行传输。图像缓冲区由DMA管理，而不是由摄像头接口管理。

从摄像头接收的数据可以按行/帧来组织（原始YUV/RGB/拜尔模式），也可以是一系列JPEG图像。要使能JPEG图像接收，必须将JPEG位（DCMI_CR寄存器的位3）置1。数据流可由可选的HSYNC（水平同步）信号和VSYNC（垂直同步）信号硬件同步，或者通过数据流中嵌入的同步码同步。

STM32F429DCMI接口的框图如图2-60所示：

图2-60 DCMI接口框图

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

图2-61 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位字中的排布如表2-13所示：

表2-13 位捕获数据在32位字中的排布

从表42.1.2.1可以看出，STM32F429的DCMI接口，接收的数据是低字节在前，高字节在后的，所以，要求摄像头输出数据也是低字节在前，高字节在后才可以，否则就还得程序上处理字节顺序，会比较麻烦。

DCMI接口支持DMA传输，当DCMI_CR寄存器中的CAPTURE位置1时，激活DMA接口。摄像头接口每次在其寄存器中收到一个完整的32位数据块时，都将触发一个DMA请求。DCMI接口支持两种同步方式：内嵌码同步和硬件（HSYNC和VSYNC）同步。我们简单介绍下硬件同步，详细介绍请参考《STM32F4xx中文参考手册》第13.5.3节。

硬件同步模式下将使用两个同步信号(HSYNC/VSYNC)。根据摄像头模块/模式的不同，可能在水平/垂直同步期间内发送数据。由于系统会忽略HSYNC/VSYNC信号有效电平期间内接收的所有数据，HSYNC/VSYNC信号相当于消隐信号。

为了正确地将图像传输到DMA/RAM缓冲区，数据传输将与VSYNC信号同步。选择硬件同步模式并启用捕获（DCMI_CR中的CAPTURE位置1）时，数据传输将与VSYNC信号的无效电平同步（开始下一帧时）。之后传输便可以连续执行，由DMA将连续帧传输到多个连续的缓冲区或一个具有循环特性的缓冲区。为了允许DMA管理连续帧，每一帧结束时都将激活VSIF（垂直同步中断标志，即帧中断），我们可以利用这个帧中断来判断是否有一帧数据采集完成，方便处理数据。

DCMI接口的捕获模式支持：快照模式和连续采集模式。一般我们使用连续采集模式，通过DCMI_CR中的CM位设置。另外，DCMI接口还支持实现了4个字深度的FIFO，配有一个简单的FIFO控制器，每次摄像头接口从AHB读取数据时读指针递增，每次摄像头接口向FIFO写入数据时写指针递增。因为没有溢出保护，如果数据传输率超过AHB接口能够承受的速率，FIFO中的数据就会被覆盖。如果同步信号出错，或者FIFO发生溢出，FIFO将复位，DCMI接口将等待新的数据帧开始。

关于DCMI接口的其他特性，我们这里就不再介绍了，请大家参考《STM32F4xx中文参考手册》第13章相关内容。

本章，我们将使用STM32F429IGT6的DCMI接口连接ALIENTEKOV5640摄像头模块，该模块采用8位数据输出接口，自带24M有源晶振，无需外部提供时钟，模组支持自动对焦功能，且支持闪光灯，整个模块只需提供3.3V供电即可正常使用。

ALIENTEKOV5640摄像头模块外观如图2-62所示：

图2-62 ALIENTEKOV5640摄像头模块外观图

模块原理图如图2-63所示：

图2-63 ALIENTEKOV5640摄像头模块原理图

从上图可以看出，ALIENTEKOV5640摄像头模块自带了有源晶振，用于产生24M时钟作为OV5640的XCLK输入，模块的闪光灯（LED1&LED2）由OV5640的STROBE脚控制（可编程控制）。同时自带了稳压芯片，用于提供OV5640稳定的2.8V和1.5V工作电压，模块通过一个2*9的双排排针（P1）与外部通信，与外部的通信信号如表2-14所示：

表2-14 OV5640模块信号及其作用描述

信号	作用描述	信号	作用描述
VCC3.3	模块供电脚，接3.3V电源	OV_PCLK	像素时钟输出
GND	模块地线	OV_PWDN	掉电使能(高有效)
OV_SCL	SCCB通信时钟信号	OV_VSYNC	帧同步信号输出
OV_SDA	SCCB通信数据信号	OV_HREF	行同步信号输出
OV_D[7:0]	8位数据输出	OV_RESET	复位信号(低有效)

本章，我们将OV5640默认配置为WXGA输出，也就是1280*800的分辨率，输出信号设置为：VSYNC高电平有效，HREF高电平有效，输出数据在PCLK的下降沿输出（即上升沿的时候，MCU才可以采集）。这样，STM32F429的DCMI接口就必须设置为：VSYNC低电平有效、HSYNC低电平有效和PIXCLK上升沿有效，这些设置都是通过DCMI_CR寄存器控制的，该寄存器描述如图2-64所示：

图2-64 DCMI_CR寄存器各位描述

ENABLE，该位用于设置是否使能DCMI，不过，在使能之前，必须将其他配置设置好。FCRC[1:0]，这两个位用于帧率控制，我们捕获所有帧，所以设置为00即可。

VSPOL，该位用于设置垂直同步极性，也就是VSYNC引脚上面，数据无效时的电平状态，根据前面说所，我们应该设置为0。

HSPOL，该位用于设置水平同步极性，也就是HSYNC引脚上面，数据无效时的电平状态，同样应该设置为0。

PCKPOL，该位用于设置像素时钟极性，我们用上升沿捕获，所以设置为1。CM，该位用于设置捕获模式，我们用连续采集模式，所以设置为0即可。

CAPTURE，该位用于使能捕获，我们设置为1。该位使能后，将激活DMA，DCMI等待第一帧开始，然后生成DMA请求将收到的数据传输到目标存储器中。注意：该位必须在DCMI的其他配置（包括DMA）都设置好了之后，才设置！！

DCMI_CR寄存器的其他位，我们就不介绍了，另外DCMI的其他寄存器这里也不再介绍，请大家参考《STM32F4xx中文参考手册》第13.8节。

最后，我们来看下用DCMI驱动OV5640的步骤：

1）配置OV5640控制引脚，并配置OV5640工作模式。

在启动DCMI之前，我们先设置好OV5640。OV5640通过OV_SCL和OV_SDA进行寄存器配置，同时还有OV_PWDN/OV_RESET等信号，我们也需要配置对应IO状态，先设置OV_PWDN=0，退出掉电模式，然后拉低OV_RESET复位OV5640，之后再设置OV_RESET为1，结束复位，然后就是对OV5640的大把寄存器进行配置了。然后，可以根据我们的需要，设置成RGB565输出模式，还是JPEG输出模式。

2）配置相关引脚的模式和复用功能（AF13），使能时钟。

OV5640配置好之后，再设置DCMI接口与摄像头模块连接的IO口，使能IO和DCMI时钟，然后设置相关IO口为复用功能模式，复用功能选择AF13(DCMI复用)。

DCMI时钟使能方法：

HAL_RCC_DCMI_CLK_ENABLE();	//使能 DCMI 时钟

引脚模式配置就是通过HAL_GPIO_Init函数来配置，这里就不多说了。

3）配置DCMI相关设置，初始化DCMI接口。

这一步，主要通过DCMI_CR寄存器设置，包括VSPOL/HSPOL/PCKPOL/数据宽度等重要参数，都在这一步设置。HAL库提供了DCMI初始化函数HAL_DCMI_Init，函数声明如下：

HAL_StatusTypeDefHAL_DCMI_Init(DCMI_HandleTypeDef*hdcmi);

结构体DCMI_HandleTypeDef定义为：

该结构体第一个成员变量Instance用来指向寄存器基地址，设置为DCMI即可。

成员变量XferCount，XferSize，XferTransferNumber，pBuffPtr和DMA_Handle是与HAL库中DMA处理相关中间变量，由于使用HAL库配置的DCMIDMA会非常复杂，而且灵活性不高，所以本实验我们是自由独立配置的DMA。

成员变量Init是DCMI_InitTypeDef结构体类型，该结构体定义为：

成员变量SynchroMode用来选择同步方式为硬件同步还是内嵌码同步。如果选择硬件同步值DCMI_SYNCHRO_HARDWARE，那么数据捕获由HSYNC/VSYNC信号同步，如果选择内嵌码同步方式值DCMI_SYNCHRO_EMBEDDED，那么数据捕获由数据流中嵌入的同步码同步。

成员变量PCKPolarity用来设置像素时钟极性为上升沿有效还是下降沿有效。我们实验使用的是上升沿有效，所以值为DCMI_PCKPOLARITY_RISING。

成员变量VSPolarity用来设置垂直同步极性VSYNC为低电平有效还是高电平有效。也就是VSYNC引脚上面，数据无效时的电平状态。我们设置为VSYNC低电平有效。所以值为DCMI_VSPOLARITY_LOW。

成员变量HSPolarity用来设置水平同步极性为高电平有效还是低电平有效，也就是HSYNC引脚上面，数据无效时的电平状态。我们设置为HSYNC低电平有效。所以值为DCMI_HSPOLARITY_LOW。

成员变量CaptureRate用来设置帧捕获率。如果设置为值DCMI_CR_ALL_FRAME，也就是全帧捕获，设置为DCMI_CR_ALTERNATE_2_FRAME，也就2帧捕获一帧，设置为DCMI_CR_ALTERNATE_4_FRAME，也就是4帧捕获一帧。

成员变量ExtendedDataMode用来设置扩展数据模式。可以设置为每个像素时钟捕获8位，

10位，12位以及14位数据。这里我们设置为8位值DCMI_EXTEND_DATA_8B。成员变量JPEGMode用来设置JPEG格式使能。

成员变量SyncroCode用来设置分隔码，包括：帧结束分隔码，行结束分隔码，行开始分隔码以及帧开始分隔码。

成员变量DCMI_CaptureMode是用来设置捕获模式为连续捕获模式还是快照模式。我们实验采取的是连续捕获模式值DCMI_CaptureMode_Continuous，也就是通过DMA连续传输数据到目标存储区。

函数HAL_DCMI_Init初始化实例为：

同样，HAL库也提供了DCMI接口的MSP初始化回调函数：

voidHAL_DCMI_MspInit(DCMI_HandleTypeDef*hdcmi);

一般情况下，该函数内部编写时钟使能，IO初始化以及NVIC相关程序。

4）配置DMA。

本章采用连续模式采集，并将采集到的数据输出到LCD（RGB565模式）或内存（JPEG模式），所以源地址都是DCMI_DR，而目的地址可能是LCD->RAM或者SRAM的地址。DCMI的DMA传输采用的是DMA2数据流1的通道1来实现的，关于DMA的介绍，请大家参考前面的DMA实验章节。这里我们列出本章我们的DMA配置源码如下：

5）设置OV5640的图像输出大小，使能DCMI捕获。

图像输出大小设置，分两种情况：在RGB565模式下，我们根据LCD的尺寸，设置输出图像大小，以实现全屏显示（图像可能因缩放而变形）；在JPEG模式下，我们可以自由设置输出图像大小（可不缩放）；最后，开启DCMI捕获，即可正常工作了。

二、硬件设计

本章实验功能简介：开机后，初始化摄像头模块（OV5640），如果初始化成功，则提示选择模式：RGB565模式，或者JPEG模式。KEY0用于选择RGB565模式，KEY1用于选择JPEG模式。

当使用RGB565时，输出图像（固定为：WXGA）将经过缩放处理（完全由OV5640的DSP控制），显示在LCD上面（默认开启连续自动对焦）。我们可以通过KEY_UP按键选择：1:1显示，即不缩放，图片不变形，但是显示区域小（液晶分辨率大小），或者缩放显示，即将1280*800的图像压缩到液晶分辨率尺寸显示，图片变形，但是显示了整个图片内容。通过KEY0按键，可以设置对比度；KEY1按键，可以启动单次自动对焦；KEY2按键，可以设置特效。当使用JPEG模式时，图像可以设置任意尺寸（QSXGA~QQVGA），采集到的JPEG数据将先存放到STM32F429的SDRAM内存里面，每当采集到一帧数据，就会关闭DMA传输，然后将采集到的数据发送到串口2（此时可以通过上位机软件（ATK-CAM.exe）接收，并显示图片），之后再重新启动DMA传输。我们可以通过KEY_UP设置输出图片的尺寸（QSXGA~QQVGA）。通过KEY0按键，可以设置对比度；KEY1按键，可以启动单次自动对焦；KEY2按键，可以设置特效。

同时时可以通过串口1，借助USMART设置/读取OV5640的寄存器，方便大家调试。DS0指示程序运行状态，DS1用于指示帧中断。

本实验用到的硬件资源有：

指示灯DS0和DS1

4个按键

串口1和串口2

LCD模块

PCF8574T

OV5640摄像头模块

这些资源，基本上都介绍过了，这里我们用到串口2来传输JPEG数据给上位机，其配置同串口1几乎一模一样，只是串口2的时钟来自APB1，频率为45Mhz。开发板板载的摄像头模块接口与MCU的连接如图2-65所示：

图2-65摄像头模块接口与STM32连接图

图中P7就是摄像头模块/OLED模块共用接口，在第十六章，我们曾简单介绍过这个接口，它在开发板的左下角，是一个2*9的排座（P7）。本章，我们只需要将ALIENTEKOV5640摄像头模块插入这个接口即可。

从图42.2.1可以看出，OV5640摄像头模块的各信号脚与STM32的连接关系为：

DCMI_VSYNC接PB7;

DCMI_HREF接PH8;

DCMI_PCLK接PA6;

DCMI_SCL接PB4;

DCMI_SDA接PB3;

DCMI_RESET接PA15;

DCMI_PWDN接PCF8574T的P2脚;

DCMI_XCLK接PA8（本章未用到）;

DCMI_D[7:0]接PB9/PB8/PD3/PC11/PC9/PC8/PC7/PC6;

这些线的连接，阿波罗STM32F429开发板的内部已经连接好了，我们只需要将OV5640摄像头模块插上去就好了。特别注意：DCMI摄像头接口和SDIO以及红外接收头有冲突，使用的时候，必须分时复用才可以，不可同时使用。另外，DCMI_PWDN连接在PCF8574T的P2脚上，所以本章必须使用PCF8574T，来间接控制DCMI_PWDN。

实物连接如图2-66所示：

图2-66 OV5640摄像头模块与开发板连接实物图

三、软件设计

打开本章实验工程可以看到，因为本实验要使用定时器和串口2，所以我们添加了timer.c

和usart2.c文件。同时新建了dcmi.c/dcmi.h，sccb.c/sccb.h以及ov5540.c/ov5540.h等文件。由于本实验代码比较多，我们就不一一列出了，仅挑几个重要的地方进行讲解。

首先，我们来看ov5640.c里面的OV5640_Init函数，该函数代码如下： //OV5640 写寄存器 //返回值:0,成功;1,失败. u8 OV5640_WR_Reg(u16 reg,u8 data) {

此部分代码先初始化OV2640相关的IO口（包括PCF8574_Init和SCCB_Init），然后最主要的是完成OV5640的寄存器序列初始化。OV5640的寄存器特多（百几十个），配置特麻烦，幸好厂家有提供参考配置序列（详见《OV5640_camera_module_software_ application_notes_1.3_Sonix.pdf》），本章我们用到的配置序列，存放在ov5640_init_reg_tbl这个数组里面，该数组是一个2维数组，存储初始化序列寄存器及其对应的值，该数组存放在ov5640cfg.h里面。

另外，在ov5640.c里面，还有几个函数比较重要，这里不贴代码了，只介绍功能：OV5640_ImageWin_Set函数，该函数用于设置ISP输入窗口；OV5640_OutSize_Set函数，用于设置预缩放窗口和输出大小窗口；OV5640_Focus_Init函数，用于初始化自动对焦功能；

OV5640_Focus_Single函数，用于实现一次自动对焦；

OV5640_Focus_Constant函数，用于开启持续自动对焦功能；

OV5640_ImageWin_Set和OV5640_OutSize_Set这就是我们在42.1.1节所介绍的3个窗口的设置，他们共同决定了图像的输出。

//JPEG 配置.7.5 帧 //最大支持 2592*1944 的JPEG 图像输出 const u16 OV5640_jpeg_reg_tbl[][2]= { 0x4300, 0x30, // YUV 422, YUYV ……//省略部分代码 0x3503, 0x00, // AEC/AGC on }; //RGB565 配置.15 帧 //最大支持 1280*800 的RGB565 图像输出 const u16 ov5640_rgb565_reg_tbl[][2]= { 0x4300, 0X6F, ……//省略部分代码 0x3503, 0x00, // AEC/AGC on }; //OV5640 初始化寄存器序列表 const u16 ov5640_init_reg_tbl[][2]= { // 24MHz input clock, 24MHz PCLK 0x3008, 0x42, // software power down, bit[6] ……//省略部分代码 0x4740, 0X21, //VSYNC 高有效 }; 接下来，我们看看ov5640cfg.h里面ov5640_init_reg_tbl的内容，ov2640cfg.h文件的代码如下：

以上代码，我们省略了很多（全部贴出来太长了），里面总共有3个数组。我们大概了解下数组结构，每个数组条目的第一个字节为寄存器号（也就是寄存器地址），第二个字节为要设置的值，比如{0x4300,0x30}，就表示在0x4300地址，写入0X30这个值。

这里面：ov5640_init_reg_tbl数组，用于初始化OV5640，该数组必须最先进行配置；ov5640_rgb565_reg_tbl数组，用于设置OV5640的输出格式为RGB565，分辨率为1280*800，帧率为15帧，在RGB模式下使用；OV5640_jpeg_reg_tbl用于设置OV5640的输出格式为JPEG，分辨率为2592*1944，帧率为7.5帧，在JPEG模式下使用。接下来，我们看看dcmi.c里面的代码，如下：

其中：DCMI_IRQHandler函数，用于处理帧中断，可以实现帧率统计（需要定时器支持）和JPEG数据处理等，实际上当捕获到一帧数据后，调用的是HAL库回调函数HAL_DCMI_FrameEventCallback进行处理，关于中断处理回调函数前面实验多次讲解，这里我们就不重复讲解处理过程了。DCMI_DMA_Init函数，则用于配置DCMI的DMA传输，其外设地址固定为：DCMI->DR，而存储器地址可变（LCD或者SRAM）。DMA被配置为循环模式，一旦开启，DMA将不停的循环传输数据。DMA2_Stream1_IRQHandler函数，用于在使用RGB屏的时候，双缓冲存储时，数据的搬运处理（通过dcmi_rx_callback函数实现）。DCMI_Init函数用于初始化STM32F429的DCMI接口，这是根据在42.1.2节提到的配置步骤进行配置的。最后，DCMI_Start和DCMI_Stop两个函数，用于开启或停止DCMI接口。

其他部分代码我们就不再细说了，请大家参考光盘本例程源码（实验37摄像头实验）。最后，打开main.c文件，代码如下：

u8 ovx_mode=0; u16 curline=0; u16 yoffset=0;	//bit0:0,RGB565 模式;1,JPEG 模式 //摄像头输出数据,当前行编号 //y 方向的偏移量
#define jpeg_buf_size	1*1024*1024	//定义 JPEG 缓存 jpeg_buf 的大小(1*4M 字节)
#define jpeg_line_size	2*1024	//定义 DMA 接收数据时,一行数据的最大值
u32 dcmi_line_buf[2][jpeg_line_size]; //RGB 屏时,数据一行一行读取,定义行缓存 u32 jpeg_data_buf[jpeg_buf_size] attribute ((at(0XC0000000+1280*800*2))); //JPEG 数据缓存 buf,定义在 LCD 帧缓存之后 volatile u32 jpeg_data_len=0; //buf 中的JPEG 有效数据长度 volatile u8 jpeg_data_ok=0; //JPEG 数据采集完成标志 //0,数据没有采集完; //1,数据采集完了,但是还没处理; //2,数据已经处理完成了,可以开始下一帧接收

这部分代码比较长，我们省略了一些内容。详细的代码，请大家参考光盘本例程源码。注意，这里定义了一个非常大的数组jpeg_data_buf（4MB），用来存储JPEG数据，因为2592*1944大小的jpeg图片，有可能大于3MB，所以必须将这个数组尽量设置大一点。这个数组，我们定义在SDRAM，由attribute关键字，指定数组地址，紧跟LTDCGRAM后的地址存放。

在main.c里面，总共有6个函数，我们接下来分别介绍。

1）jpeg_data_process函数

该函数用于处理JPEG数据的接收，在DCMI_IRQHandler函数（在dcmi.c里面）里面被调用，它与jpeg_dcmi_rx_callback函数和jpeg_test函数共同控制JPEG的数据传送。JPEG数据的接收，采用DMA双缓冲机制，缓冲数组为：dcmi_line_buf（u32类型，RGB屏接收RGB565数据时，也是用这个数组）；数组大小为：jpeg_line_size，我们定义的是2*1024，即数组大小为8K字节（数组大小不能小于存储摄像头一行输出数据的大小）；JPEG数据接收处理流程如图2-67所示：

JPEG数据流 DMA M0AR(内存1)满 M1AR(内存2)满 由M1AR存储(内存2) 程序读取M0AR(内存1) 由M0AR存储(内存1) 程序读取M1AR(内存2)

图2-67 JPEG数据流DMA双缓冲接收流程

JPEG数据采集流程：当JPEG数据流传输给MCU的时候，首先由M0AR存储，此时如果M1AR有数据，则可以读取M1AR里面的数据，当M0AR数据满时，由M1AR存储，此时程序可以读取M0AR里面所存储的数据，当M1AR数据满时，由M0AR存储……。这个存储数据的操作，绝大部分是由DMA传输完成中断服务函数，调用jpeg_dcmi_rx_callback函数实现的，当一帧数据传输完成时，会进入DCMI帧中断服务函数，调用jpeg_data_process函数，对最后的剩余数据进行存储，完成一帧JPEG数据的采集。

2）jpeg_dcmi_rx_callback函数

这是jpeg数据接收的主要函数，通过判断DMA2_Stream1->CR寄存器，读取不同buf里面的数据，存储到SDRAM里面（jpeg_data_buf）。该函数由DMA的传输完成中断服务函数：DMA2_Stream1_IRQHandler调用。

3）jpeg_test函数

该函数将OV5640设置为JPEG模式，并开启持续自动对焦，该函数实现OV5640的JPEG数据接收，并通过串口2发送给上位机软件。

4）rgblcd_dcmi_rx_callback函数

该函数仅在使用RGB屏，且使用RGB565模式的时候用到。当使用RGB屏的时候，我们每接收一行数据，就使用DMA2D填充到RGB屏的GRAM，这里同样是使用DMA的双缓冲机制来接收RGB565数据，原理参照图42.3.1。该函数由DMA传输完成中断服务函数调用。

5）rgb565_test函数

该函数将OV5640设置为RGB565模式，并将接收到的数据，传送给LCD。当使用MCU屏的时候，完全由硬件DMA传输给LCD，CPU不用处理；当使用RGB屏的时候，数据先由DMA接收到双缓存里面，然后在DMA传输完成中断服务函数里面，调用函数：rgblcd_dcmi_rx_callback，将接收到的数据，用DMA2D填充到RGBLCD，显示到屏幕上。

6）main函数

该函数完成对各相关硬件的初始化，然后检测OV5640，最后通过按键选择来调用jpeg_test还是rgb565_test，实现JPEG测试和RGB565测试。

前面提到，我们要用USMART来设置摄像头的参数，我们只需要在usmart_nametab里面添加OV5640_WR_Reg和OV5640_RD_Reg等相关函数，就可以轻松调试摄像头了。

四、下载验证

在代码编译成功之后，我们通过下载代码到ALIENTEK阿波罗STM32开发板上，在OV5640初始化成功后，屏幕提示选择模式，此时我们可以按KEY0，进入RGB565模式测试，也可以按KEY1，进入JPEG模式测试。

当按KEY0后，选择RGB565模式，LCD满屏显示压缩放后的图像（有变形），如图2-68所示：

图2-68 RGB565模式测试图片

此时，可以按KEY_UP切换为1:1显示（不变形）。同时还可以通过KEY0按键，设置对比度；KEY1按键，执行一次自动对焦；KEY2按键，设置特效。

当按KEY1后，选择JPEG模式，此时屏幕显示JPEG数据传输进程，如图2-69所示：

图2-69 JPEG模式测试图

默认条件下，图像分辨率是VGA(640*480)的，硬件上：我们需要一根RS232串口线连接开发板的COM2（注意要用跳线帽将P8的：COM2_RX连接在PA2(TX)）。如果没有RS232线，也可以借助我们开发板板载的USB转串口实现（有2个办法：1，改代码，将串口2输出改到串口1；2，杜邦线连接P8的PA2(TX)和P4的RXD）。

我们打开上位机软件：ATK-CAM.exe（路径：光盘6，软件资料软件串口&网络摄像头软件ATK-CAM.exe），选择正确的串口，然后波特率设置为921600，打开即可收到下位机传过来的图片了，如图2-70所示：

图2-70 ATK-CAM软件接收并显示JPEG图片

我们可以通过KEY_UP设置输出图像的尺寸（QQVGA~QSXGA）。通过KEY0按键，设置对比度；KEY1按键，执行一次自动对焦；KEY2按键，设置特效。

同时，你还可以在串口（开发板的串口1），通过USMART调用SCCB_WR_Reg等函数，来设置OV5640的各寄存器，达到调试测试OV5640的目的，如图2-71所示：

图2-71 USMART调试OV5640

从上图还可以看出，帧率为7/8帧（实际上是7.5帧），每张JPEG图片的大小是33KB左右（分辨率为：640*480的时候）。

第三篇硬件篇（发送端）

传感器介绍

一、DHT11数字式温湿度传感器

DHT11可以同时检测到温度和湿度，实物图如图3-1。

图3-1DHT11实物图

DHT11是一款内置校准程序的数字型复合传感器，由电感元件，NTC温度测量元件和高性能微型计算机组成。如图3-2。

图3-2DHT11内部结构图

如表3-1，DHT11为单排4针引脚封装，但其真正只使用到3个引脚，分别是接电源的1脚、接地的2脚和数据脚3脚。DHT11的电源输入要求在3.3-5.5V之间，所以在实验中可以直接用系统自带的5V电压对它进行供电。DHT11的数据接口采用了单总线，可简化系统集成和加快运行速度。

表3-1DHT11引脚分配

DHT11将温度和湿度信息采集集成到单个传感器中，采用单总线控制并直接输出数字信号，大大减少了应用使用电路复杂度和体积。只需要通过端口就可以完成数据的输入输出双向传输。数据包一共40bit数据，数据分为整数部分和小数部分，高位先出。格式如表3-2所示。

表3-2DHT11数据帧格式

8bit	8bit	8bit	8bit	8bit
湿度整数	湿度小数	温度整数	温度小数	校验和

DHT11与MCU一次通信所需时间约为3ms，最小连续通信时间间隔不得小于100ms。数据发送流程如图3-3所示。

图3-3数据发送流程

主机发送开始信号后，延时等待20us-40us后读取DHT11的回应信号，读取总线为低电平，说明DHT11发送响应信号，DHT11发送响应信号，再把总线拉高，准备发送数据，每1bit数据都以低电平开始，之后由不同持续时长的高电平来区分“0”和“1”，26-28us的高电平表示“0”，116-118us的高电平表示“1”。

二、MQ-2可燃气体传感器

MQ-2是一种便宜的气体传感器,非常适合检测可燃气体，实物如图3-4。

图3-4MQ-2实物图

MQ-2对不同的气体有不同的灵敏度，如图3-5。

图3-5MQ-2对不同气体的灵敏度曲线

MQ-2的主要技术指标如表3-3。

表3-3MQ-2主要技术指标

我们用一个简单的电路测试出，想要MQ-2正常工作，需施加2个电压：加热器电压（）和测试电压（）。其中用于为传感器提供特定的工作温度，可用直流电源或交流电源。是传感器串联的负载电阻()上的电压。是为负载电阻提供测试的电压，须用直流电源，如图3-6。

图3-6测试电路

MQ-2使用的气体敏感材料是二氧化锌（SnO2）。当传感器所处环境中存在可燃气体时，传感器的电导率随空气中可燃气体浓度的增加而增大。通过简单的电路，电导率的变化可以转换为对应于输出信号的气体信号，转化过程如图3-7。

图37A/D转换流程图

三、MQ-131臭氧传感器

MQ-131臭氧传感器具有较高的臭氧敏感性，并且对强氧化剂气体也具有一定探测效果，例如氯气或二氧化氮。

它与MQ-2长相一样，注意通过标注区分。MQ-131中使用的气体敏感材料是在洁净空气中具有高电导率的半导体金属氧化物。如果传感器周围有臭氧，则传感器的连通性将随着空气中臭氧气体浓度的增加而减小。可以使用一个简单的电路将电导率的变化转换成与气体浓度相等的气体信号。

图3-8为MQ-131传感器的基本测试电路。该传感器需施加2个电压：加热器电压（）和测试电压（）。其中用于为传感器提供特定的工作温度，可用直流电源或交流电源。是传感器串联的负载电阻()上的电压。是为负载电阻提供测试的电压，须用直流电源。

图3-8测试电路

四、PMS5003颗粒物浓度传感器

PMS5003是基于激光散射原理的数字式通用粒子浓度传感器，悬浮在空气中粒子会使激光束散射，传感器以一定角度收集散射光，获得了散射光的强度随时间的曲线。使用基于米氏理论的算法，获得单位体积的等效粒径颗粒数及不等效粒径颗粒数（等效粒径即粒子直径相同）。PMS5003可以连续工作，计算空颗粒浓度分布，并将其转换为质量浓度，输出为数字信号。该传感器可以集成到与空气中颗粒物浓度有关的各种设备中以及环境改善设备中，以提供及时而准确的浓度数据。

传感器实物图如图3-9：

图3-9PMS5003实物图

传感器各功能部分框图如图3-10：

图3-10PMS5003各功能部分框图

PMS5003是攀藤科技第五代颗粒物浓度传感器，采用TTL串口传输的形式传输数据，STM32通过串口3接收传感器发送的数据，传输波特率9600bps，8位数据位，1位停止位，协议总长度32字节。它的具体数据格式如表3-4。

STM32串口中断接收到帧开始标志后开始接收数据，接收完成后将接收完成标志位置“1”，交由PMS5003数据处理函数处理。该函数主要功能是计算整个帧的校验和，如果校验通过，将处理后的数据放入PMS结构体。

表3-4PMS5003数据帧结构

起始符1	0x42
起始符2	0x4d
帧长度高八位	帧长度=2*13+2*(数据+校验位)
帧长度低八位
数据1高八位	数据1表示PM1.0浓度（CF=1，标准颗粒物）单位μg/m3
数据1低八位
数据2高八位	数据2表示PM2.5浓度（CF=1，标准颗粒物）单位μg/m3
数据2低八位
数据3高八位	数据3表示PM10浓度（CF=1，标准颗粒物）单位μg/m3
数据3低八位
数据4高八位	数据4表示PM1.0浓度（大气环境下）单位μg/m3
数据4低八位
数据5高八位	数据5表示PM2.5浓度（大气环境下）单位μg/m3
数据5低八位
数据6高八位	数据6表示PM10浓度（大气环境下）单位μg/m3
数据6低八位
数据7高八位	数据7表示0.1升空气中直径在0.3um以上颗粒物个数
数据7低八位
数据8高八位	数据8表示0.1升空气中直径在0.5um以上颗粒物个数
数据8低八位
数据9高八位	数据9表示0.1升空气中直径在1.0um以上颗粒物个数
数据9低八位
数据10高八位	数据10表示0.1升空气中直径在2.5um以上颗粒物个数
数据10低八位
数据11高八位	数据11表示0.1升空气中直径在5.0um以上颗粒物个数
数据11低八位
数据12高八位	数据12表示0.1升空气中直径在10um以上颗粒物个数
数据12低八位
数据13高八位	版本号
数据13低八位	错误代码
数据和校验高八位	校验码=起始符1+起始符2+……..+数据13低八位
数据和校验低八位

注：标准颗粒物质量浓度值是指用工业金属颗粒物作为等效颗粒进行密度换算得到的质量浓度值，适用于工业生产车间等环境。大气环境颗粒物质量浓度值以空气中主要污染物为等效颗粒进行密度换算，适用于普通室内外大气环境。

图3-11PMS5003和MCU连线图

五、SGP30可燃性气体传感器

SGP30是一种数字多像素气体传感器，可以轻松集成到空气净化器、按需控制的通风和物联网应用中。

图3-12SGP30实物图

Sensirion的CMOSens®技术在单个芯片上提供了完整的传感器系统，SGP30带有数字I2C接口、温度控制的微型加热板和两个用于监测室内空气质量的预处理器。作为第一个在单个芯片上具有多个测量元件的金属氧化物气体传感器，SGP30提供了有关空气质量的更多详细信息。电感元件对实际应用中存在的污染物气体具有前所未有的坚固性，具有独特的长期稳定性和低漂移特性。尺寸为2.45x2.45x0.9毫米的小型3DFN外壳可以在最小的空间内使用。Sensirion的最先进制造工艺可确保高度可重复性和可靠性。

如图为SGP30信号路径：

图3-13SGP30信号路径

SGP30传感器可测量四种气体，分别为氢气(H2)、乙醇(Ethanol)、二氧化碳(CO2)和易挥发性有机物(TVOC)。通过SGP30中的信号处理部分后，最先得到H2和Ethanol浓度，然后在前两种气体的浓度基础上，TVOC与CO2通过基线补偿算法得出。在本实验中所用到的只是TVOC和CO2的数据，SGP30_ad_read()读出的是一个32位的数据。前16位为CO2，后16位为TVOC。

ESP8266无线模块介绍

国内外有许多制造商和类型的WIFI芯片，它们具有不同的性能，但都实现了物联网的功能。其中，ESP8266是一款高性能，低功耗的无线芯片。随着智能产品的普及，无线技术的持续快速发展，各种通信接口的替换，ZigBee技术，蓝牙技术，WIFI技术始终伴随着人存在，基于这种技术的产品将使人们的生活受益。在这些技术中，WIFI技术和有线网络用于整合和联网以启用物联网（IOT），并显着降低了成本。

图3-14ATK-ESP8266模块外观图

特性参数

ATK-ESP8266模块采用串口（串口LVTTL）与MCU或其他串口设备通信，内置TCP/IP协议栈，能够实现串口与WIFI之间的转换。通过ATK-ESP8266模块，传统的串口设备只是需要简单的配置即可使用模块，通过网络（WIFI）传输自己的数据。

ATK-ESP8266模块支持LVTTL串口，兼容3.3V和5V单片机系统，可以很方便地进行产品连接。

ATK-ESP8266模块基本特性如表3-5，电气特性如表3-6所示：

表3-5ATK-ESP8266模块基本特性

表3-6ATK-ESP8266电气特性

ATK-ESP8266模块非常小巧（19mm*29mm），模块通过6个2.54mm间距的排针与外部连接，方便安装。

图3-15引脚连接

表3-7ATK-ESP8266引脚功能描述

模块使用说明

ATK-ESP8266模块支持STA/AP/STA+AP三种工作模式：

STA模式：ESP8266模块通过路由器连接互联网，手机或电脑通过互联网实现对设备的远程控制；

AP模式：默认模式ATK_ESP8266模块作为热点，实现手机或电脑直接与模块通信，实现局域网无线控制；

STA+AP模式：两种模式的共存模式，（STA模式）即可以通过路由器连接到互联网，并通过互联网控制设备；（AP模式）也可作为wifi热点，其他wifi设备连接到模块。这样实现局域网和广域网的无缝切换，方便操作。

模块IP地址配置

可在工程文件的有关ESP8266模块的文件夹中查看连接参数，如图3-16。

第一行是WIFI名称，第三行是WIFI密码，WiFi的名称和密码实验室已经为大家烧录好。

第二行为路由器加密方式，不用理它。

图3-16

然后，继续在esp_conf.c中修改IP地址和端口号。IP地址为PC连接同一个WiFi后所得IP，端口号只需找一个未被占用的端口即可，图3-17中端口号为2.

图3-17

接着，将修改好的程序捎入传感器板子中的STM32F103C8T6处理器。这样，传感器端就配置好了。

最后，PC在连接同一个WIFI的前提下，配置好网络调试助手。环境采集终端模块采用UDP协议，再接着输入电脑端IP和端口，与上面的IP和端口相同，如图3-18。再点击连接，即可等待数据。

图3-18

第四篇联调篇

产品功能描述

软件界面主要包括四个部分：

（1）开机页面，见图4-1。开机后由emWin绘制的两张图片重叠在一起，并设置了一个button，点击后进入下一个页面。

图41开机界面

（2）密码输入界面。点击上一个界面的“Enter”后，进入密码输入界面，如图4-2，上面是密码输入框，下面是一个4*3的按键组，可输入0~9，也可删除和确定，密码输入框内可输入一个6位的数字，若输入错误的密码后点击“OK”，密码输入框右侧会显示红色的“Error”。输入程序预设的密码后，点击“OK”可进到主界面。

图42密码输入框

（3）主界面如图4-3。主界面上有3*3，共九个环境参数，每个环境参数有两个框，上边框里的数字是实时显示的环境参数，下边的框上写的是环境数据名称。

图43主界面

（4）预警值设置。上边讲过，主界面每个环境参数有两个框，上边的框是实时显示环境参数的，但它还有第二个功能，点击框后进入预警值设置界面，例如图4-4是温度的预警值设置界面，例如图4-4是co2的预警值设置界面。进入后，中间框里的数字就是我们提前设置好的预警值，如图温度我们设置的是40℃，二氧化碳是1000ppm，设置成这些值的原因是，环境温度高于40℃，我们就会觉得闷热。环境二氧化碳浓度超过1000ppm，人就会开始觉得头晕，氧气不足，工作效率下降。该预警值可以进行更改，点击左边的加号预警值加一，点击减号，预警值减一，该加减号有长按功能，长按可实现快速持续加减。设置完成后我们点击下方的“OK”键回到主界面。

图44预警值设置界面

（5）曲线显示界面。在（4）里讲过，主界面每个环境参数有两个框，下边的框上写的是环境数据名称，它也有第二个功能，点击后进入曲线显示界面，例如图4-5是二氧化碳的曲线显示界面，曲线显示界面会将监测到的环境参数描点绘图，最后组成一条曲线，曲线的纵坐标和描点的刷新频率都可以在代码里通过参数进行修改。如图4-6，我们对监测二氧化碳的SGP-30传感器间断呼气后呈现出这样的效果。

图45曲线显示界面

图46CO2监测效果图

模式切换。上边演示的都是主板与传感器板直接通过串口线相连来工作的。本课题还开发了第二种模式，使主板与传感器板通过wifi相连来传输数据，并可实现主板与多块相同的传感器板相连，远程监测房间的各个角落。

要使主板与传感器板相连，实现的方式是发送板采用“255.255.255.255”进行UDP广播，主板作接收方，本地监听端口为8086，它们都连接在相同的热点，实现连接。主板Wi-Fi部分我们使用的是串口三，Wi-Fi模块与串口三相连，接收打包好的数据。如图4-7、4-8，主板和传感器板都去连接名称是“ATK”，密码是“ALIENTEK”的热点。此WiFi名称和密码都可以自己设置，如实验室设置为“DPJ_SHIYAN1”,密码“12345678”。

图47主板代码

图48传感器板代码

如图4-9所示，我们把wake-up按键设置成切换按键，点击按键会在两种模式之间进行切换，为了方便，我们称用串口线直接相连的模式为采集模式，称用Wi-Fi连接的模式为Wi-Fi模式。我们在采集模式下按切换按键，切换到了Wi-Fi模式，主板显示屏如图4-10，与采集模式相比，Wi-Fi模式主界面上方有一行小字，用来显示你接受到的数据内容。

图49模式切换按键

图410切换后效果

此外，我们在Wi-Fi模式下，还实现了主板与多块相同的传感器板相连，并进行区分，在主版与多块相同传感器板子相连后，我们点击图4-11所示的按键，可以切换主板与哪块传感器板相连，如图我们按压一次，这行字最开始的0变成了1，表示它现在与一号板相连，如图4-12。

图411Wi-Fi模式下的采集板选择按键

图412选择一号板效果

我们再按压一次，1变成了2，表示现在主板与二号传感器板相连，如图4-13。

图413选择二号板效果

说明一下，如图4-10，当最前面的数字是0时，以轮循的方式查找，接收所有与主板连接在同一热点的传感器板的数据。如图4-14，按键每按一下，前面的数字加一，最高到9，也就是说满负荷时可与九块传感器板相连。现在我们只连接了两块传感器板子，所以这行字没有显示实时的数据。

图414最高到九号板

解释一下这串字，我们已经知道“WIFI”前边数字的含义，“WIFI”后边数字的含义是一共向主板发送了几次数据，IPD后的数字代表当前发送过来的数据一共是多少个字节，data后面就是我们的数据协议了。

在烧录传感器板子前，如图4-15，我们把data后面的数字改为几，它就是几号板子，现在是二号板子。

图415二号板传输协议

系统联调

做好硬件设计和软件开发后，我们要联调整个系统。联调时务必要保证正确的接线和操作步骤。监测完硬件再检测软件，在这个过程中我们需要用到热点。

本次测试的预期实现功能为两部分：

系统启动工作后，接收传感器板不停发送过来的数据，并能在显示屏上实时显示数据，超过环境阈值时蜂鸣器发出警报，并可手动设置阈值。保证按键灵敏，能够通过按键切换采集模式与Wi-Fi模式，采集模式下通过串口线相连，Wi-Fi模式下通过ESP8266将传感板监测到的的数据发送给开发板。

（1）首先我们打开手机热点，名称设置为“ATK”，密码设置为“ALIENTEK”，如图4-16。实验室使用了小米路由器，效果一样。

图4-16打开热点

（2）我们打开传感器板，等传感器预热一段时间后，趋于稳定，此时就可以打开主板。等待一会，看到热点已经将三块板子都连接上。如图4-17。

图4-17主板

图4-18一号板

图4-19二号板

图4-20已连接在同一热点

（3）环境模拟开始后，等待传感器板预热一段时间，等显示的数据趋于稳定后，我们再通过间断的改变各种环境参量，开始观察数据。间隔向监测湿度的DHT11哈气，曲线如图4-21。

图4-21湿度实时监测

如图4-22，通过臭氧发生器制造臭氧，间隔靠近MQ-131。

图4-22O3实时监测

如图4-23，用打火机释放甲烷，间隔靠近MQ-2。

图4-23CH4实时监测

如图4-24，PMS5003监测到的空气中PM2.5变化情况。

图4-24PM2.5实时监测

经测试，该系统可以实时监测各项环境指标，并且在显示屏上进行实时的曲线显示。系统运行良好，功能完备，效果十分理想。