背光灯代码(近光灯代码)

前沿拓展:


1)实验平台:ALIENTEK NANO STM32F411 V1开发板

2)摘自《正点原子STM32F4 开发指南(HAL 库版》关注官方微信号公众号,获取更多资料:正点原子

第六章 跑马灯实验

STM32 最简单的外设莫过于 IO 口的高低电平控制了,本章将通过一个经典的跑马灯程序,

带大家开启 STM32F4 之旅,通过本章的学习,你将了解到 STM32F4 的 IO 口作为输出使用的

方法。在本章中,我们将通过代码控制 ALIENTEK NANO STM32F411 开发板上的两个 LED:

DS0 和 DS1 交替闪烁,实现类似跑马灯的效果。 本章分为如下四个小节:

6.1,STM32F4 IO 口简介

6.2,硬件设计

6.3,软件设计

6.4,仿真与下载

6.5 STM32CubeMX 配置 IO 口

6.1 STM32F4 IO 简介

本章将要实现的是控制 ALIENTEK NANO STM32F4 V1 开发板上的两个 LED 实现一个类

似跑马灯的效果,该实验的关键在于如何控制 STM32 的 IO 口输出。了解了 STM32F4 的 IO 口

如何输出的,就可以实现跑马灯了。通过这一章的学习,你将初步掌握 STM32F4 基本 IO 口的

使用,而这是迈向 STM32F4 的第一步。

这一章节因为是第一个实验章节,所以我们在这一章将讲解一些知识为后面的实验做铺垫。

为了小节标号与后面实验章节一样,这里我们不另起一节来讲。

在讲解 STM32F4 的 GPIO 之前,首先打开我们光盘的第一个 HAL 库版本实验工程跑马灯

实 验 工 程 ( 光 盘 目 录 为 : “ 4 , 程 序 源 码 \ 标 准 例 程 -HAL 库 函 数 版 本 \ 实 验 1 跑 马 灯

/USER/LED.uvprojx”),可以看到我们的实验工程目录:

图 6.1.1 跑马灯实验目录结构

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

1

组HALLIB下面存放的是ST官方提供的HAL库文件,每一个源文件stm32f4xx_hal_ppp.c

都对应一个头文件 stm32f4xx_hal_ppp.h。分组内的源文件我们可以根据工程需要添加和

删除。这里对于跑马灯实验,我们需要添加 9 个源文件。

2

组 CORE 下面存放的是固件库必须的核心头文件和启动文件。这里面的文件用户不需要

修改。大家可以根据自己的芯片型号选择对应的启动文件。

3

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

4

组 HARDWARE 下面存放的是每个实验的外设驱动代码,他的实现是通过调用 HALLIB

下面的 HAL 库文件函数实现的,比如 led.c 中函数调用 stm32f4xx_hal_gpio.c 内定义的函

数对 led 进行初始化,这里面的函数是讲解的重点。后面的实验中可以看到会引入多个

源文件。

5

组 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,下同)的工作模

式,该寄存器各位描述如表表 6.1.2 所示:

表 6.1.2 GPIOx MODER 寄存器各位描述

该寄存器各位在复位后,一般都是 0(个别不是 0,比如 JTAG 占用的几个 IO 口),也就

是默认条件下一般是输入状态的。每组 IO 下有 16 个 IO 口,该寄存器共 32 位,每 2 个位控制

1 个 IO,不同设置所对应的模式见表 6.1.1 描述。

然后看 OTYPER 寄存器,该寄存器用于控制 GPIOx 的输出类型,该寄存器各位描述见表

表 6.1.3 所示:

表 6.1.3 GPIOx OTYPER 寄存器各位描述

该寄存器仅用于输出模式,在输入模式(MODER[1:0]=00/11 时)下不起作用。该寄存器

低 16 位有效,每一个位控制一个 IO 口。设置为 0 是推挽输出,设置为 1 是开漏输出。复位后,

该寄存器值均为 0,也就是在输出模式下 IO 口默认为推挽输出。

然后看 OSPEEDR 寄存器,该寄存器用于控制 GPIOx 的输出速度,该寄存器各位描述见表

表 6.1.4 所示:

该寄存器也仅用于输出模式,在输入模式(MODER[1:0]=00/11 时)下不起作用。该寄存

器每 2 个位控制一个 IO 口,复位后,该寄存器值一般为 0。

然后看 PUPDR 寄存器,该寄存器用于控制 GPIOx 的上拉/下拉,该寄存器各位描述见表表

6.1.5 所示:

表 6.1.5 GPIOx PUPDR 寄存器各位描述

该寄存器每 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 函数完成:

void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);

该函数有两个参数,第一个参数是用来指定需要初始化的 GPIO 对应的 GPIO 组,取值范

围为 GPIOA~GPIOK。第二个参数为初始化参数结构体指针,结构体类型为 GPIO_InitTypeDef。

下面我们看看这个结构体的定义。首先我们打开我们光盘的跑马灯实验,然后找到 HALLIB 组

下面的 stm32f4xx_hal_gpio.c 文件,定位到 HAL_GPIO_Init 函数体处,双击入口参数类型

GPIO_InitTypeDef 后右键选择“Go to definition of …”可以查看结构体的定义如下:

typedef struct

{

uint32_t Pin;

//指定 IO 口

uint32_t Mode;

//模式设置

uint32_t Pull;

//上下拉设置

uint32_t Speed;

//速度设置

uint32_t Alternate;//复用映射配置

}GPIO_InitTypeDef;

结构体有 5 个成员变量,关于怎么来确定这 5 个成员变量的取值范围,请参考 4.7 小节内

容。下面我们通过一个 GPIO 初始化实例来讲解这个结构体的成员变量的含义。

初始化 GPIO 的常用格式是:

GPIO_InitTypeDef GPIO_Initure;

GPIO_Initure.Pin=GPIO_PIN_0;

//PB0

GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出

GPIO_Initure.Pull=GPIO_PULLUP;

//上拉

GPIO_Initure.Speed=GPIO_SPEED_HIGH;

//高速

HAL_GPIO_Init(GPIOB,&GPIO_Initure);

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

从上面初始化代码可以看出,结构体 GPIO_Initure 的第一个成员变量 Pin 用来设置是要初

始化哪个或者哪些 IO 口。第二个成员变量 Mode 是用来设置对应 IO 端口的输出输入端口模式,

这个变量实际配置的是我们前面讲解的 GPIOx 的 MODER 寄存器。第三个成员变量 Pull 是用

来设置上拉还是下拉,配置的是 GPIOx PUPDR 寄存器。第四个成员变量 Speed 用来设置输出

速度,配置的是 GPIOx OSPEEDR 寄存器。第五个成员变量 Alternate,我们在 4.4 小节引脚复

用器和映射已经讲解,它是用来设置引脚的复用映射的。

这些入口参数的取值范围怎么定位,怎么快速定位到这些入口参数取值范围的枚举类型,

在我们上面章节 4.7 的“快速组织代码”章节有讲解,不明白的朋友可以翻回去看一下,这里

我们就不重复讲解,在后面的实验中,我们也不会再重复讲解定位每个参数取值范围的方法。

看完了 GPIO 的参数配置寄存器,接下来我们看看 GPIO 输入输出电平控制相关的寄存器。

首先我们看 ODR 寄存器,该寄存器用于控制 GPIOx 的输出电平,该寄存器各位描述见表

6.1.6 所示:

表 6.1.6 GPIOx ODR 寄存器各位描述

该寄存器用于设置某个 IO 输出低电平(ODRy=0)还是高电平(ODRy=1),该寄存器也仅在输

出模式下有效,在输入模式(MODER[1:0]=00/11 时)下不起作用。该寄存器在 HAL 库中使用

不多,操作这个寄存器的库函数主要是 HAL_GPIO_TogglePin 函数:

void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

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

接下来我们看看另一个非常重要的寄存器 BSRR,它叫置位/复位寄存器。该寄存器和 ODR

寄存器具有类似的作用,都可以用来设置 GPIO 端口的输出位是 1 还是 0。寄存器描述如下:

表 6.1.7 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 寄存器使用方法如下:

GPIOA->BSRR=1<<1;

//设置 GPIOA.1 为高电平

GPIOA->BSRR=1<<(16+1)//设置 GPIOA.1 为低电平;

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

void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin,

GPIO_PinState PinState);

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

高,方法为:

HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET); //GPIOB.5 输出高

设置 GPIOB.5 输出低电平,方法为:

HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5, GPIO_PIN_RESET); //GPIOB.5 输出低

接下来我们看看 IDR 寄存器,该寄存器用于读取 GPIOx 的输入数据,该寄存器各位描述

见表 6.1.8 所示:

表 6.1.8 GPIOx IDR 寄存器各位描述

该寄存器用于读取某个 IO 的电平,如果对应的位为 0(IDRy=0),则说明该 IO 输入的是低

电平,如果是 1(IDRy=1),则表示输入的是高电平。HAL 库操作该寄存器读取 IO 输入数据相

关函数:

GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

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

方法为:

HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_5);//读取 PF5 的输入电平

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

最后我们来看看 2 个 32 位复用功能选择寄存器(AFRH 和 AFRL),这两个寄存器是用

来设置 IO 口的复用功能的。实际上,在我们调用函数 HAL_GPIO_Init 的时候,如果我们设置

了初始化结构体成员变量 Mode 为复用模式,同时设置了 Alternate 的值,那么会在该函数内部

自动设置这两个寄存器的值,达到设置端口复用映射的目的。关于这两个寄存器的详细配置以

及相关库函数的使用,在我们前面 4.4 小节 IO 引脚复用和映射有详细讲解,这里我们只是简

要说明一下。

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

的总结,操作步骤为:

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

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

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

上面我们讲解了 STM32F4 IO 口的基本知识以及 HAL 库操作 GPIO 的一些函数方法,下面

我们来讲解我们的跑马灯实验的硬件和软件设计。

6.2 硬件设计

本章用到的硬件只有 LED(DS0 和 DS1)。其电路在 ALIENTEK NANO STM32F411 开发

板上默认是已经连接好了的。DS0 接 PC0,DS1 接 PC1。所以在硬件上不需要动任何东西。其

连接原理图如图 6.2.1 下:

图 6.2.1 LED 与 STM32F1 连接原理图

6.3 软件设计

这是我们学习的一个实验,所以我会手把手教大家怎么从我们前面讲解的 Template 工程模

板一步一步加入 HAL 库以及 led 相关的驱动函数到我们工程,使之跟我们光盘的跑马灯实验工

程一模一样。首先大家打开我们 3.3 小节新建的 HAL 库工程模板。如果你还没有新建,也可以

直接打开我们光盘已经新建好了的工程模板,路径为:“\4,程序源码\标准例程-HAL 库函数

版本\实验 0-1 Template 工程模板-新建工程章节使用”(注意:是直接点击工程下面的 USER

目录下面的 Template.uvprojx)。

大家可以看到,我们模板里面的 HALLIB 分组下面,我们引入了所有的 HAL 库源文件和

对应头文件,如下图 6.3.1:

图 6.3.1 Template 模板工程结构

实际上,这些大家可以根据工程需要添加,比如跑马灯实验并没有用到 ADC,我们可以在

工程中删除文件 stm32f4xx_hal_adc.c,这样可以大大减少工程编译时间。跑马灯实验我们一共

使用到 HAL 库 9 个源文件,具体那 9 个请直接参考我们跑马灯实验工程,其他不用的源文件

大家可以直接在工程中删除。在工程的 Manage Project Items 页面,选择要删除文件所在的分组,

然后选中文件点击删除按钮即可。具体操作方法如下图 6.3.2 所示:

图 6.3.2 删除工程分组中的文件

接下来我们进入我们工程的目录,在工程根目录文件夹下面新建一个 HARDWARE 的文

件夹,用来存储以后与硬件相关的代码。然后在 HARDWARE 文件夹新建一个 LED 文件夹,

用来存放与 LED 相关的代码。如图 6.3.3 所示:

图 6.3.3 新建 HARDWARE 文件夹

接下来,我们回到们的工程(如果是使用的上面新建的工程模板,那么就是

Template.uvproj,大家可以将其重命名为 LED.uvproj),按

按钮新建一个文件,然后按

保存在 HARDWARE->LED 文件夹下面,保存为 led.c,操作步骤如下图 6.3.4 和 6.3.5:

图 6.3.4 新建文件

图 6.3.5 保存 led.c

然后在 led.c 文件中输入如下代码(代码大家可以直接打开我们光盘的实验 1 跑马灯实验,

从 led.c 文件内复制过来),输入后保存即可:

//LED IO 初始化

void LED_Init(void)

{

GPIO_InitTypeDef GPIO_Initure;

__HAL_RCC_GPIOC_CLK_ENABLE();

//开启 GPIOC 时钟

GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|

GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;//PC0~7

GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;//推挽输出

GPIO_Initure.Pull=GPIO_PULLUP;

//上拉

GPIO_Initure.Speed=GPIO_SPEED_HIGH;

//高速

HAL_GPIO_WritePin(GPIOC,GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|

GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7,

GPIO_PIN_SET); //PC0~7 置 1,默认初始化后灯灭

HAL_GPIO_Init(GPIOC,&GPIO_Initure);

}

该代码里面就包含了一个函数 void LED_Init(void),该函数通过调用 HAL_GPIO_Init 实验

配置 PC0~PC7 为推挽输出。关于函数 HAL_GPIO_Init 的使用方法在 6.1 小节有详细讲解。这

里需要注意的是:在配置 STM32 外设的时候,任何时候都要先使能该外设的时钟。使能 GPIOC

时钟方法为:

__HAL_RCC_GPIOC_CLK_ENABLE(); //使能 GPIOC 时钟

在设置完时钟以后,先调用 HAL_GPIO_WritePin 控制 LED0~LED7 输出 1(LED 全灭),

然后调用 HAL_GPIO_Init 函数完成对 PC0~PC7 的初始化配置。至此,LED 的初始化完毕。这

样就完成了对这八个 IO 口的初始化。这段代码的具体含义,大家可以看前面 6.1 小节,我们有

详细的讲解。

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

在 led.h 中输入如下代码:

#ifndef _LED_H

#define _LED_H

#include "sys.h"

#define LED0 PCout(0)

//LED0

#define LED1 PCout(1)

//LED1

#define LED2 PCout(2)

//LED2

#define LED3 PCout(3)

//LED3

#define LED4 PCout(4)

//LED4

#define LED5 PCout(5)

//LED5

#define LED6 PCout(6)

//LED6

#define LED7 PCout(7)

//LED7

void LED_Init(void);

#endif

这段代码里面最关键就是 8 个宏定义:

#define LED0 PCout(0)

//LED0

#define LED1 PCout(1)

//LED1

#define LED2 PCout(2)

//LED2

#define LED3 PCout(3)

//LED3

#define LED4 PCout(4)

//LED4

#define LED5 PCout(5)

//LED5

#define LED6 PCout(6)

//LED6

#define LED7 PCout(7)

//LED7

这里使用的是位带操作来实现操作某个 IO 口,关于位带操作前面第五章 5.2.1 已经有详细

介绍,这里不再多说。需要说明的是,这里同样可以使用 HAL 库操作来实现 IO 口操作。如下:

HAL_GPIO_WritePin(GPIOC,GPIO_Pin_0,GPIO_PIN_SET);//PC0=1,等同 LED0=1;

HAL_GPIO_ReadPin(GPIOC,GPIO_Pin_0);

//读取 PC0 的输入电平

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

将 led.h 也保存一下。接着,我们在 Manage Project Itms 管理界面新建一个 HARDWARE

的组,并把 led.c 加入到这个组里面,如图 6.3.6 所示:

图 6.3.6 给工程新增 HARDWARE 组

单击 OK,回到工程,然后你会发现在 Project Workspace 里面多了一个 HARDWARE 组,

在该组下面有一个 led.c 文件。如图 6.3.7 所示:

图 6.3.7 工程主界面

然后用之前介绍的方法(在 3.3 节介绍的)将 led.h 头文件的路径加入到工程里面,然后点

击 OK 回到主界面,如下图 6.3.8 所示

图 6.3.8 添加 LED 目录到 PATH

回到主界面后,修改 main.c 文件内容如下(具体内容请参考跑马灯实验 main.c 文件):

#include "sys.h"

#include "delay.h"

#include "usart.h"

#include "led.h"

int main(void)

{

HAL_Init();

//初始化 HAL 库

Stm32_Clock_Init(96,4,2,4);

//设置时钟,96Mhz

delay_init(96);

//初始化延时函数

LED_Init();

//初始化 LED

while(1)

{

HAL_GPIO_WritePin(GPIOC,GPIO_PIN_0,GPIO_PIN_RESET);//LED0 亮

HAL_GPIO_WritePin(GPIOC,GPIO_PIN_1,GPIO_PIN_SET);//LED1 灭

delay_ms(500);

//延时 500ms

HAL_GPIO_WritePin(GPIOC,GPIO_PIN_0,GPIO_PIN_SET);//LED0 灭

HAL_GPIO_WritePin(GPIOC,GPIO_PIN_1,GPIO_PIN_RESET);//LED1 亮

delay_ms(500);

//延时 500ms

}

}

代码包含了#include“led.h”这句,使得 LED0、LED1、LED_Init 等能在 main()函数里被

调用。main()函数非常简单,先调用 HAL_Init 函数初始化 HAL 库,然后调用 Stm32_Clock_Init

进行时钟系统配置,然后调用 delay_init()函数进行延时初始化。接着就是调用 LED_Init()来初

始化 PC0~PC7 为推挽输出模式,最后在 while 死循环里面实现 LED0 和 LED1 交替闪烁,间隔

为 500ms。

上面是通过库函数来实现的 IO 操作,我们也可以修改 main()函数,直接通过位带操作达到

同样的效果,大家不妨试试,位带操作的代码如下:

int main(void)

{

HAL_Init();

//初始化 HAL 库

Stm32_Clock_Init(96,4,2,4);

//设置时钟,96Mhz

delay_init(96);

//初始化延时函数

LED_Init();

//初始化 LED

while(1)

{

LED0=0;

//LED0 亮

LED1=1;

//LED1 灭

delay_ms(500);

LED0=1;

//LED0 灭

LED1=0;

//LED1 亮

delay_ms(500);

}

}

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

内容:

int main(void)

{

HAL_Init();

//初始化 HAL 库

Stm32_Clock_Init(96,4,2,4);

//设置时钟,96Mhz

delay_init(96);

//初始化延时函数

LED_Init();

//初始化 LED

while(1)

{

GPIOC->BSRR=GPIO_PIN_0<<16; //LED0 亮

GPIOC->BSRR=GPIO_PIN_1;

//LED1 灭

delay_ms(500);

GPIOC->BSRR=GPIO_PIN_0;

//LED0 灭

GPIOC->BSRR=GPIO_PIN_1<<16; //LED1 亮

delay_ms(500);

}

}

将主函数替换为上面代码,然后重新执行,可以看到,结果跟库函数操作和位带操作一样

的效果。大家可以对比一下。这个代码在我们跑马灯实验的 main 文件有注释,大家可以替换试

试。然后编译工程,得到的结果如图 6.3.9 所示:

图 6.3.9 编译结果

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

5376 字节(4910+466),所用的 SRAM 大小为 1928(1900+28)。

这里我们解释一下,编译结果里面的几个数据的意义:

Code:表示程序所占用 FLASH 的大小(FLASH)。

RO-data:即 Read Only-data,表示程序定义的常量(FLASH)。

RW-data:即 Read Write-date,表示已被初始化的变量(SRAM)。

ZI-data:即 Zero Init-data,表示未被初始化的变量(SRAM)

有了这个就可知道你当前使用的 flash 和 sram 大小了,所以,一定要注意的是程序大小不

是.hex 文件的大小,而是编译后的 Code 和 RO-data 之和。

接下来,我们下载到 NANO STM32F4 开发板看看实际运行的结果。

6.4 下载验证

运行结果如图 6.4.1 所示:

图 6.4.4 执行结果

在工程中我们另外添加了实现流水灯代码,DS0~DS7 轮流点亮,只需将代码屏蔽去掉,重

新编译一下下载到开发板就可以看到实现效果了,这里我们就不作贴图了。

至此,我们的第一章的学习就结束了,本章作为 STM32F4 的入门第一个例子,详细介绍

了 STM32F4 的 IO 口的使用及注意事项。希望大家好好理解一下。

6.5 STM32CubuMX 配置 IO 口

在讲解完使用 HAL 库操作 GPIO 口之后,本小节我们教大家怎么使用 STM32CubeMX 图

形化配置工具配置 GPIO 初始化过程。关于 STM32CubeMX 工具的入门使用在前面 4.8 小节我

们有手把手教大家入门该工具,本小节我们就不重复讲解入门部分,我们直接讲解在

STM3232CubeMX 工具中怎么来配置 GPIO 口的相关参数。

首先大家打开 STM32CubeMX 工具,参考 4.8 小节内容进行 RCC 相关配置。这里大家也

可以直接打开 4.8 小节的 STM32CubeMX 工程直接在工程上面修改,该工程保存的光盘目录为:

“4,程序源码\标准例程-HAL 库函数版本\实验 0-3 Template 工程模板-使用 STM32CubeMX 配

置”。

这里大家会发现,我们在 4.8 小节实际上已经讲解了 GPIO 的配置,并且同样是以 PC0 和

PC1 为例。这里我们将详细解析在 STM32CubeMX 中配置 IO 口详细参数的过程。使用

STM32CubeMX 配置 GPIO 口的步骤如下:

第一步,打开 STM32CubeMX 工具,在引脚图中选择要配置的 IO 口。这里我们选择 PC0

为例,在弹出的下拉菜单中选择要配置的 IO 口模式,如下图 6.5.1 所示:

图 6.5.1 选择 IO 口模式

从上图可以看出,这里我们除了配置 IO 口为输入输出之外,还可以选择 IO 口的复用功能

或者作为外部中断引脚功能,比如我们要选择 IO 口复用为 ADC1 的通道 10 引脚,那么我们只

需要选选项 ADC1_IN10 即可。对于本章跑马灯实验,PC0 是作为输出,所以我们选 GPIO_Output

即可。

第二步,进入 Configuration->GPIO,在弹出的界面配置 IO 口的详细参数。如下图 6.5.2 所

示:

图 6.5.3 配置 IO 口详细参数

在 IO 口详细参数配置界面,点击我们要配置的 IO 口,会在窗口下方显示该 IO 口配置的

详细参数表,下面我们依次来解释这些配置项的含义:

1、选项 GPIO output level 用来设置 IO 口初始化电平状态为 High(高电平)还是 Low(低电

平)。本实验我们设置为默认输出高 High。

2、选项 GPIO mode 用来设置输出模式为 Output Push Pull(推挽)还是 Output Open Drain(开

漏)。本实验我么设置为推挽输出 Output Push Pull。

3、选项 GPIO Pull-up/Pull-down 用来设置 IO 口是上拉/下拉/没有上下拉。本实验我们设置

为上拉(Pull-up)。

4、选项 Mzximum ouput speed 用来设置输出速度为高速(Hign)/快速(Fast)/中速(Medium)/低

速(Low)。本实验我们设置为高速 High。

5、选项 User Label 是用来设置初始化的 IO 口 Pin 值为我们自定义的宏,一般情况我们可以

不用设置,有兴趣的同学可以自由设置后查看生成后的代码就很容易明白其含义。

配置完 PC0 后,PC1 配置方法和参数都一模一样,这样我们就不重复配置。

然后我们参考 4.8 小节方法,生成工程源码。接下来打开工程的 main.c 文件可以看到,该

文件内部有 STM32CubeMX 生成了函数 MX_GPIO_Init,内容如下:

static void MX_GPIO_Init(void)

{

GPIO_InitTypeDef GPIO_InitStruct;

/* GPIO Ports Clock Enable */

__HAL_RCC_GPIOD_CLK_ENABLE();

__HAL_RCC_GPIOC_CLK_ENABLE();

/*Configure GPIO pin Output Level */

HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0|GPIO_PIN_1, GPIO_PIN_SET);

/*Configure GPIO pins : PC0 PC1 */

GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;

GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;

GPIO_InitStruct.Pull = GPIO_PULLUP;

GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;

HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

}

该函数的作用跟前面讲解的跑马灯实验中 LED_Init 函数作用一模一样,有兴趣的同学可以

直接修改跑马灯实验工程源码,把 LED_Init 函数内容修改为 MX_GPIO_Init 内容,大家会发现

实验效果一模一样。

一般情况下,STM32CubeMX 的主要作用是配置时钟系统和外设初始化函数。所以我们对

外设进行配置之后,生成外设初始化代码,然后把该代码应用到我们工程即可。关于本章中使

用 STM32CubeMX 配置

拓展知识:

标题:背光灯代码(近光灯代码)

地址:http://www.cdaudi4s.com/kongdiao/weixiuzixun/103375.html