目录:
新建工程
新建工程
新建32 的工程,这个 STM32 的工程结构还是比较复杂的,需要用到很多文件。之后我们的代码也都是需要建立在工程结构上的,所以在开始学习后续内容之前,我们先来学习一下如何新建 STM32 的工程。
目前 STM32 的开发方式主要有基于寄存器的方式、基于标准库也就是库函数的方式和基于 HAL 库的方式。
基于寄存器的方式和我们 51 单片机的开发方式一样,是用程序直接配寄存器来达到我们想要的功能。这种方式最底层,最直接,效率会更高一些。但是由于 STM32 的结构复杂,寄存器太多,所以基于寄存器的方式目前是不推荐的。
基于库函数的方式是使用 ST 官方提供的封装好的函数,通过调用这些函数来间接的配置计存器。由于 ST 对寄存器封装的比较好,所以这种方式既能满足对寄存器的配置,对开发人员也比较友好啊,有利于提高开发效率。我们本课程使用的就是库函数的开发方式。
最后一个基于 HAL 库的方式,可以用图形化界面快速配置 STM32。这个比较适合快速上手 STM32 的情况。但是这种方式隐藏了底层逻辑,如果你对 STM32 不熟悉,基本只能停留在很浅的水平啊。所以目前暂时不推荐 HAL 库,但是推荐你学过标准库之后去了解一下这个方式,毕竟这个 HAL 库还是非常方便的。
那使用库函数的方式,我们需要准备一个 STM32 库函数的压缩包。大家可以在我提供的资料链接里找到固件库的文件,然后可以看到 STM32F10X 标准外设库这个压缩包

我们先把它解压。打开解压后的文件夹,在这里就是库函数的文件夹目录了。

第一个文件夹里是两个图片,这个没啥用啊。第二个 libraries 里面就是库函数的文件了,我们之后建工程时会用到。第三个 project 是官方提供的工程示例和模板,以后使用库函数的时候可以参考一下。第四个 utilities 是 STM32 官方评估版的相关例程,这个评估版就是官方用 STM32 做的一个小电路板。用来测评 STM32 的。这个文件夹里面存的就是这个小电路板的测评程序。
接下来后面两个文件,一个是库函数的发布文档,一个是使用手册。发布文档里有一些版本的说明哈,使用手册里有教怎么使用这个库函数的。好那接下来我们就正式开始新建一个基于标准库的工程。
新建一个目录,以后所有的工程都放到这个目录下



工程名称

器件型号:我们的芯片是STM32F103C8T6



下面添加一些工程的必要文件:
打开固件库的文件夹:

这些就是STM32的启动文件,stm32的程序就是从启动文件开始执行的
D:\BaiduNetdiskDownload\STM32入门教程资料\固件库\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm

把这些文件放到刚创建好的目录里,新建一个Start文件夹:


固件库的 STM32F10X 文件夹:

可以看到 STM32F10X . H 和两个 System 开头的文件。这个 STM32F10X 点.H 就是 STM32 的外设寄存器描述文件。它的作用就跟 51 单片机的头文件 REGX52 . H 一样,是用来描述 STM32 有哪些寄存器和它对应的地址的。
这两个 System 文件主要是用来配置时钟的啊, STM32 主频 72 兆赫兹就是 System 文件里的函数配置的。那我们把这三个文件复制下来,也粘贴到 start 文件夹下。

接下来因为这个 STM32 是内核和内核外围的设备组成的,而且这个内核的寄存器描述和外围设备的描述文件不是在一起的,所以我们还需要添加一个内核寄存器的描述文件。我们可以打开 CM3 Core Support

这两个 CM3 文件就是内核的寄存器描述。当然它还带了一些内核的配置函数啊,所以多了个点 c 文件,我们把它俩一并复制下来,也粘贴到 start 文件夹下。

到此为止我们工程的必要文件就复制完成了。然后我们回到 Keil 软件,把我们刚才复制的那些文件添加到工程里来。


接着右键选择添加已经存在的文件到组里来。打开 start 文件夹,把下面这个文件过滤器选择 All files,这样我们就能看到文件夹里的所有文件了。
我们首先添加一下启动文件,这个启动文件有很多分类,我们只能添加其中一个。我们视频所用型号需要选择这个后缀为 md.s 的启动文件。至于启动文件这个怎么选择,我等会再讲啊。我们选中它,点击 Add。

然后剩下的点 c 和点 h 文件都要添加进来,我们可以按住 CTRL 键,然后依次选择它们,点击 Add, 然后 close。 这样我们 start 文件夹里面的文件就添加好了,然后 close


这里的文件都是 STM32 里最基本的文件,是不需要我们修改的,我们添加进来即可。那大家可以看到这个文件图标上带了个小钥匙啊,这个意思是它是个只读文件。我们可以打开试一下,这些信息都是不让我们修改的哈。最后我们还需要在工程选项里添加上这个文件夹的头文件路径,要不然软件是找不到点 h 文件的。
我们点击这个魔术棒按钮,打开工程选项,在 C C 加加里找到这这个 include paths 栏,然后点击右边三个点的按钮:

在这里新建路径,然后再点三个点的按钮,把 start 的路径添加进来。点击 OK, 这样就把这个文件夹的头文件路径添加进来了。



接下来我们再新建一个 main 函数,看看这个工程是不是可行啊。我们打开工程文件夹,然后新建一个文件夹,叫做 user,我们的 main 函数就放在这个文件夹里。

然后 Keil 里在 Target 这里右键,点击添加组。


改个名字啊叫 USER。 然后在 USER 上右键,点击添加新文件,选择 C 文件,名字叫 main。 下面的路径注意一下啊,要选择 USER 文件夹,要不然默认是放在文件夹外面的。




然后点击 Add, 这样我们就有了 main .c 文件了。那在工程文件表的 User 目录下也可以看到我们新建的 main .c 文件。


在这个 main . c 里我们先右键插入头文件,选择 STM32F10X 点 h。
这里注意,Main函数是一个int型返回值void参数的函数,还有文件的最后一行必须要是空行,要不然会报警告的。然后我们点击这个按钮编译并建立工程

可以看到下面提示的是 0 错误 0 警告哈。那这就说明我们建立的工程是没问题的。这个工程目前还没有添加 STM32 的库函数,所以它还是一个基于寄存器开发的工程。如果你想用寄存器开发 STM32,那工程建到这里就可以了。接下来我给大家演示一下如何通过配置寄存器来完成点灯的操作。当然直接操作寄存器的方式不是我们本课程的重点,大家了解一下即可。

拿出 STM32 的最小系统板、 STLink 和 4 根母对母的杜邦线。按照插针边上的标识符号把 3.3 伏、 SWDIO、 SWCLK、 GND 对应连接好。插好之后就是这个样子的。然后把 STLink 插在电脑上。插上电之后这个板子上的电源灯应该会常亮啊,另一个连接在 PC13 口上的灯默认应该是闪烁状态,这是芯片里的一个测试程序啊。

然后我们再在 Keil 里面配置一下调试器,点击魔术棒按钮,选择 Debug,这个调试器默认是 UniLink 啊,我们用的是 STLink,所以选择 STLink Debugger,然后再点击右边的设置按钮,在 Flash 下载这一项把这个 Reset and Run 这一项勾上


勾上这一项之后,我们下载程序后会立马复位并执行啊,这样方便一些,否则每次下载之后还需要按一下板子上的复位按键才能执行程序,那配置好调器之后,点击确认 OK,

然后重新编译一下没有错吧,再点击这个 load 按钮,如果一切正常的话,这个程序就会下载到 STM32 里面了,我们看一下板子,可以看到这个灯已经不闪了,

因为我们目前的程序啥都没有,那接下来我们就配置一下寄存器来点亮这个灯,我们只需要配置 3 个寄存器就可以点灯了
我们可以打开 STM32 的参考手册,首先是RCC 的一个寄存器来使能 GPIOC 的时钟,GPIO 都是 APB2 的外设,所以在这个 APB2 外设时钟使能寄存器 RCC APB2 ENR 里面配置,可以看到这里有个 IOPCEN,这一位就是使能 GPIOC 的时钟的:



下面的解释是,这一位写一就是打开 GPIOC 的时钟,那这一位写一,其他的无关项我们先都给 0 哈,那整个寄存器的二进制数据换成十六进制就是 4 个一分组,也就是 0000 0010
然后我们回到 Keil,在 while 死循环之前写上 RCC 的 AHB2ENR 寄存器等于 0x0000 0010,这样就可以打开 GPIOC 的时钟了

然后第二个寄存器我们需要配置一下 PC13 口的模式,我们可以找到端口配置高寄存器 GPIOX 杠 CRH 啊,这个 X 可以是 A 到 G 的任意一个字母,然后右边的 CNF13 和 MOD13 就是用来配置 13 号口的


下面的说明我们看一下,这个 CNF 我们需要配置为通用推挽输出模式,也就是这两位为 00,MOD 要配置为输出模式,最大速度可以给 50 兆赫兹,也就是这两位为 11。
最后我们对照上面的寄存器,这四位为 0011,其他的我们也都给它配置为 0,这样整个寄存器的值换算成十六进制就是 00300000。

然后我们回到 Keil,在这里写上 GPIO C 的 CRH 等于 0X 00300000

接下来我们就可以给 PC13 口输出数据了,我们可以看到这个端口输出数据寄存器 GPIO X 杠 ODR 中间有一位 ODR13,这一位写一 13 号口就是高电平,写 0 就是低电平

如果写一的话,ODR 的值就是 0000 2000,那在这里我们写上 GPIO C 的 ODR 等于 0X 00002000,因为这个灯是低电平点亮的,所以我们给 ODR 全为 0 就是亮,给 ODR 0X 00002000 就是灭。

那我们试一下先给 ODR 全 0 编译下载,这时可以看到这个 PC13 的灯已经亮起来了。




上面就是配置寄存器的的方式进行点灯对的操作,可以看出来,这种方式需要不断的查手册来了解每个寄存器的每一位都是干啥的。而且这个操作方式也有个弊端,就是我们把除了PC 13之外的位都配置成了零,这样会影响到其他端口的原有配置,如果要做到只配置这个PC13而不影响其他位,那还需要 &=和|= 的操作,这个在51单片机的视频里我们也经常遇到,那就会更加麻烦。所以这种寄存器的操作方式虽然代码简洁,但是还是不太方便。那接下来我们就要为这个过程添加库函数了,不函数和寄存器的操作方式有哪些区别
新建一个Library文件夹:


这个文件夹是库函数文件:misc是内核的库函数,其他的是内核外的外设库库函数

复制到Library:

inc文件夹:这些事库函数的头文件也复制到Library







再添加:
这个 CONF 文件是用来配置库函数头文件的包含关系的。另外这里面还有一个用来参数检查的函数定义哈,这是所有库函数都需要的。两个 IT 文件是用来存放中断函数的。我们把这三个文件复制下来,然后粘贴到工程的 USER 目录下。





最后还需要一个宏定义啊,我们可以在这个头文件右键打开文件,然后滑到最下面啊,看到这个语句,这是一个条件编译啊:

意思是如果你定义了使用标准外设驱动这个字符串,下面这个 include conf 点.h 语句才有效,所以我们还需要复制一下这个字符串。

然后打开工程选项,在 C C 加加的预处理栏目粘贴这个字符串。这样才能包含标准外设库,也就是库函数。
当然还有下面的头文件路径也不要忘了,把这个 USER 和 LIBRARY 目录的路径也都添加上,然后 OK。


这样我们基于库函数的工程就建好了。我们可以看一下,这个 library 里面的库函数也都带了钥匙,不需要我们更改。我们唯一需要更改的就是 user 组里面的这些文件。
那我们点一下这个三个箱子的按钮,把这个 library 往上挪一下,把这些不用改的都放到最上面,这样看着舒服一些。



我们再用库函数来实现点灯的操作。我们先把这三句删了。

那库函数其实也是间接的配置寄存器,所以它们的步骤也是一样的。首先是使能时钟,那库函数就有这样一个函数来开启时钟了,叫 RCC_ APB2 外设时钟控制。然后这里提示有两个参数,第一个是选择外设,第二个是选择新的状态。我们可以右键跳到函数定义,这上面有函数的简介和参数说明。

简介说这个函数是用来使能或者失能 APB2 的外设时钟,第一个参数可以是下面这些值,那我们可以找到 APB2 外设 GPIOC 这一项,然后复制。直接作为第一个参数即可。然后我们再回过去看第二个参数, new state 的值可以是 enable 或者 disable,那我们复制 enable 放在第二个参数的位置。最后别忘了括号和分号啊,这样 gpio c 的外设始终就配置好了。

我们可以看一下这个函数,它的内部其实还是配置 rcc apb2 enr 这个寄存器啊,但是经过函数的包装,我们不需要再去查手册来确认哪一位是干啥的。而且这里它已经帮我们用或等于和与等于来操作了,所以这个库函数的配置是不会影响到寄存器的其他位的。这就是库函数和寄存器的区别。那我们看到这个代码虽然比寄存器长啊,但是语义更加明确。也不需要我们再查表计算这个寄存器的值了,我们只需要调用库函数,按照它的提示把参数填好就行了。所以说从这点对比上来看,库函数是比寄存器有更大优势的。

好那我们继续来配置,第二步是配置端口模式,我们需要用到 GPIO_ Init 这个函数。然后有两个参数,第一个是选择哪个 GPIO, 第二个是参数的结构体哈。这个比上一个函数要麻烦一些,但也是一个套路,我们根据提示来配置参数即可。那我这里来操作一下,这里使用了结构体来配置参数,代码逻辑还是有些复杂的,这个我们下节还会继续讲,大家先跟着我操作就行了。我们首先还是去到这个函数的定义,可以看到这个函数的介绍是根据 GPIO_ Init 的结构体的参数来配置 GPIO,第一个参数 GPIOx 其中 X 是可以是 A 到 G 来选择你要配置哪个 GPIO。那我们是 PC13 口的 LED,所以第一个参数就写 GPIO C。第二个参数是一个 GPIO 一例的 TypeDef 的结构体,我们需要先定义一个结构体。那在上面我们先把这个结构体的类型写上。然后给结构体起个名字,这个名字可以随便起哈,但是根据官方的推荐,我们最好起这样的名字,叫 GPIO_ InitStruct。 然后我们把结构体的每一个参数填上,复制粘贴结构体的名字,然后用点来引出结构体的参数,可以看到这个结构体有三个参数,分别是 GPIO 模式, GPIO 端口, GPIO 速度啊。那我们先把这三个参数都罗列出来。最后也还是一样

右键转到 mode 的定义。这右边的介绍说这个参数可以是 GPIOMode,typedef 里面的一个值。因为这是注释里面的东西啊,所以没办法用右键跳转了。那这里我们需要选中这个字符,按一下 CTRL + F,搜索一下这个定义的位置。



点击 Find Next 的,可以看到这是个枚举啊, GPIOMode 就是这里的其中一个值。然后我们选择 OUT_ PP 这一项,复制。这个就是通用推挽输出。然后在这里写上 GPIO Mode OUT_ PP 这样这个参数就配置好了。

然后我们继续看一下下一个参数,转到它的定义,这里下面出现了一个框啊,这个是说它的定义有很多个,我们来看一下,我们选择这个 member 这一项,双击,然后跳转的其实还是刚才那个位置啊。

然后看一下这个 GPIO Pin 的说明,他说这个参数在 GPIO Pin 是 define 里面定义的,我们还是一样,选中, Ctrl + F, Find Next 可以看到这里有个宏定义的列表



我们选择 GPIO_ Pin_ 13,复制,然后填在第二个位置。
第三个参数也是一样,右键,跳到定义,选中, Ctrl + F, Find Next。


在这里选中 50 兆赫兹的速度,复制,在这里粘贴,最后别忘了分号啊。那结构体变量就有了,我们就可以填这个 GPIO_ InitTypeDef 的第二个参数了。
那在这里有说明啊,这个第二个参数是一个指向结构体的指针,所以这里我们需要传递结构体的地址啊。那我们复制结构体的名字,粘贴到这个位置,然后前面加上一个取地址的符号啊,最后打上右括号分号,这个 GPIO 模式配置就完成了。

这个配置的操作方式看上去比较难理解啊,但是 STM32 的这种方式都是固定的,大家多打几次就知道怎么用了。
最后我们设置端口的高级电平来进行点灯吧。这里有个函数gpio_setBits 这个就可以把指定端口设置为高电平。大家也可以右键去看一下参数的说明哈。那这里我就直接填了,第一个是 GPIO C, 第二个是 GPIO 杠 Pin 杠 13。这一句就可以将 PC13 号口置为高电平。

接下来置低电平也有函数,叫 GPIO 杠 Reset Bit。 参数同样是 GPIO C GPIO 杠 Pin 杠 13 这一句就可以将 PC13 号口置为低电平。

那我们依次试一下啊,我们先把高电平的注释掉,编译。





新建工程里的启动文件选择,我们新建工程第一个加的就是启动文件。这个启动文件有很多类型,至于选择哪一个,我们要根据芯片型号来选择。那我们看到这个表,这里是 STM32F1 系列中的型号分类
其中根据 Flash 的大小分为了小容量产品, Flash 为 16~32K, 简写为 LD 啊。中容量产品, Flash 为 16~128K, 简写为 MD。 大容量产品, Flash 为 256~512K, 简写为为 HD 加大容量产品 Flash 大于 512K 简写为 XL。
那 STM32F100 的系列呢? ST 把它叫做超值系列,简写为 VL。
STM32F105 和 107, ST 把它叫做互联型产品,这个就没有根据 Flash 大小再分类了,所以就有了这个表。
如果你使用 STM32F100 的型号,就选择带 VL 的启动文件,然后再根据 Flash 的大小选择 LD、 MD 还是 HD。如果你使用 STM32F100、 102、 103 的型号,就选择不带 VL 的,然后根据 Flash 的大小选择 LD、 MD、 HD 还是 XL。如果你使用 STM32F105、 107 的型号,直接选择 CL 的启动文件即可。
那在这里我们可以看一下哈,这个启动文件后面带的字母和我这个表的字母都是对应的。这样我们就知道了,我们这个 STM32F103 的芯片,就需要选择这四列。

又因为 C8T6 的 Flash 是 64K, 所以选择 MD 的启动文件。这就是 STM32F1 系列的型号分类和启动文件的选取哈。接下来我们再总结一下新建工程的步骤。

第三步是工程里对应建立 Start、 Library、 User 等同名称的分组,然后将文件夹里的文件添加到工程分组里。这一步的原因是在 Keil 里方便管理文件啊,因为 Keil 没法直接添加文件夹,所以还得重复一下,在 Keil 里建立分组,然后添加文件。这个添加文件我是把所有的点 h 文件和点 c 文件都添加进来了,因为点 h 文件是不参与编译的,所以其他很多工程都是不添加点 h 文件的。但是我认为把点 h 文件加进来比较方便,而且点 h 文件也是需要经常打开看的,所以我比较习惯把所有的文件都添加进来。
第四步是工程选项, C、 C++、 Include、 Paths 类,声明所有包含头文件的文件夹。这一步是因为你这个 start、 library 等文件夹是你自己建的, Keil 软件它并不知道,所以你要用自己文件夹里面的点 h 文件,就必须声明一下这个路径。那最好就是你自己建的所有文件夹都声明一下哈,这样就不会出现点 h 文件找不到的问题了。
第五步是工程选项 C C++,以 define 内定义使用标准外设驱动这个字符串。这是使用库函数的条件编译哈,使用库函数就必须定义这个。另外其他的工程在这个位置还声明了一个 STM32F103 是杠 MT 的字符串。那根据我的调查, Keil 在新建工程后自动就帮我们声明好了这个,不需要再额外声明了。所以在这个位置只需要声明使用标准外设驱动的字符串即可。
第六步是工程选项, Debug 下拉列表选择对应调试器。 Setting 是 Flash download 这里,勾选 Reset and Run。 这个就是选择调试器来进行下载的选项了,我们用 STLink 就选择 STLink 的那一项即可。

来看一下这个工程的每个文件都是干啥的,为啥需要这些文件。那我们看到这个图:
首先是 start up 启动文件,这个是程序执行最基本的文件。我们可以看到 keil 中启动文件是用汇编写的,启动文件内定义了中断向量表、中断服务函数等。那这个中断服务函数中有个复位中断,这就是整个程序的入口啊。当 STM32 上电复位或者按下复位按键之后,程序就会进入复中断函数执行。复位中断函数主要就做了两件事情,第一个是调用 SystemInit 函数,第二个是调用 main 函数,对应启动文件的这里:

可以看到这是复位的中断函数,然后调用 SystemInit,再调用 main,然后程序就结束了。当然实际上单片机的程序永远也不会结束,所以在 main 函数的最后一定是一个死循环。

那 SystemInit 函数就是定义在这个 System 开头的.c 文件里的。那在 Keil 里我们也可以看到这个函数的定义。
这里的简介写了这个函数的作用啊,是设置微控制器的启动,初始化嵌入式闪存接口,锁相环,更新系统内核的时钟变量。下面的 note 写的是这个函数仅在复位后需要调用。那下面这些就是用来配置这些东西的,这个也不需要我们更改,我们只需要知道在 main 函数之前,单片机就已经执行了一堆东西了,帮我们把这个闪存接口时钟等一系列杂碎的东西都配置好了。
另外在启动文件还定义了 STM32 所有的其他中断啊,这些中断达到触发条件后就会自动执行。那在启动文件这下面,这都是其他的中断调用了。这个中断函数的定义就是在 STM32F10X_ IT 里面的,我们打开 Keil 可以看到,这些就是中断函数的定义。


那最后 ST 还建议我们把自己的中断写在这个位置上,当然我们还是习惯在哪用中断就写在哪里了,写在别的地方也是可以的。那这些就是中断部分的执行逻辑了。
另外你也可以自己定义一些用户文件,来分装一些模块,供主函数和中断调用。这些也都是没问题的啊,同时也有利于我们程序结构的模块化。要不然所有的程序都堆在主函数里,那主函数也太长了是吧?到此为止,这个工程结构主动执行的部分就介绍完了
剩下右边的就是被动执行的东西了,相当于 STM32 的资源,我们在主函数或者中断函数里就可以调用这些资源了。右上角这两个 F103 . h 和 core - cm3 这些文件就是外设和内核外设的寄存器描述。在 Keil 中我们可以看到这里面都是寄存器和寄存器每一位的名字啊,还有地址信息等。如果直接调用这些寄存器来使用 STM32,那就是寄存器的开发方式。我们已经试过了这种方式会有一些弊端,也比较麻烦。所以 ST 公司就提供了下面这两个文件,这个就是库函数文件,在 Keil 中可以看到。这每个外设都提供了一大堆的函数,这些函数分装了寄存器的操作,给我们提供更加人性化的函数调用方式。
只要学会了操作套路,那配置一个外设就是很简单的,连手册都不需要看一眼了。这个 conf 文件就是用来配置头文件的包含关系的。我们可以看到在这里, conf 文件 include 了所有的库函数头文件。同时我们在 STM32F10X . h 的最后又包含了 conf,所以在使用这些库函数时,我们只需要包含 STM32F10X .h 这一个头文件,就相当于包含了所有的库函数头文件。这样我们就可以任意的调用库函数了。那这些就是整个工程的结构和每个文件的作用了。