V1.0
嵌入式基础教程<br />——<br />STM32单片机卷2HelloWorld<br/>节04标准库点灯实验<br/><br/>最新版本V1.0
<br>王道嵌入式团队<br/>COPYRIGHT ⓒ 2021-2025 王道版权所有电路接线思路分析库函数讲解RCC_APB2PeriphClockCmd函数附录: 时钟是什么?GPIO_Init函数GPIO_WriteBit函数编写代码,实现功能使用Astyle自动格式化代码(必做)第一步: 下载Astyle第二步: 将Astyle文件夹放入Keil5安装目录下第三步:在Keil5软件中配置AStyle第四步:配置快捷键注意事项The End
Gn!
在进行编码实现PC13点灯之前,首先需要使用ST-LINK仿真器将STM32F103C8T6最小系统板和电脑链接到一起。
具体的接线方式,如下图所示:
需要注意几个细节:
最小系统板连接ST-LINK的四个针脚,板子上的丝印可能会有名称上的不同,但本质上没有区别。以下丝印名称的意义都是一致的:
四个针脚从上到下:
GND --- G
SWCLK --- DCLK --- CLK
SWDIO --- DIO -- SWIO
3.3 --- 3V3 --- 3.3V
ST-LINK仿真器上我们所需的四个针脚是:SWCLK、SWDIO、GND、3.3V。只需要找到这四个针脚,然后按照图中的方式和最小系统板链接即可。无需在意你手中的ST-LINK针脚顺序是否和图中一致。
不要在意图中线的颜色,只要将对应位置的针脚用母对母杜邦线连接起来就可以了。重要的是对应针脚按名称连接起来,颜色不重要,针脚位置也不重要。
连线的时候要细心一点,连错了肯定做不成功实验。
验证接线是否正确:
如果接线是成功的,最小系统板的PWR电源指示灯会常亮。除此之外,你还可以打开你的标准库模板工程,尝试向MCU中烧录一个空的程序。
如下图所示:
若烧录成功,没有报错,则说明连续成功了。(由于烧录了一个空程序,烧录完成后PC13指示灯会熄灭)
Gn!
一般来说,使用GPIO操作外设时,基本的步骤有三个:
开启GPIO时钟。
在STM32微控制器中,为了降低功耗,默认情况下大部分外设的时钟是关闭的。开启时钟是使用 GPIO 或其他外设的前提,只有在时钟开启后,才能对相应的 GPIO 端口或外设进行操作。
关于时钟我们后面课程会详细讲,本着入门不增加负担的考虑,这里不详细讲时钟了。
可以把时钟简单理解成一个节拍器,为 STM32 里的外设提供工作节奏。只有时钟打开了,外设们才知道如何工作。
或者你可以将时钟开启,理解成"给外设供电、或者心脏给人体的某个需要使用的部分供血"。
总之,对于大多数片内外设的使用,手动开启时钟都是第一步。
配置引脚工作模式。根据需求为引脚配置不同的工作模式。
控制引脚电平。根据需求来控制引脚的电平,从而实现功能。
Gn!
为了点亮PC13指示灯,我们就需要完成上述三个步骤,每一个步骤都有一个封装好的库函数使用,它们分别是:
RCC_APB2PeriphClockCmd
函数
GPIO_Init
函数
GPIO_WriteBit
函数下面逐一介绍这三个函数。
小Tips:
第一次见到STM32的外设标准库函数,肯定会觉得它们的命名很古怪,和传统C语言函数的命名风格很不同。
之所以这么设计,其原因是为了与C 语言标准库做出区分,让开发者一眼就能看出是在使用 STM32 标准库函数,而不是 C 语言标准库函数,提高代码的可读性和可维护性。
Gn!
我们需要做的第一步是开启时钟,开启外设时钟。
PC13引脚属于GPIOC外设,而GPIOC又挂载在APB2外设总线上,所以我们使用的库函数就是RCC_APB2PeriphClockCmd函数。
其函数声明如下:
xxxxxxxxxx
11void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
函数没有返回值,所以只需要弄明白它的参数即可。
解释一下形参:
RCC_APB2Periph形参:
这是一个uint32_t类型的参数,用来指定要操作的 APB2 总线上的外设。
该参数的取值不需要手动给定一个无符号数,标准库中已经给出了各种外设的宏定义供程序员使用。
打开标准库头文件
stm32f10x_rcc.h
,Ctrl + F搜索字符串"RCC_APB2Periph",就可以找到下图内容:
想要打开某个外设时钟,就将对应的宏定义作为参数传递进去。
把上面的十六进制无符号数,全部转换成二进制(用0b开头),如下所示:
xxxxxxxxxx
9123456789.....
所以若想要一次性开启多个APB2外设时钟,只需要用"|"按位或运算符连接多个宏定义就可以了。
NewState形参:
这是一个
FunctionalState
类型的参数,用于指定要对上述指定的外设执行的操作,它可以取两个值:
ENABLE
:开启指定外设的时钟。
DISABLE
:关闭指定外设的时钟。
FunctionalState
实际上是一个枚举类型,它的定义可以在头文件stm32f10x.h
中找到。如下:xxxxxxxxxx
11typedef enum {DISABLE = 0, ENABLE =!DISABLE} FunctionalState;
其实
ENABLE
就是1,就是true;DISABLE
就是0,就是false。
FunctionalState
这个枚举类型,在STM32的库函数编程中,是很常见的,常用于表示各种功能的开关。
ENABLE
用于开启,DISABLE
用于关闭。
Gn!
时钟是STM32单片机的核心概念,是一个复杂且重要的功能。但是在课程入门阶段,我们暂时抛开时钟系统的复杂性,简单了解一下并使用时钟。
时钟的基本概念:
时钟基于STM32单片机,就相当于心脏基于人体。
时钟为嵌入式系统的设备提供 “心跳”,为所有硬件外设(如 GPIO、串口、定时器等)提供稳定且同步的工作节奏。就像人类需要心跳维持生命活动一样,STM32 芯片内部的各种功能模块也需要时钟信号驱动才能正常工作。
为什么需要时钟?
时钟发送的时钟信号可以类比乐队指挥的节拍,它可以确保所有硬件模块按统一节奏工作,这在通信中尤为重要。
除此之外,时钟信号的频率高低还决定了STM32外设的运行速度。
比如GPIO引脚的输出速度,引脚输出速度越快意味着:引脚能够更快地建立起稳定的高电平信号,也能更快的切换到低电平输出状态。当然功耗也就越高。
再比如通信中传输数据的速度,也是依靠时钟频率来决定的。
时钟信号的频率是多少?
STM32F103C8T6 的主时钟频率是72MHZ,再基于分频操作,不同外设上所使用的时钟频率是不同的。
比如:
APB2外设总线的最大时钟频率也是72MHZ,也就是不分频。
APB1外设总线的最大时钟频率是36MHZ,也就是2分频。
这也是我们前面说过APB2总线的性能更高的原因。
为什么要开启时钟?
为了最大化的节省系统功耗,STM32 的外设默认处于时钟关闭状态。所以在使用单片机的外设时,第一步往往都需要开启此外设的时钟。
而且由于外设挂载的总线不同,开启外设时钟的库函数也会有所不同。
总之:时钟是单片机外设运行的基础,使用某个外设前都需要开启对应时钟,相当于"供血"。
补充:哪些外设在使用的时候,是不需要手动开启时钟呢?RoRdOg
不需要手动开启时钟的外设主要分两种情况:
外设自身就属于内核的一部分,而不是挂载在外部总线上(严格来说它们不算"外设"),它们自然就不需要手动开启时钟。这种例子只有两个:
嵌套向量中断控制器(NVIC),用于配置管理系统中的中断。
系统滴答定时器(SysTick),是一个简易的系统定时器。
系统启动时,会自动开启时钟的外设。这种情况就多一些,最典型的例子就是:复位和时钟控制器(RCC),本身就用于管理系统中外设时钟的开启关闭,它自己的始终当然是默认开启的。
这些内容在后续的课程中,我们会慢慢涉及,大家暂时先了解一下即可。
Gn!
GPIO_Init
是 STM32 标准外设库中用于初始化 GPIO 引脚的重要函数,通过这个函数你可以配置引脚的工作模式和输出速度。其函数声明如下:
xxxxxxxxxx
11void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
函数没有返回值,所以只需要弄明白它的参数即可。
解释一下形参:
GPIOx形参:
这是一个指向
GPIO_TypeDef
结构体的指针,用于指定要初始化的 GPIO 端口。这个参数的传递,并不需要程序员去慢慢创建GPIO_TypeDef结构体对象,再传递结构体对象指针。‘
而是在头文件
stm32f10x.h
中,直接用宏定义,定义了可能操作的GPIO 端口,其内容如下:
所以,如果你想要初始化 GPIOC,那么这个参数可以直接传递
GPIOC
。GPIO_InitStruct形参:
这是一个指向
GPIO_InitTypeDef
结构体的指针,该结构体包含了 GPIO 引脚的各种初始化配置信息。该结构体的类型定义,可以在标准库头文件
stm32f10x_gpio.h
中找到,内容如下所示:xxxxxxxxxx
51typedef struct{
2uint16_t GPIO_Pin; // 要初始化的引脚编号
3GPIOSpeed_TypeDef GPIO_Speed; // 引脚的输出速度
4GPIOMode_TypeDef GPIO_Mode; // 引脚的工作模式
5} GPIO_InitTypeDef;
下面解释一下这个结构体的三个成员:
GPIO_Pin成员:
该成员的取值表示要初始化的引脚编号,也就是PC13中的这个13。它是一个16位无符号整数,但它的取值仍然依赖于宏定义,也可以直接在标准库头文件
stm32f10x_gpio.h
中找到。内容如下所示:
比如要配置PC13引脚,该成员就可以设置为
GPIO_Pin_13
。如果需要同时配置多个引脚,使用按位或运算符
|
即可。比如同时初始化引脚 10 和 11,可以设置为GPIO_Pin_10 | GPIO_Pin_11
。GPIO_Speed成员:
指定引脚的输出速度,它是
GPIOSpeed_TypeDef
枚举类型,可以在标准库头文件stm32f10x_gpio.h
中找到此枚举类型定义。它共有三个成员如下:
GPIO_Speed_2MHz
:低速,输出速度为 2MHz。
GPIO_Speed_10MHz
:中速,输出速度为 10MHz。
GPIO_Speed_50MHz
:高速,输出速度为 50MHz。IO输出速度是一个稍微有点麻烦的概念,但简单来说:
GPIO的输出速度越快,引脚切换电平的时间就越快,对一些需要高速切换电平的外设来说,IO输出速度不能低于最低要求。
IO输出速度越快就越耗电,如果使用电池供电时,就需要考虑功耗问题。
输出速度要根据引脚操作的具体外设来决定,在可能的情况下,尽量选择更低速的输出速度以降低功耗。
在学习的过程中,我们不考虑功耗问题,所以如无特殊情况,可以直接使用高速输出速度。
GPIO_Mode成员:
指定引脚的工作模式,它需要传参一个
GPIOMode_TypeDef
枚举类型的值。可以在标准库头文件stm32f10x_gpio.h
中找到此枚举类型定义。它有以下成员:
GPIO_Mode_AIN
:模拟输入模式。
AIN 代表 Analog Input(模拟输入)
用于接收引脚模拟信号输入,我们暂时用不到。
GPIO_Mode_IN_FLOATING
:浮空输入模式。IN 代表 Input(输入),FLOATING 代表 Floating(浮空)。
GPIO_Mode_IPD
:下拉输入模式。IPD 代表 Input Pull-Down(输入下拉)。
GPIO_Mode_IPU
:上拉输入模式。IPU 代表 Input Pull-Up(输入上拉)。
GPIO_Mode_Out_OD
:开漏输出模式。Out 代表 Output(输出),OD 代表 Open-Drain(开漏)。
GPIO_Mode_Out_PP
:推挽输出模式。Out 代表 Output(输出),PP 代表 Push-Pull(推挽)。
GPIO_Mode_AF_OD
:复用开漏输出模式。AF 代表 Alternate Function(复用功能),OD 代表 Open-Drain(开漏)。
GPIO_Mode_AF_PP
:复用推挽输出模式。AF 代表 Alternate Function(复用功能),PP 代表 Push-Pull(推挽)。注意,一个引脚在同一时间不能同时设置为多种工作模式,只能在上述枚举值中选择一个。
Gn!
GPIO_WriteBit
是 STM32 标准外设库中用于设置 GPIO 引脚电平的函数。该函数的函数名中带有"Write"单词,说明它是一个输出向的函数。实际上该函数在引脚配置为输出模式下才能够使用,用于向输出数据寄存器写1或者0,用于控制引脚的电平状态。
该函数的声明如下所示:
xxxxxxxxxx
11void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
函数没有返回值,所以只需要弄明白它的参数即可。
解释一下形参:
GPIOx形参:
这是一个指向
GPIO_TypeDef
结构体的指针,用于指定要初始化的 GPIO 端口。上面已经解释过了,如果你想要初始化 GPIOC,那么这个参数可以直接传递
GPIOC
。GPIO_Pin形参:
在上面的
GPIO_InitTypeDef
结构体中,我们见过同名的成员,实际上它们的意思也确实是一样的。用于指出要操作的引脚编号,如果要操作的是13号引脚,可以直接传参
GPIO_Pin_13
。BitVal形参:
这是一个
BitAction
类型的参数,用于指定向输出数据寄存器输出0或者1。这是一个枚举类型,可以在标准库头文件stm32f10x_gpio.h
中找到此枚举类型定义,其类型定义如下():x1typedef enum{
2Bit_RESET = 0,
3Bit_SET
4}BitAction;
很明显,它可以取两个值:
Bit_RESET
:向输出数据寄存器中写入0
Bit_SET
:向输出数据寄存器中写入1利用该函数就可以完成对引脚的控制,当然选择不同的输出模式,最终引脚的电平表现是不同的。
Gn!
现在我们使用开漏输出模式,来实现点亮/熄灭PC13指示灯,参考代码如下:
x1// Device header
2
3int main(void) {
4// 1.开启GPIOC外设时钟
5RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
6
7// 2.1 初始化GPIO_InitTypeDef结构体,设置工作模式和输出速度
8GPIO_InitTypeDef GPIO_InitStruct; // GPIO_InitStruct是一个习惯命名
9GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13; // 初始化的引脚是PC13引脚
10GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 工作速度为高速50MHz
11GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; // 引脚的工作模式是开漏输出模式
12// 2.2 调用GPIO_Init, 完成PC13引脚初始化
13GPIO_Init(GPIOC, &GPIO_InitStruct);
14
15// 下面两句代码,轮流放开可以控制PC13指示灯的亮灭
16// 3.1 在开漏输出模式的情况下,向输出数据寄存器写0,引脚输出低电平,点亮
17// GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET);
18// 3.2 在开漏输出模式的情况下,向输出数据寄存器写1,引脚高阻抗,熄灭
19// GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET);
20
21while (1) {
22}
23}
24
编号3的两句代码,可以轮流注释掉,从而控制PC13指示灯的亮灭。
除此之外,我们还可以通过一个简单的思路,实现指示灯的闪烁:
点亮PC13指示灯
程序延时休眠一段时间,比如100ms
熄灭PC13指示灯
程序延时休眠一段时间,比如100ms
循环往复的执行上述结构,就能轻松实现闪烁了。
首先,为了实现程序延时功能,你还需要把"Delay模块"的源文件和头文件添加到工程中。你可以在"百度网盘 -- 函数模块 -- Delay函数模块"文件夹下找到这个源文件和头文件。
该模块提供了下面三个函数,大家可以直接使用:
首先你需要在工程目录下,新建一个"Tools"文件夹用来存放模块的文件,并且需要同步在Keil5软件中新建一个"Tools"组并将文件添加进去,最后你还需要将"Tools"添加到工程的头文件目录中去。
这些操作之前文档中都做过,这里不再赘述,若有疑问不妨直接互相问问或者问问老师。
添加完成后,不要忘记修改main.c文件,需要在main.c文件中包含头文件。
整体实现后的参考代码如下所示:
x1// Device header
2// 包含Delay模块头文件
3
4int main(void) {
5// 1.开启GPIOC外设时钟
6RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
7
8// 2.1 初始化GPIO_InitTypeDef结构体,设置工作模式和输出速度
9GPIO_InitTypeDef GPIO_InitStruct; // GPIO_InitStruct是一个习惯命名
10GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13; // 初始化的引脚是PC13引脚
11GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 工作速度为高速50MHz
12GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; // 引脚的工作模式是开漏输出模式
13// 2.2 调用GPIO_Init, 完成PC13引脚初始化
14GPIO_Init(GPIOC, &GPIO_InitStruct);
15
16// 下面两句代码,轮流放开可以控制PC13指示灯的亮灭
17// 3.1 在开漏输出模式的情况下,向输出数据寄存器写0,引脚输出低电平,点亮
18// GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET);
19// 3.2 在开漏输出模式的情况下,向输出数据寄存器写1,引脚高阻抗,熄灭
20// GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET);
21
22while (1) {
23// PC13指示灯闪烁
24// 1.点亮PC13指示灯
25GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET);
26// 2.延时100ms
27Delay_Ms(100);
28// 熄灭PC13指示灯
29GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET);
30// 2.延时100ms
31Delay_Ms(100);
32}
33}
34
以上,一个最最基本的STM32实验就完成啦,恭喜你,你现在已经可以称得上入门了。
Gn!
在Keil5软件当中编写代码,由于Keil5软件本身没有自动格式化代码的功能,经常会碰到一个棘手的问题,
手动调整代码格式既繁琐又耗时,影响编码效率。
但如果无视代码缩进对齐格式,不仅影响可读性,还可能导致对代码结构的误判,例如大括号边界不清晰,容易引发逻辑错误。
总之,我们需要一个三方工具来帮助我们完成代码的自动格式化:也就是使用一个简单的快捷键,就可以自动格式化代码,对齐代码格式。
Astyle(Artistic Style) 是一个 代码自动格式化工具,用于美化和规范 C、C++、C# 和 Java 代码的缩进、对齐、括号风格等。它可以自动调整代码格式,提高可读性,减少因代码缩进混乱而导致的错误。
Astyle可以集成到Keil5软件当中,下面来讲解一下该软件的使用流程。
Gn!
大家可以在"百度网盘 -- 王道嵌入式软件环境 -- Astyle软件"当中找到Astyle软件的压缩包。
如果你已经下载过了,那就不需要重新下载了,如果还没有下载那就去百度网盘中下载一下。
通过网盘分享的文件:000_王道嵌入式软件环境
链接: https://pan.baidu.com/s/1PovrhzvoIKDGgIZ4ZxYETA?pwd=4cxz 提取码: 4cxz
注意下载完成后,要将它进行解压缩操作,比如解压缩到桌面,你将得到下面一个文件夹:
Gn!
接下来,你需要将解压缩好的Astyle文件夹放入Keil5安装目录下,具体而言需要将这个文件夹放入安装时选择的Core位置。
如果在安装Keil5软件的时候如果你专门配置了这个Core位置,如下图所示:
那么你就找到这个Core位置,将文件夹剪切进去,如下图所示:
如果你实在找不到放到哪里,可以求助老师。
Gn!
最后只需要在Keil5软件当中配置AStyle即可,操作步骤如下:
打开Keil5菜单栏的下列菜单:
打开后,在弹出的窗口进行下列配置:
最后一步,需要填入一系列格式化相关的指令,这些指令用于确定将代码最终格式化为什么样子。
大家可以直接参考我的配置:
xxxxxxxxxx
11!E --style=java --indent-col1-comments --break-blocks --pad-oper --pad-comma --pad-header --unpad-paren --align-pointer=name --break-one-line-headers --add-braces --max-code-length=120
直接将这一段复制粘贴到上面"Arguments"输入框中。
注:
这些指令的作用如下列表格所示:
参数 作用 !E
Keil5 传入当前编辑的文件路径,也就是格式化当前文件 --style=java
采用 Java 的大括号风格(大括号独占一行) --indent-col1-comments
自动缩进注释格式,使其对齐代码块 --break-blocks
在代码块之间添加空行 --pad-oper
操作符( +
、-
、=
等)两侧加空格--pad-comma
逗号 ,
后面加空格--pad-header
if
,for
,while
关键字后加空格--unpad-paren
移除括号 ()
内侧的空格,让括号更紧凑--align-pointer=name
对齐指针 *
,使其靠近变量名,而不是数据类型--break-one-line-headers
if
,while
,for
语句强制换行--add-braces
为单行 if
,while
,for
语句添加大括号{}
--max-code-length=120
代码行长度超过 120个字符 时换行 一般不需要修改上述这些指令,大家直接照抄即可,感兴趣且有闲余时间再考虑去研究研究这些指令。
完成上述配置后,就可以测试一下格式化效果了,使用方式如下:
注意点击的选项就是你刚刚新增栏目的名字,如果你的名字和我不一样,那么看到的选项名也不一样。
Gn!
如果每一次格式化代码都需要点击选项,还是有点太慢了,太笨了。所以我们要给Astyle格式化代码,配置一个快捷键。
具体的操作流程如下:
我建议将这个快捷键配置为"F4",你也可以配置为你自己喜欢的快捷键。
Gn!
在使用快捷键(比如"F4")格式化代码之前,推荐先使用快捷键"Ctrl + S"保存一下文件,然后再执行格式化操作。
如果修改代码后没有保存文件,直接使用快捷键格式化代码,有几率会格式化失败!