嵌入式基础教程
——
STM32单片机卷2HelloWorld
节04标准库点灯实验

最新版本V1.0
王道嵌入式团队
COPYRIGHT ⓒ 2021-2025 王道版权所有

电路接线

Gn!

在进行编码实现PC13点灯之前,首先需要使用ST-LINK仿真器将STM32F103C8T6最小系统板和电脑链接到一起。

具体的接线方式,如下图所示:

点亮PC13-接线图

需要注意几个细节:

  1. 最小系统板连接ST-LINK的四个针脚,板子上的丝印可能会有名称上的不同,但本质上没有区别。以下丝印名称的意义都是一致的:

    1. 四个针脚从上到下:

    2. GND --- G

    3. SWCLK --- DCLK --- CLK

    4. SWDIO --- DIO -- SWIO

    5. 3.3 --- 3V3 --- 3.3V

  2. ST-LINK仿真器上我们所需的四个针脚是:SWCLK、SWDIO、GND、3.3V。只需要找到这四个针脚,然后按照图中的方式和最小系统板链接即可。无需在意你手中的ST-LINK针脚顺序是否和图中一致。

  3. 不要在意图中线的颜色,只要将对应位置的针脚用母对母杜邦线连接起来就可以了。重要的是对应针脚按名称连接起来,颜色不重要,针脚位置也不重要。

  4. 连线的时候要细心一点,连错了肯定做不成功实验。

验证接线是否正确:

如果接线是成功的,最小系统板的PWR电源指示灯会常亮。除此之外,你还可以打开你的标准库模板工程,尝试向MCU中烧录一个空的程序。

如下图所示:

验证接线烧录程序-图

若烧录成功,没有报错,则说明连续成功了。(由于烧录了一个空程序,烧录完成后PC13指示灯会熄灭)

思路分析

Gn!

一般来说,使用GPIO操作外设时,基本的步骤有三个:

  1. 开启GPIO时钟。

    1. 在STM32微控制器中,为了降低功耗,默认情况下大部分外设的时钟是关闭的。开启时钟是使用 GPIO 或其他外设的前提,只有在时钟开启后,才能对相应的 GPIO 端口或外设进行操作。

    2. 关于时钟我们后面课程会详细讲,本着入门不增加负担的考虑,这里不详细讲时钟了。

    3. 可以把时钟简单理解成一个节拍器,为 STM32 里的外设提供工作节奏。只有时钟打开了,外设们才知道如何工作。

    4. 或者你可以将时钟开启,理解成"给外设供电、或者心脏给人体的某个需要使用的部分供血"。

    5. 总之,对于大多数片内外设的使用,手动开启时钟都是第一步。

  2. 配置引脚工作模式。根据需求为引脚配置不同的工作模式。

  3. 控制引脚电平。根据需求来控制引脚的电平,从而实现功能。

库函数讲解

Gn!

为了点亮PC13指示灯,我们就需要完成上述三个步骤,每一个步骤都有一个封装好的库函数使用,它们分别是:

  1. RCC_APB2PeriphClockCmd函数

  2. GPIO_Init函数

  3. GPIO_WriteBit函数

下面逐一介绍这三个函数。

小Tips:

第一次见到STM32的外设标准库函数,肯定会觉得它们的命名很古怪,和传统C语言函数的命名风格很不同。

之所以这么设计,其原因是为了与C 语言标准库做出区分,让开发者一眼就能看出是在使用 STM32 标准库函数,而不是 C 语言标准库函数,提高代码的可读性和可维护性。

RCC_APB2PeriphClockCmd函数

Gn!

我们需要做的第一步是开启时钟,开启外设时钟。

PC13引脚属于GPIOC外设,而GPIOC又挂载在APB2外设总线上,所以我们使用的库函数就是RCC_APB2PeriphClockCmd函数。

其函数声明如下:

函数没有返回值,所以只需要弄明白它的参数即可。

解释一下形参:

RCC_APB2Periph形参:

这是一个uint32_t类型的参数,用来指定要操作的 APB2 总线上的外设。

该参数的取值不需要手动给定一个无符号数,标准库中已经给出了各种外设的宏定义供程序员使用。

打开标准库头文件stm32f10x_rcc.h,Ctrl + F搜索字符串"RCC_APB2Periph",就可以找到下图内容:

APB2外设宏定义-图

想要打开某个外设时钟,就将对应的宏定义作为参数传递进去。

把上面的十六进制无符号数,全部转换成二进制(用0b开头),如下所示:

所以若想要一次性开启多个APB2外设时钟,只需要用"|"按位或运算符连接多个宏定义就可以了。

NewState形参:

这是一个 FunctionalState 类型的参数,用于指定要对上述指定的外设执行的操作,它可以取两个值:

  1. ENABLE:开启指定外设的时钟。

  2. DISABLE:关闭指定外设的时钟。

FunctionalState实际上是一个枚举类型,它的定义可以在头文件stm32f10x.h中找到。如下:

其实ENABLE就是1,就是true;DISABLE就是0,就是false。

FunctionalState 这个枚举类型,在STM32的库函数编程中,是很常见的,常用于表示各种功能的开关。

ENABLE用于开启,DISABLE用于关闭。

附录: 时钟是什么?

Gn!

时钟是STM32单片机的核心概念,是一个复杂且重要的功能。但是在课程入门阶段,我们暂时抛开时钟系统的复杂性,简单了解一下并使用时钟。

时钟的基本概念:

时钟基于STM32单片机,就相当于心脏基于人体。

时钟为嵌入式系统的设备提供 “心跳”,为所有硬件外设(如 GPIO、串口、定时器等)提供稳定且同步的工作节奏。就像人类需要心跳维持生命活动一样,STM32 芯片内部的各种功能模块也需要时钟信号驱动才能正常工作。

为什么需要时钟?

时钟发送的时钟信号可以类比乐队指挥的节拍,它可以确保所有硬件模块按统一节奏工作,这在通信中尤为重要。

除此之外,时钟信号的频率高低还决定了STM32外设的运行速度。

比如GPIO引脚的输出速度,引脚输出速度越快意味着:引脚能够更快地建立起稳定的高电平信号,也能更快的切换到低电平输出状态。当然功耗也就越高。

再比如通信中传输数据的速度,也是依靠时钟频率来决定的。

时钟信号的频率是多少?

STM32F103C8T6 的主时钟频率是72MHZ,再基于分频操作,不同外设上所使用的时钟频率是不同的。

比如:

  1. APB2外设总线的最大时钟频率也是72MHZ,也就是不分频。

  2. APB1外设总线的最大时钟频率是36MHZ,也就是2分频。

这也是我们前面说过APB2总线的性能更高的原因。

为什么要开启时钟?

为了最大化的节省系统功耗,STM32 的外设默认处于时钟关闭状态。所以在使用单片机的外设时,第一步往往都需要开启此外设的时钟。

而且由于外设挂载的总线不同,开启外设时钟的库函数也会有所不同。

总之:时钟是单片机外设运行的基础,使用某个外设前都需要开启对应时钟,相当于"供血"。

补充:哪些外设在使用的时候,是不需要手动开启时钟呢?RoRdOg

不需要手动开启时钟的外设主要分两种情况:

  1. 外设自身就属于内核的一部分,而不是挂载在外部总线上(严格来说它们不算"外设"),它们自然就不需要手动开启时钟。这种例子只有两个

    1. 嵌套向量中断控制器(NVIC),用于配置管理系统中的中断。

    2. 系统滴答定时器(SysTick),是一个简易的系统定时器。

  2. 系统启动时,会自动开启时钟的外设。这种情况就多一些,最典型的例子就是:复位和时钟控制器(RCC),本身就用于管理系统中外设时钟的开启关闭,它自己的始终当然是默认开启的。

这些内容在后续的课程中,我们会慢慢涉及,大家暂时先了解一下即可。

GPIO_Init函数

Gn!

GPIO_Init 是 STM32 标准外设库中用于初始化 GPIO 引脚的重要函数,通过这个函数你可以配置引脚的工作模式和输出速度。

其函数声明如下:

函数没有返回值,所以只需要弄明白它的参数即可。

解释一下形参:

GPIOx形参:

这是一个指向 GPIO_TypeDef 结构体的指针,用于指定要初始化的 GPIO 端口。

这个参数的传递,并不需要程序员去慢慢创建GPIO_TypeDef结构体对象,再传递结构体对象指针。‘

而是在头文件stm32f10x.h中,直接用宏定义,定义了可能操作的GPIO 端口,其内容如下:

GPIO_Init函数-图1

所以,如果你想要初始化 GPIOC,那么这个参数可以直接传递 GPIOC

GPIO_InitStruct形参:

这是一个指向 GPIO_InitTypeDef 结构体的指针,该结构体包含了 GPIO 引脚的各种初始化配置信息。

该结构体的类型定义,可以在标准库头文件stm32f10x_gpio.h中找到,内容如下所示:

下面解释一下这个结构体的三个成员:

GPIO_Pin成员:

该成员的取值表示要初始化的引脚编号,也就是PC13中的这个13。它是一个16位无符号整数,但它的取值仍然依赖于宏定义,也可以直接在标准库头文件stm32f10x_gpio.h中找到。

内容如下所示:

GPIO_Init函数-图2

比如要配置PC13引脚,该成员就可以设置为 GPIO_Pin_13

如果需要同时配置多个引脚,使用按位或运算符 | 即可。比如同时初始化引脚 10 和 11,可以设置为 GPIO_Pin_10 | GPIO_Pin_11

GPIO_Speed成员:

指定引脚的输出速度,它是 GPIOSpeed_TypeDef 枚举类型,可以在标准库头文件stm32f10x_gpio.h中找到此枚举类型定义。

它共有三个成员如下:

  1. GPIO_Speed_2MHz:低速,输出速度为 2MHz。

  2. GPIO_Speed_10MHz:中速,输出速度为 10MHz。

  3. GPIO_Speed_50MHz:高速,输出速度为 50MHz。

IO输出速度是一个稍微有点麻烦的概念,但简单来说:

  1. GPIO的输出速度越快,引脚切换电平的时间就越快,对一些需要高速切换电平的外设来说,IO输出速度不能低于最低要求。

  2. IO输出速度越快就越耗电,如果使用电池供电时,就需要考虑功耗问题。

  3. 输出速度要根据引脚操作的具体外设来决定,在可能的情况下,尽量选择更低速的输出速度以降低功耗。

在学习的过程中,我们不考虑功耗问题,所以如无特殊情况,可以直接使用高速输出速度。

GPIO_Mode成员:

指定引脚的工作模式,它需要传参一个GPIOMode_TypeDef枚举类型的值。可以在标准库头文件stm32f10x_gpio.h中找到此枚举类型定义。

它有以下成员:

  1. GPIO_Mode_AIN:模拟输入模式。

    1. AIN 代表 Analog Input(模拟输入)

    2. 用于接收引脚模拟信号输入,我们暂时用不到。

  2. GPIO_Mode_IN_FLOATING:浮空输入模式。IN 代表 Input(输入),FLOATING 代表 Floating(浮空)。

  3. GPIO_Mode_IPD:下拉输入模式。IPD 代表 Input Pull-Down(输入下拉)。

  4. GPIO_Mode_IPU:上拉输入模式。IPU 代表 Input Pull-Up(输入上拉)。

  5. GPIO_Mode_Out_OD:开漏输出模式。Out 代表 Output(输出),OD 代表 Open-Drain(开漏)。

  6. GPIO_Mode_Out_PP:推挽输出模式。Out 代表 Output(输出),PP 代表 Push-Pull(推挽)。

  7. GPIO_Mode_AF_OD:复用开漏输出模式。AF 代表 Alternate Function(复用功能),OD 代表 Open-Drain(开漏)。

  8. GPIO_Mode_AF_PP:复用推挽输出模式。AF 代表 Alternate Function(复用功能),PP 代表 Push-Pull(推挽)。

注意,一个引脚在同一时间不能同时设置为多种工作模式,只能在上述枚举值中选择一个。

GPIO_WriteBit函数

Gn!

GPIO_WriteBit 是 STM32 标准外设库中用于设置 GPIO 引脚电平的函数。该函数的函数名中带有"Write"单词,说明它是一个输出向的函数。

实际上该函数在引脚配置为输出模式下才能够使用,用于向输出数据寄存器写1或者0,用于控制引脚的电平状态。

该函数的声明如下所示:

函数没有返回值,所以只需要弄明白它的参数即可。

解释一下形参:

GPIOx形参:

这是一个指向 GPIO_TypeDef 结构体的指针,用于指定要初始化的 GPIO 端口。

上面已经解释过了,如果你想要初始化 GPIOC,那么这个参数可以直接传递 GPIOC

GPIO_Pin形参:

在上面的 GPIO_InitTypeDef 结构体中,我们见过同名的成员,实际上它们的意思也确实是一样的。

用于指出要操作的引脚编号,如果要操作的是13号引脚,可以直接传参 GPIO_Pin_13

BitVal形参:

这是一个BitAction类型的参数,用于指定向输出数据寄存器输出0或者1。这是一个枚举类型,可以在标准库头文件stm32f10x_gpio.h中找到此枚举类型定义,其类型定义如下():

很明显,它可以取两个值:

  1. Bit_RESET:向输出数据寄存器中写入0

  2. Bit_SET:向输出数据寄存器中写入1

利用该函数就可以完成对引脚的控制,当然选择不同的输出模式,最终引脚的电平表现是不同的。

编写代码,实现功能

Gn!

现在我们使用开漏输出模式,来实现点亮/熄灭PC13指示灯,参考代码如下:

编号3的两句代码,可以轮流注释掉,从而控制PC13指示灯的亮灭。

除此之外,我们还可以通过一个简单的思路,实现指示灯的闪烁:

  1. 点亮PC13指示灯

  2. 程序延时休眠一段时间,比如100ms

  3. 熄灭PC13指示灯

  4. 程序延时休眠一段时间,比如100ms

循环往复的执行上述结构,就能轻松实现闪烁了。

首先,为了实现程序延时功能,你还需要把"Delay模块"的源文件和头文件添加到工程中。你可以在"百度网盘 -- 函数模块 -- Delay函数模块"文件夹下找到这个源文件和头文件。

该模块提供了下面三个函数,大家可以直接使用:

Delay模块函数-图

首先你需要在工程目录下,新建一个"Tools"文件夹用来存放模块的文件,并且需要同步在Keil5软件中新建一个"Tools"组并将文件添加进去,最后你还需要将"Tools"添加到工程的头文件目录中去。

这些操作之前文档中都做过,这里不再赘述,若有疑问不妨直接互相问问或者问问老师。

添加完成后,不要忘记修改main.c文件,需要在main.c文件中包含头文件。

整体实现后的参考代码如下所示:

以上,一个最最基本的STM32实验就完成啦,恭喜你,你现在已经可以称得上入门了。

使用Astyle自动格式化代码(必做)

Gn!

在Keil5软件当中编写代码,由于Keil5软件本身没有自动格式化代码的功能,经常会碰到一个棘手的问题,

  1. 手动调整代码格式既繁琐又耗时,影响编码效率。

  2. 但如果无视代码缩进对齐格式,不仅影响可读性,还可能导致对代码结构的误判,例如大括号边界不清晰,容易引发逻辑错误。

总之,我们需要一个三方工具来帮助我们完成代码的自动格式化:也就是使用一个简单的快捷键,就可以自动格式化代码,对齐代码格式。

Astyle(Artistic Style) 是一个 代码自动格式化工具,用于美化和规范 C、C++、C# 和 Java 代码的缩进、对齐、括号风格等。它可以自动调整代码格式,提高可读性,减少因代码缩进混乱而导致的错误。

Astyle可以集成到Keil5软件当中,下面来讲解一下该软件的使用流程。

第一步: 下载Astyle

Gn!

大家可以在"百度网盘 -- 王道嵌入式软件环境 -- Astyle软件"当中找到Astyle软件的压缩包。

如果你已经下载过了,那就不需要重新下载了,如果还没有下载那就去百度网盘中下载一下。

百度网盘Astyle-图

通过网盘分享的文件:000_王道嵌入式软件环境

链接: https://pan.baidu.com/s/1PovrhzvoIKDGgIZ4ZxYETA?pwd=4cxz 提取码: 4cxz

注意下载完成后,要将它进行解压缩操作,比如解压缩到桌面,你将得到下面一个文件夹:

百度网盘Astyle-图2

第二步: 将Astyle文件夹放入Keil5安装目录下

Gn!

接下来,你需要将解压缩好的Astyle文件夹放入Keil5安装目录下,具体而言需要将这个文件夹放入安装时选择的Core位置。

如果在安装Keil5软件的时候如果你专门配置了这个Core位置,如下图所示:

Keil5软件的安装设置-图

那么你就找到这个Core位置,将文件夹剪切进去,如下图所示:

Keil5软件的安装位置-图

如果你实在找不到放到哪里,可以求助老师。

第三步:在Keil5软件中配置AStyle

Gn!

最后只需要在Keil5软件当中配置AStyle即可,操作步骤如下:

打开Keil5菜单栏的下列菜单:

在Keil5软件中配置AStyle-图1

打开后,在弹出的窗口进行下列配置:

在Keil5软件中配置AStyle-图2

最后一步,需要填入一系列格式化相关的指令,这些指令用于确定将代码最终格式化为什么样子。

大家可以直接参考我的配置:

直接将这一段复制粘贴到上面"Arguments"输入框中。

注:

这些指令的作用如下列表格所示:

参数作用
!EKeil5 传入当前编辑的文件路径,也就是格式化当前文件
--style=java采用 Java 的大括号风格(大括号独占一行)
--indent-col1-comments自动缩进注释格式,使其对齐代码块
--break-blocks在代码块之间添加空行
--pad-oper操作符(+-= 等)两侧加空格
--pad-comma逗号 , 后面加空格
--pad-headerif, for, while 关键字后加空格
--unpad-paren移除括号()内侧的空格,让括号更紧凑
--align-pointer=name对齐指针*,使其靠近变量名,而不是数据类型
--break-one-line-headersif, while, for 语句强制换行
--add-braces为单行 if, while, for 语句添加大括号 {}
--max-code-length=120代码行长度超过 120个字符 时换行

一般不需要修改上述这些指令,大家直接照抄即可,感兴趣且有闲余时间再考虑去研究研究这些指令。

完成上述配置后,就可以测试一下格式化效果了,使用方式如下:

在Keil5软件中配置AStyle-图3

注意点击的选项就是你刚刚新增栏目的名字,如果你的名字和我不一样,那么看到的选项名也不一样。

第四步:配置快捷键

Gn!

如果每一次格式化代码都需要点击选项,还是有点太慢了,太笨了。所以我们要给Astyle格式化代码,配置一个快捷键。

具体的操作流程如下:

配置快捷键的流程-图

我建议将这个快捷键配置为"F4",你也可以配置为你自己喜欢的快捷键。

注意事项

Gn!

在使用快捷键(比如"F4")格式化代码之前,推荐先使用快捷键"Ctrl + S"保存一下文件,然后再执行格式化操作。

如果修改代码后没有保存文件,直接使用快捷键格式化代码,有几率会格式化失败!

The End