STM32 入门开发 - 详解 GPIO 口的配置与控制方式(以 LED 灯为例)
[TOC]
一、前言
这篇文章学习STM32F103
单片机,以寄存器方式,点亮 LED 灯。以控制 LED 灯为例,学习如何配置 STM32 的寄存器,实现输出高低电平的控制。
所以,重点不是 LED 灯如何控制,重点是教会大家如何写代码配置 STM32 的 GPIO 口,实现对 LED 这种外设模块进行控制。
学会如何配置 GPIO 口,不管是多少 LED 灯,还是其他的外设模块,比如:继电器、电机、各种传感器,控制办法都是一样的,都是高低电平进行控制。
![](https://static001.geekbang.org/infoq/48/488d324b091e056508a382830173a61c.png)
二、系列文章
在本专栏里,除了有很多完整的项目案例之外,剩下的大部分文章是讲解 STM32 的基础编程,方便没有基础的同学可以从 0 开始学习 STM32 编程,我的所有 STM32 项目,都是采用寄存器
风格编程,整体代码简洁,工程文件少,工程构造简单,这样写出的代码也很方便移植到其他系列单片机使用。
下面是物联网项目开发专栏
里的一部分 STM32 基础开发系列文章,大家可以打开专栏看目录学习。
三、如何学习?
在学习 STM32 单片机编程之前,肯定得有点基础的准备。
(1)先保证自己的 C 语言基础过关,不需要太高深的 C 语言基础,最起码 C 语言的基本语法要能够看懂。
(2)C 语言的位运算
是必须必须
要会,我的 STM32 教程里都是采用寄存器编程,寄存器编程都是采用位运算操作 STM32 的寄存器,如果你不懂位运算,那么可能就看不懂如何操作硬件寄存器。
(3)要先自己安装好 keil 软件,要会 keil 软件的基本使用:比如,如何新建工程? 如何添加.c 文件?如何添加.h 头文件?如何设置.h 头文件的搜索路径?
如果不会 C 语言怎么办?
你可以关注我的微信公众号:《DS 小龙哥嵌入式技术资讯》进行学习。 在公众号里,有完全免费
的 C 语言教程可以学习,在菜单栏里,可以找到 C 语言的菜单。
也可以去网盘里下载文档学习: https://pan.quark.cn/s/aa9abc2979c4
![](https://static001.geekbang.org/infoq/55/5586ed0af14e5b66ae395a16cc10fa55.png)
如果 STM32 文章看起来有难度怎么办?
**【1】可以看视频辅助学习,这个视频是以前我录制的课堂上现场讲解的一部分
STM32 的视频,大家可以下载学习(这是网盘下载):**https://pan.quark.cn/s/266dcb6db50c
【2】后面也会在 B 站发布更新 STM32 基础学习视频,会重新录制整套 STM32 的视频(可以关注动态): https://space.bilibili.com/68130189
**【3】教程里用到的各种文档资料、工具,软件,在哪里下载? (网盘下载): ** https://pan.quark.cn/s/7b2aacb3be24
【4】学会 STM32 之后有哪些项目可以练手?(网盘下载) https://pan.quark.cn/s/b9e518ea5beb
四、STM32 编程-控制 LED 灯
4.1 STM32 开发板
学习之前,那肯定得准备一块 STM32 的开发板。只要是STM32F103
的芯片都可以,本系列教程都适用。
4.2 原理图
控制 LED 灯,首先需要知道 LED 灯的原理图,知道 LED 是连接到 STM32 板子的那一个 IO 口才可以编程。
![](https://static001.geekbang.org/infoq/e7/e7a41d5838e328d382e268e324482fe7.png)
![](https://static001.geekbang.org/infoq/c4/c4a2529542c9e634991af66370849836.png)
从原理图上得知,LED0 接在PD2
,LED1 接在PA8
上面的。
4.3 STM32 的 GPIO 口
STM32 的 GPIO 口是分组管理的,它的命名规则是这样的: GPIOA
、GPIOB
、GPIOC
、GPIOD
.......
每个组里面有 16 个口,比如(简称):
PA0、PA1、PA2 ... PA15
PB0、PB1、PB2 ... PB15
...
在寄存器里管理这些 IO 口的时候,每个组分为了高和低两个部分。
L 表示低,IO 口的编号范围是: 0 ~ 7
H 表示高,IO 口的编号范围是: 8 ~ 15
4.4 开时钟
STM32 的每个组的 GPIO 口要能使用,需要打开对应组的时钟。其实就是相当于打开开关
,表示允许这个组的 GPIO 口可以配置。
控制时钟开关的寄存器是,RCC_APB1
和 RCC_APB2
。
在数据手册第 6 章节有说明这个时钟如何开。
![](https://static001.geekbang.org/infoq/67/67e5955255e6abc963368396ec9bedbc.png)
从下面图标出来的寄存器位置可以知道,要开启时钟,只需要把对应的寄存器位设置为 1 就可以了。
![](https://static001.geekbang.org/infoq/9a/9a1658aaaae38777dc486a8a903265b5.png)
如果我要把GPIOA
的时钟打开,代码里如何写呢? 可以这样写
如果我要把GPIOB
的时钟打开,代码里如何写呢? 可以这样写
如果我要把GPIOC
的时钟打开,代码里如何写呢? 可以这样写
如果我要把GPIOD
的时钟打开,代码里如何写呢? 可以这样写
如果要继续开其他外设的时钟,按这个规律写就行。
在第 6.2 章还有一张图,时钟树
。 这个是用来描述 STM32 芯片内部的时钟的情况(看不懂也没有关系,这不影响下面的编程)。
![](https://static001.geekbang.org/infoq/39/390b7a5a67df92a16413a9821a06794f.png)
4.5 配置 GPIO 模式的寄存器
在数据手册的第 8 章,是专门配置 GPIO 口的章节。可以配置模式、输出控制、输入检测等等。
下面是 GPIO 口端口模式配置对应的寄存器,名字叫:GPIOX_CRL
和 GPIOX_CRH
。
![](https://static001.geekbang.org/infoq/1a/1aac3ed7eba4e7f7fa2f2c044a2aebc9.png)
端口配置低寄存器 (GPIOx_CRL) (x=A..E)
端口配置高寄存器 (GPIOx_CRH) (x=A..E)
这个寄存器为什么分GPIOX_CRL
和 GPIOX_CRH
?
前面已经说过,STM32 的 IO 口为了高、低两个部分进行管理。 0~7 的 IO 口编号属于低位,8 ~ 15 的 IO 口属于高位。
其中的 X 是代号,实际使用可以替换为:A、B、C、D、..... 也就是实际 IO 口的分组名字。
那么这个GPIOx_CRL
和 GPIOx_CRH
寄存器如何使用呢?
从下面的寄存器截图里可以看出,这个寄存器是 32 位,有 32 个位。 每 4 个位表示一个 GPIO 口的模式。
![](https://static001.geekbang.org/infoq/fd/fd4d24bac282835f4c2bc2a103af7197.png)
这 4 个位如何填,从上面截图里的 2 个红色框框可以看到的很清楚,这个 4 个位可以有很多种组合,每个组合都表示了一直模式。
CNFy:端口配置位
MODEy 端口模式位:
如果现在的 GPIO 口是要控制 LED 灯、控制继电器、或者控制其他外设,需要强大的驱动力气,对速度没有要求,那就需要配置为推挽输出
模式。
对的 4 个位就应该填:0011
,前面的00
表示 通用推挽输出模式; 后面的11
表示输出模式,最大速度 50MHz。
如果你需要配置为其他模式,那么就按这个规律进行组合即可。
**【1】如果我要将 PB6 配置为推挽输出模式,应该怎么写代码? ** 说明:这里的 PB6 表示 GPIOB 这个组的第 6 个 IO 口。
**【2】如果我要将 PB6 和 PB7 同时配置为推挽输出模式,应该怎么写代码? ** 看下面的代码学习。
**【3】如果我要将 PC2 配置为推挽输出模式,应该怎么写代码? ** 看下面的代码学习。
**【4】如果我要将 PA8 配置为推挽输出模式,应该怎么写代码? ** 看下面的代码学习。
细心的同学可能发现,PA8 的代码写法与前面的几个例子不一样了。 不再是 CRL,而是 CRH 了。 因为 PA8 是属于高位,属于8~15
的范围,对应的寄存器是CRH
。
通过以上几个例子,相信大家已经看懂了吧? 如果你要继续配置其他的 IO 口,照着这个规律配置就行了。
4.6 编写 LED 灯的初始化代码
下面就来实操一下,学一个完整的代码,初始化 LED 灯链接的 GPIO 口。
4.7 GPIO 口控制输出寄存器
上面已经将模式配置好了,配置为输出模式。 那如何控制 GPIO 口输出?
看下面的寄存器GPIOx_ODR
。 GPIOx_ODR
这个寄存器就是用来控制 GPIO 口每个位输出0
和1
的。
![](https://static001.geekbang.org/infoq/c9/c965a7fa1a311f7ba28849ecaa4ee0bb.png)
那么这个GPIOx_ODR
寄存器如何使用呢? 下面举几个例子,大家就明白了。
【1】如果想控制 PB6 这个口输出 1,应该怎么写?
【2】如果想控制 PB6 这个口输出 0,应该怎么写?
【3】如果想控制 PC2 这个口输出 0,应该怎么写?
【4】如果想控制 PA13 这个口输出 1,应该怎么写?
通过以上几个例子,相信大家已经看懂了吧? 如果你要继续配置其他的 IO 口输出,照着这个规律写就行了。
4.8 一个完整的闪光灯程序代码
这个也就是控制 LED 灯,一亮,一灭的效果代码。让大家看看如何在主函数里调用写好的函数。
下面是工程代码截图:
![](https://static001.geekbang.org/infoq/eb/eb58676a7b3d4ed2b91cd96d0e8dcee1.png)
五、关于寄存器是问题
喜欢深究代码
探寻真理
的同学,看了上面内容之后,可能还有疑问。
比如 下面的代码:
我们写了这个代码之后,就可以配置PB6和PB7
为推挽输出模式。
从 C 语言的语法上我们可以看出GPIOB->CRL
是一个结构体的类型, 那么这个GPIOB
是怎么来的? 在哪里定义的?
我们建立工程的时候,是会添加一个stm32f10x.h
头文件。 并在代码里最前面引用了。
这个头文件是ST
官方提供的,里面已经定义了全部寄存器,设置好了地址偏移,我们只要在代码里包含了#include "stm32f10x.h"
头文件。就可以直接使用已经定义好的寄存器进行配置。
![](https://static001.geekbang.org/infoq/a8/a837fa1bbfe1c3e0d071eea2ddfaf769.png)
![](https://static001.geekbang.org/infoq/54/541de7d0cc303bbbac17d605b0f49460.png)
那我们可不可以自己定义寄存器名字,不要官方的stm32f10x.h
头文件呢? 那当然是可以的。
如果你为了能更加清晰的搞懂底层,是可以自己写的头文件的。
我们注意看数据手册,在每个寄存器上,都写了这个寄存器的地址偏移
的。
![](https://static001.geekbang.org/infoq/e8/e8f504abd786d87a2248b01d574b5f65.png)
![](https://static001.geekbang.org/infoq/68/68fc6fd554c369c9d9d7f84d74d86a89.png)
在得知偏移地址
之后,还需要知道基地址,也就是基于什么地址偏移的,这样就可以找到寄存器的真实地址了。
翻到数据手册的 2 章就可以看到每个寄存器的起始地址。
![](https://static001.geekbang.org/infoq/72/72623e6d299e12a81a967ee38c6cfe9c.png)
比如:时钟寄存器 RCC 的起始地址。
![](https://static001.geekbang.org/infoq/46/4682c8db3f6cdea69d281fa623621dfc.png)
比如:GPIO 配置寄存器 A B C D ... 的起始地址。
![](https://static001.geekbang.org/infoq/4a/4a24983671cc3c3ec8f1dcea76cddc44.png)
根据前面的说明的偏移量。起始地址加上偏移量就是这个寄存器的实际地址。 我们只要用 C 语言的指针,定义指针类型指向这个地址,我们就可以对这个地址进行操作进行配置寄存器。 从这里大家应该理解了。
学指针的时候,各种资料都说 C 语言指针是 C 语言的灵魂,还可以操作硬件,这不,活生生的实际例子就来了。
下面给出完整的代码: 下面这个代码例子,就是完全自己定义寄存器,配置寄存器,完成 LED 灯的控制的例子。 大家可以琢磨琢磨。
版权声明: 本文为 InfoQ 作者【DS小龙哥】的原创文章。
原文链接:【http://xie.infoq.cn/article/2ede7f647f67491ebc175ed58】。文章转载请联系作者。
评论