STM32/STM8技术william hill官网
直播中

正点原子运营官

5年用户 1793经验值
擅长:模拟技术 嵌入式技术 控制/MCU
私信 关注
[资料]

「正点原子STM32Mini板资料连载」第六章 跑马灯实验

1)实验平台:正点原子STM32mini开发板
2)摘自《正点原子STM32 不完全手册(HAL 库版)关注官方微信号公众号,获取更多资料:正点原子


第六章 跑马灯实验
STM32最简单的外设莫过于IO口的高低电平控制了,本章将通过一个经典的跑马灯程序,
带大家开启 STM32 之旅,通过本章的学习,你将了解到 STM32 的 IO 口作为输出使用的方法。
在本章中,我们将通过代码控制 ALIENTEK MiniSTM32 开发板上的两个 LED:DS0 和 DS1 交
替闪烁,实现类似跑马灯的效果。 本章分为如下四个小节:
6.1, STM32 IO 口简介
6.2, 硬件设计
6.3, 软件设计
6.4, 下载验证
6.1 STM32 IO 简介
本章将要实现的是控制 ALIENTEK Mini STM32 V3 开发板上的两个 LED 实现一个类似跑
马灯的效果,该实验的关键在于如何控制 STM32 的 IO 口输出。了解了 STM32 的 IO 口如何输
出的,就可以实现跑马灯了。通过这一章的学习,你将初步掌握 STM32 基本 IO 口的使用,而
这是迈向 STM32 的第一步。
这一章节因为是第一个实验章节,所以我们在这一章将讲解一些知识为后面的实验做铺垫。
为了小节标号与后面实验章节一样,这里我们不另起一节来讲。
在讲解 STM32F103 的 GPIO 之前,首先打开我们光盘的第一个 HAL 库版本实验工程跑马
灯实验工程(光盘目录为:“4,程序源码标准例程-HAL 库函数版本实验 1 跑马灯/USER/
LED.uvproj”),可以看到我们的实验工程目录如下图 6.1.1 所示:
图 6.1.1 跑马灯实验目录结构


接下来我们逐一讲解一下我们的工程目录下面的组以及重要文件。
① 组HALLIB下面存放的是ST官方提供的HAL库文件,每一个源文件stm32f1xx_hal_ppp.c
都对应一个头文件 stm32f1xx_hal_ppp.h。分组内的源文件我们可以根据工程需要添加和删除。这里对于跑马灯实验,我们需要添加 10 个源文件。
② 组 CORE 下面存放的是固件库必须的核心头文件和启动文件。这里面的文件用户不需要
修改。大家可以根据自己的芯片型号选择对应的启动文件。
③ 组 SYSTEM 是 ALIENTEK 提供的共用代码,这些代码在第五章都有详细讲解。
④ 组 HARDWARE 下面存放的是每个实验的外设驱动代码,他的实现是通过调用 HALLIB
下面的 HAL 库文件函数实现的,比如 led.c 中函数调用 stm32f1xx_hal_gpio.c 内定义的函数对 led
进行初始化,这里面的函数是讲解的重点。后面的实验中可以看到会引入多个源文件。
⑤ 组 USER 下面存放的主要是用户代码。但是 system_stm32f1xx.c 文件用户不需要修改,
同时 stm32f1xx_it.c 里面存放的是中断服务函数,这两个文件的作用在 3.3 节有讲解。main.c 函
数主要存放的是主函数了。
工程分组情况我们就讲解到这里,接下来我们就要进入我们跑马灯实验的讲解部分了。这
里需要说明一下,我们在讲解 HAL 库之前会首先对重要寄存器进行一个讲解,这样是为了大
家对寄存器有个初步的了解。大家学习 HAL 库,并不需要记住每个寄存器的作用,而只是通
过了解寄存器来对外设一些功能有基本的了解,这样对以后的学习也很有帮助。
STM32 的 IO 口可以由软件配置成如下 8 种模式:
1、输入浮空
2、输入上拉
3、输入下拉
4、模拟输入
5、开漏输出
6、推挽输出
7、推挽式复用功能
8、开漏复用功能
每个 IO 口可以自由编程,但 IO 口寄存器必须要按 32 位字被访问。STM32 的很多 IO 口都
是 5V 兼容的,这些 IO 口在与 5V 电平的外设连接的时候很有优势,具体哪些 IO 口是 5V 兼容
的,可以从该芯片的数据手册管脚描述章节查到(I/O Level 标 FT 的就是 5V 电平兼容的)。
STM32 的每个 IO 端口都有 7 个寄存器来控制。他们分别是:配置模式的 2 个 32 位的端口
配置寄存器 CRL 和 CRH;2 个 32 位的数据寄存器 IDR 和 ODR;1 个 32 位的置位/复位寄存器
BSRR;一个 16 位的复位寄存器 BRR;1 个 32 位的锁存寄存器 LCKR;这里我们仅介绍常用 的
几个寄存器,我们常用的 IO 端口寄存器只有 4 个:CRL、CRH、IDR、ODR。
CRL 和 CRH 控制着每个 IO 口的模式及输出速率。
STM32 的 IO 口位配置表如表 6.1.1 所示:
表 6.1.1 STM32 的 IO 口位配置表

STM32 输出模式配置如表 6.1.2 所示:
表 6.1.2 STM32 输出模式配置表

接下来我们看看端口低配置寄存器 CRL 的描述,如图 6.1.1 所示:
图 6.1.1 端口低配置寄存器 CRL 各位描述

该寄存器的复位值为 0X4444 4444,从图 6.1.1 可以看到,复位值其实就是配置端口为浮空
输入模式。从上图还可以得出:STM32 的 CRL 控制着每组 IO 端口(A~G)的低 8 位的模式。
每个 IO 端口的位占用 CRL 的 4 个位,高两位为 CNF,低两位为 MODE。这里我们可以记住几
个常用的配置,比如 0X0 表示模拟输入模式(ADC 用)、0X3 表示推挽输出模式(做输出口用,
50M 速率)、0X8 表示上/下拉输入模式(做输入口用)、0XB 表示复用输出(使用 IO 口的第二
功能,50M 速率)。
CRH 的作用和 CRL 完全一样,只是 CRL 控制的是低 8 位输出口,而 CRH 控制的是高 8
位输出口。这里我们对 CRH 就不做详细介绍了。
接下来我们讲解怎么在库函数初始化 GPIO 的配置。GPIO 相关的函数和定义分布在 HAL
库文件 stm32f1xx_hal_gpio.c 和头文件 stm32f1xx_hal_gpio.h 文件中。
在 HAL 库开发中,初始化 GPIO 是通过 GPIO 初始化函数完成:
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)
这个函数有两个参数,第一个参数是用来指定需要初始化的 GPIO 对应的 GPIO 组,取值范围
为 GPIOA~GPIOE。第二个参数为初始化参数结构体指针,结构体类型为 GPIO_InitTypeDef。
下面我们看看这个结构体的定义。首先我们打开我们光盘的跑马灯实验,然后找到 HALLIB 组
下面的 stm32f1xx_hal_gpio.c 文件,定位到 HAL_GPIO_Init 函数体处,选中结构体“Ctrl + F”
全局搜索,可以查看结构体的定义:
typedef struct
{
uint32_t Pin;
uint32_t Mode;
uint32_t Pull;
uint32_t Speed;
}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_FREQ_HIGH; //高速
HAL_GPIO_Init(GPIOF,&GPIO_Initure);
上面代码的意思是设置 GPIOF 的第 9 和 10 端口为推挽输出模式,同时速度为 50M,上拉。
从上面初始化代码可以看出,结构体 GPIO_InitStructure 的第一个成员变量 Pin 用来设置是
要初始化哪个或者哪些 IO 口,这个很好理解;第二个成员变量 Mode 是用来设置对应 IO 端口
的输出输入端口模式。在 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 (GPIO_CRL_MODE0_1)
#define GPIO_SPEED_FREQ_MEDIUM (GPIO_CRL_MODE0_0)
#define GPIO_SPEED_FREQ_HIGH (GPIO_CRL_MODE0)
这些入口参数的取值范围怎么定位,怎么快速定位到这些入口参数取值范围的枚举类型,
在我们上面章节 4.7 的“快速组织代码”章节有讲解,不明白的朋友可以翻回去看一下,这里
我们就不重复讲解,在后面的实验中,我们也不再去重复讲解怎么定位每个参数的取值范围的
方法。
IDR 是一个端口输入数据寄存器,只用了低 16 位。该寄存器为只读寄存器,并且只能以
16 位的形式读出。该寄存器各位的描述如图 6.1.2 所示:
图 6.1.2 端口输入数据寄存器 IDR 各位描述

要想知道某个 IO 口的状态,你只要读这个寄存器,再看某个位的状态就可以了。使用起
来是比较简单的。库函数相关函数为:
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
函数是用来读取一组 IO 口的一个输入电平。比如我们要读取 GPIOF.5 的输入电平,方法
为:
HAL_GPIO_ReadPin (GPIOF, GPIO_Pin_5);
ODR 是一个端口输出数据寄存器,也只用了低 16 位。该寄存器为可读写,从该寄存器读
出来的数据可以用于判断当前 IO 口的输出状态。而向该寄存器写数据,则可以控制某个 IO 口
的输出电平。该寄存器的各位描述如图 6.1.3 所示:
图 6.1.3 端口输出数据寄存器 ODR 各位描述


在 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);
了解了这几个寄存器,我们就可以开始跑马灯实验的真正设计了。关于 IO 口更详细的介绍,请参考《STM32 参考手册》第 105 页 8.1 节。
6.2 硬件设计
本章用到的硬件只有 LED(DS0 和 DS1)。其威廉希尔官方网站 在 ALIENTEK MiniSTM32 开发板上默认
是已经连接好了的。DS0 接 PA8,DS1 接 PD2。所以在硬件上不需要动任何东西。其连接原理
图如图 6.2.1 下:

图 6.2.1 LED 与 STM32 连接原理图

6.3 软件设计
这是我们学习的第一个实验,所以我会手把手教大家怎么从我们前面讲解的 Template 工程
模板一步一步加入 HAL 库以及 led 相关的驱动函数到我们工程,使之跟我们光盘的跑马灯实验
工程一模一样。首先大家打开我们 3.3 小节新建的 HAL 库工程模板。如果您还没有新建,也可
以直接打开我们光盘已经新建好了的工程模板,路径为:“4,程序源码标准例程-HAL 库函数
版本实验 0-1 Template 工程模板-新建工程章节使用”(注意,是直接点击工程下面的 USER 目
录下面的 Tempate.uvprojx。)。
大家可以看到,我们模板里面的 HALLIB 分组下面,我们引入了所有的 HAL 库源文件和
对应的头文件,如下图 6.3.1:
图 6.3.1 Template 模板工程结构


实际上,这些大家可以根据工程需要添加,比如跑马灯实验并没有用到 ADC,我们可以在
工程中删掉文件 stm32f1xx_hal_adc.c,这样可以大大减少工程编译时间。跑马灯实验我们一共
使用到 HAL 库中 10 个源文件,具体哪 10 个请直接参考我们跑马灯实验工程,其他不用的源
文件大家可以直接在工程中删除。在工程的 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 文件内复制过来),输入后保存即可:
#include "led.h"
//初始化 PA8 和 PD2 为输出口.并使能这两个口的时钟
//LED IO 初始化
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOA_CLK_ENABLE();
//开启 GPIOA 时钟
__HAL_RCC_GPIOD_CLK_ENABLE();
//开启 GPIOD 时钟
GPIO_Initure.Pin=GPIO_PIN_8;
//PB5
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;
//推挽输出
GPIO_Initure.Pull=GPIO_PULLUP;
//上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;
//高速
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
GPIO_Initure.Pin=GPIO_PIN_2;
//PE5
HAL_GPIO_Init(GPIOD,&GPIO_Initure);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,GPIO_PIN_SET);
//PA8 置 1,默认初始化后灯灭
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
//PD2 置 1,默认初始化后灯灭
}
该代码里面就包含了一个函数 void LED_Init(void),该函数通过调用函数 HAL_GPIO_Init
实现配置PA8和PD2为推挽输出。关于函数HAL_GPIO_Init的使用方法在6.1小节有详细讲解。
这里需要注意的是:在配置STM32外设的时候,任何时候都要先使能该外设的时钟。使能GPIOA
和 GPIOD 时钟方法为:
__HAL_RCC_GPIOA_CLK_ENABLE(); //开启 GPIOA 时钟
__HAL_RCC_GPIOD_CLK_ENABLE(); //开启 GPIOD 时钟
在设置完时钟之后,LED_Init 调用 HAL_GPIO_Init 函数完成对 PB0 和 PB1 的初始化配置,
然后调用函数 HAL_GPIO_WritePin 控制 LED0 和 LED1 输出 1(LED 灭)。至此,两个 LED 的
初始化完毕。这样就完成了对这两个 IO 口的初始化。这段代码的具体含义,大家可以看前面
6.1 小节,我们有详细的讲解。
保存 led.c 代码,然后我们按同样的方法,新建一个 led.h 文件,也保存在 LED 文件夹下面。
在 led.h 中输入如下代码:
#ifndef __LED_H
#define __LED_H
#include "sys.h"
//LED 端口定义
#define LED0 PAout(8) //LED0
#define LED1 PDout(2) //LED1
void LED_Init(void);
#endif
这段代码里面最关键就是 2 个宏定义:
#define LED0 PAout(8) //LED0
#define LED1 PDout(2) //LED1
这里使用的是位带操作来实现操作某个 IO 口,关于位带操作前面第五章 5.2.1 已经有详细
介绍,这里不再多说。需要说明的是,这里同样可以使用 HAL 库操作来实现 IO 口操作。如下:
HAL_GPIO_WritePin (GPIOA, GPIO_Pin_8, GPIO_PIN_SET);//PA8=1,等同 LED0=0;
HAL_GPIO_ReadPin(GPIOA, GPIO_Pin_8); //读取 PA9 的输入电平
有兴趣的朋友不妨修改我们的位带操作为库函数直接操作,这样也有利于学习。
将 led.h 也保存一下。接着,我们在 Manage Project Itmes 管理里面新建一个 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 "usart.h"
#include "delay.h"
#include "led.h"
int main(void)
{
HAL_Init(); //初始化 HAL 库
Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟,72M
delay_init(72);
//初始化延时函数
LED_Init();
//初始化 LED
while(1)
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,GPIO_PIN_RESET);
//LED0 对应引脚 PA8 拉低,亮,等同于 LED0(0)
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
//LED1 对应引脚 PD2 拉高,灭,等同于 LED1(1)
delay_ms(500);
//延时 500ms
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,GPIO_PIN_SET);
//LED0 对应引脚 PA8 拉高,灭,等同于 LED0(1)
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
//LED1 对应引脚 PD2 拉低,亮,等同于 LED1(0)
delay_ms(500);
}
}
代码包含了#include "led.h"这句,使得 LED0、LED1、LED_Init 等能在 main()函数里被调用。
main()函数非常简单,先调用 HAL_Init 函数初始化 HAL 库,然后调用 Stm32_Clock_Init 进行
时钟系统配置,然后调用 delay_init()函数进行延时初始化。接着就是调用 LED_Init()来初始化
PA8 和 PA2 为推挽输出模式,最后在 while 死循环里面实现 LED0 和 LED1 交替闪烁,间隔为
500ms。
上面是通过库函数来实现的 IO 操作,我们也可以修改 main()函数,直接通过位带操作达到
同样的效果,大家不妨试试。位带操作的代码如下:
int main(void)
{
HAL_Init(); //初始化 HAL 库
Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟,72M
delay_init(72);
//初始化延时函数
LED_Init();
//初始化 LED
while(1)
{
LED0=0;
//LED0 亮
LED1=1;
//LED1 灭
delay_ms(500);
LED0=1;
//LED0 灭
LED1=0;
//LED1 亮
delay_ms(500);
}
}
delay_ms(500); //延时 500ms
}
}
将主函数替换为上面代码,然后重新执行,可以看到,结果跟库函数操作和位带操作一样
的效果。大家可以对比一下。编译工程,得到结果如图 6.3.9 所示:
图 6.3.9 编译结果


可以看到没有错误,也没有警告。从编译信息可以看出,我们的代码占用 FLASH 大小为:
4516 字节(4038+478),所用的 SRAM 大小为:1928 个字节(1896+32)。
这里我们解释一下,编译结果里面的几个数据的意义:
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 之和。
接下来,大家就可以下载验证了。如果有 ST-LINK,则可以用 ST-LINK 进行在线调试(需
要先下载代码),单步查看代码的运行,STM32F1 的在线调试方法介绍请参见 3.4.2 小节。
6.4 下载验证
这里我们使用 flymcu 下载(也可以通过 ST-LINK 等仿真器下载,具体方法请参考 3.4.2 小
节),如图 6.4.1 所示:
6.4.1 利用 flymcu 下载代码


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

图 6.4.2 程序运行结果

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






更多回帖

发帖
×
20
完善资料,
赚取积分