龙空技术网

探索者 STM32F407 开发板资料连载第六章 跑马灯实验

正点原子日常 75

前言:

现时咱们对“走马灯效果的代码怎么写”大约比较关怀,大家都需要分析一些“走马灯效果的代码怎么写”的相关内容。那么小编在网上网罗了一些有关“走马灯效果的代码怎么写””的相关内容,希望兄弟们能喜欢,大家一起来了解一下吧!

1)实验平台:探索者 STM32F407 开发板

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

第六章 跑马灯实验

任何一个单片机,最简单的外设莫过于 IO 口的高低电平控制了,本章将通过一个经典的

跑马灯程序,带大家开启 STM32F4 之旅,通过本章的学习,你将了解到 STM32F4 的 IO 口作

为输出使用的方法。在本章中,我们将通过代码控制 ALIENTEK 探索者 STM32F4 开发板上的

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

6.1, STM32F4 IO 口简介

6.2, 硬件设计

6.3, 软件设计

6.4, 下载验证

6.1 STM32F4 IO 简介

本章将要实现的是控制ALIENTEK探索者STM32F4开发板上的两个 LED实现一个类似跑

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

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

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

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

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

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

实验工程(光盘目录为“

: 4,程序源码\标准例程-HAL库版本\实验1跑马灯/USER/ LED.uvproj”)

,

可以看到我们的实验工程目录:

图 6.1.1 跑马灯实验目录结构

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

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

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

除,但是一定要注意如果你引入了某个源文件,一定要在头文件 stm32f4xx_hal_conf.h

文件中确保对应的头文件也已经添加。比如我们跑马灯实验,我们只添加了 5 个源文件,

那么对应的头文件我们必须确保在 stm32f4xx_hal_conf.h 内也包含进来,否则工程会报错。

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

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

③ 组 SYSTEM 是 ALIENTEK 提供的共用代码,这些代码的作用和讲解在第五章都有讲解,

大家可以翻过去看下。

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

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

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

⑤ 组 USER 下面存放的主要是用户代码。但是 system_stm32f4xx.c 文件用户不需要修改,

同时 stm32f4xx_it.c 里面存放的是中断服务函数,这两个文件的作用在 3.1 节有讲解,大家可以翻过去看看。main.c 函数主要存放的是主函数了,这个大家应该很清楚。

工程分组情况我们就讲解到这里,接下来我们就要进入我们跑马灯实验的讲解部分了。这

里需要说明一下,我们在讲解 HAL 库之前会首先对重要寄存器进行一个讲解,这样是为了大

家对寄存器有个初步的了解。大家学习 HAL 库,并不需要记住每个寄存器的作用,而只是通

过了解寄存器来对外设一些功能有个大致的了解,这样对以后的学习也很有帮助。

首先要提一下,在 HAL 库中,GPIO 端口操作对应的 HAL 库函数函数以及相关定义在文

件 stm32f4xx_hal_gpio.h 和 stm32f4xx_hal_gpio.c 中。

相对于 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,代码会比较多,也不容易记住,所以我们在讲解寄存器的同时会讲解使用 HAL 库函数配

置 IO 的方法。

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

1、输入浮空

2、输入上拉

3、输入下拉

4、模拟输入

5、开漏输出

6、推挽输出

7、推挽式复用功能

8、开漏式复用功能

关于这些模式的介绍及应用场景,我们这里就不详细介绍了,感兴趣的朋友,可以看看这

个帖子了解下: 。接下来我们详细介绍 IO 配置常

用的 8 个寄存器: MODER、OTYPER、OSPEEDR、PUPDR、ODR、IDR 、AFRH 和 AFRL。

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

首先看 MODER 寄存器,该寄存器是 GPIO 端口模式控制寄存器,用于控制 GPIOx

(STM32F4 最多有 9 组 IO,分别用大写字母表示,即 x=A/B/C/D/E/F/G/H/I,下同)的工作模

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

表 6.1.1 GPIOx MODER 寄存器各位描述

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

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

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

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

表 6.1.2 所示:

表 6.1.2 GPIOx OTYPER 寄存器各位描述

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

低 16 位有效,每一个位控制一个 IO 口,复位后,该寄存器值均为 0。

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

表 6.1.3 所示:

表 6.1.3 GPIOx OSPEEDR 寄存器各位描述

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

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

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

6.1.4 所示:

表 6.1.4 GPIOx PUPDR 寄存器各位描述

该寄存器每 2 个位控制一个 IO 口,用于设置上下拉,这里提醒大家,STM32F1 是通过 ODR

寄存器控制上下拉的,而 STM32F4则由单独的寄存器 PUPDR控制上下拉,使用起来更加灵活。

复位后,该寄存器值一般为 0。

前面,我们讲解了 4 个重要的配置寄存器。顾名思义,配置寄存器就是用来配置 GPIO 的

相关模式和状态,接下来我们讲解怎么在库函数初始化 GPIO 的配置。

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

stm32f4xx_hal_gpio.h 文件中。

在 HAL 库开发中,操作四个配置寄存器初始化 GPIO 是通过 GPIO 初始化函数完成:

void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)

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

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

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

面的 stm32f4xx_hal_gpio.c 文件,定位到 HAL_GPIO_Init 函数体处,选中结构体“Ctrl + F”全

局搜索,可以查看结构体的定义:

typedef struct

{

uint32_t Pin;

uint32_t Mode;

uint32_t Pull;

uint32_t Speed;

uint32_t Alternate;

}GPIO_InitTypeDef;

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

通过初始化结构体初始化 GPIO 的常用格式是:

GPIO_Initure.Pin=GPIO_PIN_9|GPIO_PIN_10;

//PF9,10

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

GPIO_Initure.Pull=GPIO_PULLUP;

//上拉

GPIO_Initure.Speed=GPIO_SPEED_HIGH;

//高速

HAL_GPIO_Init(GPIOF,&GPIO_Initure);

上面代码的意思是设置 GPIOF 的第 9 和 10 端口为推挽输出模式,同时速度为 100M,上拉。

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

要初始化哪个或者哪些 IO 口,这个很好理解;第二个成员变量 Mode 是用来设置对应 IO 端口

的输出输入端口模式,这个值实际就是配置我们前面讲解的 GPIOx 的 MODER 寄存器的值。在

MDK 中是通过宏定义来定义的,我们只需要选择对应的值即可:

#define GPIO_MODE_INPUT 0x00000000U

#define GPIO_MODE_OUTPUT_PP 0x00000001U

#define GPIO_MODE_OUTPUT_OD 0x00000011U

……省略部分宏定义

#define GPIO_MODE_EVT_RISING_FALLING 0x10320000U

例如GPIO_MODE_INPUT是输入模式,GPIO_MODE_OUTPUT_PP是推挽输出模式等等,

根据实际需求来选择。

第三个参数 Pull 用来设置 IO 口的上下拉,实际上就是设置 GPIO 的 PUPDR 寄存器的值。

同样通过宏定义来定义的:

#define GPIO_NOPULL

0x00000000U

#define GPIO_PULLUP

0x00000001U

#define GPIO_PULLDOWN

0x00000002U

这三个值的意思很好理解,GPIO_NOPULL 为不使用上下拉,GPIO_PULLUP 为上拉,

GPIO_PULLDOWN 为下拉。我们根据我们 需要设置相应的值即可。

第四个参数 GPIO_Speed 是 IO 口输出速度设置,有四个可选值。实际上这就是配置的 GPIO

对应的 OSPEEDR 寄存器的值。在 MDK 中同样是宏定义来定义的:

#define GPIO_SPEED_FREQ_LOW

0x00000000U

#define GPIO_SPEED_FREQ_MEDIUM

0x00000001U

#define GPIO_SPEED_FREQ_HIGH 0x00000002U

#define GPIO_SPEED_FREQ_VERY_HIGH 0x00000003U

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

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

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

方法。

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

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

所示:

表 6.1.5 GPIOx ODR 寄存器各位描述

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

出模式下有效,在输入模式(MODER[1:0]=00/11 时)下不起作用。

在 HAL 库 中 设 置 ODR 寄 存 器 的 值 来 控 制 IO 口 的 输 出 状 态 是 通 过 函 数

HAL_GPIO_WritePin 来实现的:

void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin,

GPIO_PinState PinState)

使用实例如下:

HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_SET);

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

6.1.6 所示:

表 6.1.6 GPIOx IDR 寄存器各位描述

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

电平,如果是 1(IDRy=1),则表示输入的是高电平。库函数相关函数为:

GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

函数是用来读取一组 IO 口的一个输入电平。比如我们要读取 GPIOF.5 的输入电平,方法

为:

HAL_GPIO_ReadPin (GPIOF, GPIO_Pin_5);

接下来我们看看 32 位置位/复位寄存器 (BSRR),顾名思义,这个寄存器是用来置位或者

复位 IO 口,该寄存器和 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 为低电平;

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

来设置 IO 口的复用功能的。关于这两个寄存器的配置以及相关库函数的使用,在我们前面章

节 4.4 IO 引脚复用和映射有详细讲解,这里我们就不做过多的说明。

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

的总结,操作步骤为:

1) 使能 IO 口时钟。调用函数为__HAL_RCC_GPIOF_CLK_ENABLE();

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

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

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

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

6.2 硬件设计

本章用到的硬件只有 LED(DS0 和 DS1)。其电路在 ALIENTEK 探索者 STM32F4 开发板

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

接原理图如图 6.2.1 下:

图 6.2.1 LED 与 STM32F4 连接原理图

6.3 软件设计

这是我们的第一个实验,所以我教大家怎么从我们前面讲解的 Template 工程一步一步加入

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

一模一样。首先大家打开我们 3.3.2 小节新建的库函数版本工程模板。如果您还没有新建,也可

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

\实验 0 Template工程模板”。注意,是直接点击工程下面的USER 目录下面的Template.uvproj。

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

应的头文件,如下图 6.3.1:

图 6.3.1 Template 模板工程结构

实际上,这些大家可以根据工程需要添加,这样可以减少工程编译时间。

跑马灯实验我们主要用到的 HAL 库文件是:

其中 stm32f4xx_hal_rcc.c 库文件在每个实验中都要引入,因为系统时钟配置函数以及相关

的外设时钟使能函数都在这个源文件中。stm32f4xx_hal_usart.c 的源文件在我们 SYSTEM 文件

夹中都需要使用到,所以每个实验都会引用。其余是负责内核、电源、DMA 相关的库文件也

都需要引入。

在 stm32f4xx_hal_conf.h 文件里面,这些头文件默认都是打开的(通过各自的启用模块的宏

来定义),实际我们可以不用理。当然我们也可以注释掉其他不用的头文件。引用新的模块时需

要自己打开该模块,删掉注释符号即可,因为这些模块是在使用 Cube MX 工程生成的时候配置

的。

#define HAL_MODULE_ENABLED

#define HAL_ADC_MODULE_ENABLED

#define HAL_CAN_MODULE_ENABLED

/* #define HAL_CAN_LEGACY_MODULE_ENABLED */

#define HAL_CRC_MODULE_ENABLED

#define HAL_CRYP_MODULE_ENABLED

#define HAL_DAC_MODULE_ENABLED

#define HAL_DCMI_MODULE_ENABLED

#define HAL_DMA_MODULE_ENABLED

……省略部分内容

#ifdef HAL_RCC_MODULE_ENABLED

#include "stm32f4xx_hal_rcc.h"

#endif /* HAL_RCC_MODULE_ENABLED */

#ifdef HAL_EXTI_MODULE_ENABLED

#include "stm32f4xx_hal_exti.h"

#endif /* HAL_EXTI_MODULE_ENABLED */

#ifdef HAL_GPIO_MODULE_ENABLED

#include "stm32f4xx_hal_gpio.h"

#endif /* HAL_GPIO_MODULE_ENABLED */

#ifdef HAL_DMA_MODULE_ENABLED

#include "stm32f4xx_hal_dma.h"

#endif /* HAL_DMA_MODULE_ENABLED */

#ifdef HAL_CORTEX_MODULE_ENABLED

#include "stm32f4xx_hal_cortex.h"

#endif /* HAL_CORTEX_MODULE_ENABLED */

……省略部分内容

接下来,我们讲解怎样去掉多余的其他的源文件,方法如下图,右击 Template,选择“Manage

project Items”,进入这个选项卡:

图 6.3.2 点击 Manage project Items

我们选中“HALLIB”分组,然后选中不需要的源文件点击删除按钮删掉,留下下图中我

们使用到的源文件,然后点击 OK:

图 6.3.3 删除不需要的源文件

这样我们的工程 HALLIB 下面源文件:

图 6.3.4 工程界面

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

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

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

图 6.3.5 新建 HARDWARE 文件夹

接 下 来, 我们 回到 我们 的 工程 ( 如果 是 使用的 上 面新 建的 工程 模板 , 那么 就是Template.uvproj,大家可以将其重命名为 LED.uvproj),按

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

保存

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

图 6.3.6 新建文件

图 6.3.7 保存 led.c

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

的文件中间复制过来),输入后保存即可:

#include "led.h"

//初始化 F9、10 为输出.并使能时钟

//LED IO 初始化

void LED_Init(void)

{

GPIO_InitTypeDef GPIO_Initure;

__HAL_RCC_GPIOF_CLK_ENABLE();

//开启 GPIOF 时钟

GPIO_Initure.Pin=GPIO_PIN_9|GPIO_PIN_10; //PF9,10

GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;

//推挽输出

GPIO_Initure.Pull=GPIO_PULLUP;

//上拉

GPIO_Initure.Speed=GPIO_SPEED_HIGH;

//高速

HAL_GPIO_Init(GPIOF,&GPIO_Initure);

HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_SET);

//PF9 置 1,默认初始化后灯灭

HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_SET);

//PF10 置 1,默认初始化后灯灭

}

该代码里面就包含了一个函数 void LED_Init(void),该函数的功能就是用来实现配置 PF9

和 PF10 为推挽输出。这里需要注意的是:在配置 STM32 外设的时候,任何时候都要先使能该

外设的时钟!在 HAL 库中对打开时钟使能是通过函数__HAL_RCC_GPIOF_CLK_ENABLE()来

实现的。

__HAL_RCC_GPIOF_CLK_ENABLE();//开启 GPIOF 时钟

这行代码的作用是使能 GPIOF 时钟。

在设置完时钟之后,LED_Init调用HAL_GPIO_Init 函数完成对PF9 和PF10的初始化配置,

然后调用函数 HAL_GPIO_WritePin 控制 LED0 和 LED1 输出 1(LED 灭)。至此,两个 LED 的

初始化完毕。这样就完成了对这两个 IO 口的初始化。

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

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

#ifndef __LED_H

#define __LED_H

#include "sys.h"

//LED 端口定义

#define LED0 PFout(9) // DS0

#define LED1 PFout(10)// DS1

void LED_Init(void);//初始化

#endif

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

#define LED0 PFout(9) // DS0 PF9

#define LED1 PFout(10)// DS1 PF10

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

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

如下:

GPIO_SetBits(GPIOF, GPIO_Pin_9); //设置 GPIOF.9 输出 1,等同 LED0=1;

GPIO_ResetBits (GPIOF, GPIO_Pin_9); //设置 GPIOF.9 输出 0,等同 LED0=0;

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

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

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

图 6.3.8 给工程新增 HARDWARE 组

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

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

图 6.3.9 新增 HARDWARE 组

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

点击 OK 回到主界面。

图 6.3.10 添加 LED 目录到 PATH

回到主界面后,在 main 函数里面编写如下代码:

#include "sys.h"#include "delay.h"#include "usart.h"#include "led.h"/*下面主函数是使用 HAL 库函数实现控制 IO 口输出*/int main(void){  HAL_Init(); //初始化 HAL 库  Stm32_Clock_Init(336,8,2,7); //设置时钟,168Mhzdelay_init(168); //初始化延时函数LED_Init(); //初始化 LEDwhile(1){ HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_RESET); //LED0 对应引脚 PF9 拉低,亮,等同于 LED0(0) HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_SET); //LED1 对应引脚 PF10 拉高,灭,等同于 LED1(1) delay_ms(500); //延时 500ms HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_SET); //LED0 对应引脚 PF9 拉高,灭,等同于 LED0(1)HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_RESET); //LED1 对应引脚 PF10 拉低,亮,等同于 LED1(0) delay_ms(500); //延时 500ms } }

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

这里我们需要重申的是,在 HAL 库中,系统在启动的时候会调用 system_stm32f4xx.c 中的函数

SystemInit()对系统时钟进行初始化,在时钟初始化完毕之后会调用 main()函数。 所以我们不需

要再在 main()函数中调用 SystemInit()函数。当然如果有需要重新设置时钟系统,可以写自己的

时钟设置代码,SystemInit()只是将时钟系统初始化为默认状态。

main()函数非常简单,先调用 delay_init()初始化延时,接着就是调用 LED_Init()来初始化

GPIOF.9 和 GPIOF.10 为输出。最后在死循环里面实现 LED0 和 LED1 交替闪烁,间隔为 500ms。

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

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

int main(void){  HAL_Init(); //初始化 HAL 库  Stm32_Clock_Init(336,8,2,7); //设置时钟,168Mhzdelay_init(168); //初始化延时函数LED_Init(); //初始化 LEDwhile(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(336,8,2,7); //设置时钟,168Mhzdelay_init(168); //初始化延时函数LED_Init(); //初始化 LEDwhile(1){ GPIOF->BSRR=GPIO_PIN_9; //LED0 亮 GPIOF->BSRR=GPIO_PIN_10<<16; //LED1 灭 delay_ms(500);  GPIOF->BSRR=GPIO_PIN_9<<16; //LED0 灭 GPIOF->BSRR=GPIO_PIN_10; //LED1 亮 delay_ms(500);}}

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

作一样的效果。大家可以对比一下。这个代码在我们跑马灯实验的 main.c 文件中有注释掉,大

家可以替换试试。

然后按

,编译工程,得到结果如图 6.3.11 所示:

图 6.3.11 编译结果

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

4384 字节(3942+442),所用的 SRAM 大小为:1920 个字节(1896+24)。

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

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

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

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

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

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

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

接下来,大家就可以下载验证了。如果有 STLINK,则可以用 STLINK 进行在线调试(需

要先下载代码),单步查看代码的运行,STM32F4 的在线调试方法介绍,参见:3.4.2 节。

6.4 下载验证

这里我们使用 flymcu 下载(也可以通过 STLINK 等仿真器下载,具体方法请参考 3.4.2 小

节),如图 6.4.1 所示:

6.4.1 利用 flymcu 下载代码

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

图 6.4.2 程序运行结果

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

STM32F4 的 IO 口的使用及注意事项,同时巩固了前面的学习,希望大家好好理解一下。

标签: #走马灯效果的代码怎么写 #b3942电路图 #stm32f407按键控制led灯