目前 STM32 的开发方式有 3 个层次:

  1. 基于寄存器方式。极少的人使用这种形式。这种方式最底层、最直接、效率更高,但 STM32 寄存器十分复杂,一般不建议采取这种方式。
  2. 基于标准库(库函数)方式。使用 ST 官方提供封装好的函数,通过调用函数间接配置寄存器。这种方式对开发人员比较友好,有利于提高开发效率。
  3. 基于 HAL 库方式。HAL 库是 ST 公司目前主力推的开发方式,全称就是 Hardware Abstraction Layer(抽象印象层)。可以使用图形化界面快速配置 STM32,隐藏底层的逻辑。

这里介绍基于寄存器或标准库方式创建 STM32 工程(以 STM32F103C8T6 为例,其它型号大差不差),并通过点灯演示示例。基于 HAL 库方式请移步:STM32 工程的创建 - 基于 HAL 库

软件安装提示:阅读前请准备好 Keil uVision5 MDK 的安装(可以和 Keil C51 安在一起),软件需破解。安装和破解过程不再赘述。Keil 的一些使用技巧可以看这篇文章:Keil 的使用技巧与问题解决

LED 点灯实验工具准备:

  1. ST-LINK
  2. STM32(以 STM32F103C8T6 最小核心板为例)
  3. 杜邦线若干

固件库下载

进入官网: https://www.st.com/

右上角选择中文。根据菜单选择:工具与软件>嵌入式软件>STM32 微控制器软件。

image.png

选择标准外设软件库,选择 F1 系列。

image.png

点击获取最新版本进行下载。

image.png

解压后得到固件库文件夹:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
STM32F10x_StdPeriph_Lib_V3.6.0
│ Package_license.html
│ Package_license.md
│ Release_Notes.html # 发行说明
│ stm32f10x_stdperiph_lib_um.chm # 使用手册
├─Libraries
│ ├─CMSIS
│ │ ├─CM3 # Cortex-M3
│ │ │ ├─CoreSupport # 【C】 内核的寄存器描述文件
│ │ │ │ core_cm3.c
│ │ │ │ core_cm3.h
│ │ │ └─DeviceSupport
│ │ │ └─ST
│ │ │ └─STM32F10x
│ │ │ │ LICENSE.txt
│ │ │ │ Release_Notes.html
│ │ │ │ stm32f10x.h # 【B】STM32 外设寄存器描述文件。和51单片机REGX52.H的作用相似,描述寄存器和对应的地址
│ │ │ │ system_stm32f10x.c # 【B】用于配置时钟
│ │ │ │ system_stm32f10x.h # 【B】用于配置时钟
│ │ │ └─startup
│ │ │ ├─arm # 【A】STM32的启动文件
......
│ │ └─Documentation
│ └─STM32F10x_StdPeriph_Driver # stm32 标准外设驱动
│ │ LICENSE.txt
│ │ Release_Notes.html
│ │
│ ├─inc # 【E】库函数的头文件。
│ │ misc.h
......
│ │
│ └─src # 【D】库函数的源文件。除了misc.c,其余都是内核外的外设库函数
│ misc.c # 内核库函数。
......
├─Project # 官方提供的工程示例和模板
│ ├─STM32F10x_StdPeriph_Examples
│ └─STM32F10x_StdPeriph_Template
│ │ LICENSE.txt
│ │ main.c
│ │ Release_Notes.html
│ │ stm32f10x_conf.h # 【F】配置库函数头文件的包含关系
│ │ stm32f10x_it.c # 【F】存放中断函数
│ │ stm32f10x_it.h # 【F】存放中断函数
│ │ system_stm32f10x.c
├─Utilities # 官方评估板的相关例程
│ └─STM32_EVAL
└─_htmresc

注意上面带【A】【B】【C】【D】【E】【F】标号的文件位置,后面会有相应操作。

新建项目

打开 Keil,选择 Project>New uVision project。

  • 选择你要存放项目的文件夹(这个文件夹下有多个项目)
  • 新建一个文件夹(这个文件夹存放一个项目的所有东西,文件夹名称以后可以方便改)
  • 选择新的空文件夹进入,设置工程的名字(比如这里为 STM32-proj)。工程的名字可以设置为通用的名字(以后不太方便改)。

注意路径名称 不要有中文

新建工程后,选择 STM32F103C8:

image.png

我们可以把弹出的新建工程小助手给关掉,这里暂时不演示其的使用。

image.png

我们就得到以下文件结构:

image.png

库函数移植

接下来我们将新建多个文件夹以对新文件进行分类,每个人有不同的风格,我这里就演示 江科大 的风格。

在项目文件夹中新建 Start 文件夹,并按照以下要求进行操作:

  • 把固件库文件夹中【A】文件夹下的所有 .s 文件复制到这里。
  • 把固件库文件夹中【B】标识的三个文件也复制到这里。
  • 把固件库文件夹中【C】文件夹下的所有文件(2 个文件)复制到这里。

回到 Keil,现在将我们新建的 Start 添加到工程。

image.png

至于确定自己芯片型号的缩写,可看文末介绍。STM32F103C8T6 应选择后缀为 md 的启动文件。

同样的方法也将这几个也加进来,结果如下图:

image.png

上图中 Start 组里的文件我们是不需要进行修改的。(我们还可以在电脑文件管理器中将这些文件设置为只读,这样可以避免误修改。这时上图的文件图标上就会带有一把小钥匙,表示这是只读文件。)

接下来为工程添加头文件路径,不然编译的时候找不到头文件:

image.png

然后两次 OK,退出魔术棒弹窗。

main 函数

新建 User 文件夹(同样也是 江科大 的风格):

image.png

回到 Keil,新建组,并改名为 User。

image.png

在组里面新建文件:

image.png

image.png

进入 main.c,插入头文件:

image.png

main.c 内容如下:

1
2
3
4
5
6
7
8
9
#include "stm32f10x.h"                  // Device header

int main(void){
while(1){

}
}
// 最后一行为空行,不然编译后出现警告

选择编译并建立工程:

image.png

下面的提示栏显示 0 错误,0 警告,说明工程建立没问题:

image.png

基于寄存器开发 STM32 工程创建结束

进行到这里时并没有使用库函数。如果想用寄存器开发 STM32,那么新建一个工程到这里就结束了。可以继续阅读下面两节进行寄存器点灯操作。

接线与调试器配置

接线图:

image.png

注意,当你使用的引脚标号位置与上图不一致时,请以你使用的器件为准。

调试器配置:

image.png

继续点击 Setting,设置下载程序后立即复位运行,这样比较方便。否则下载之后都需要按一下复位按钮才能执行程序。

image.png

重新编译一下。

基于寄存器的 LED 点灯

本小节可略读。

接下来通过寄存器点亮连接 PC13(就在最小核心板上)的 LED 灯:

1
2
3
4
5
6
7
8
9
10
11
12
#include "stm32f10x.h"                  // Device header

int main(void){
// 基于手册配置寄存器
RCC->APB2ENR = 0x00000010;
GPIOC->CRH = 0x00300000;
GPIOC->ODR = 0x00000000; // 灭灯为 0x00002000
while(1){

}
}

编译后没有错误即可下载:

image.png

程序现象为最小核心板的 PC13 连接的 LED 灯点亮。

基于寄存器编程代码简洁,但需要不断查询手册,过程比较繁杂。且为了不影响寄存器中的其它位,还需要进行 |=&= 操作。

为工程添加库函数

新建 Library 文件夹(同样也是 江科大 的风格):

image.png

在项目文件夹中新建 Library 文件夹,并按照以下要求进行操作:

  • 把固件库文件夹中【D】文件夹下的所有 .c 文件复制到这里。
  • 把固件库文件夹中【E】文件夹下的所有 .h 文件复制到这里。

回到 Keil 软件,和 Start 文件夹的操作类似(往上翻复习一下)。添加组并命名 Library,添加已经存在的文件(Library 下所有的 .c.h 文件,注意文件类型筛选器选择 All Files)。结果为:image.png

Library 中的这些文件我们也不需要更改,可以把它们的权限设置为只读。

在项目文件夹中之前建立的 User 文件夹(存放 main.c 函数的文件夹)中:

  • 把固件库文件夹中标记【F】的 3 个文件复制到这里。

回到 Keil 软件,把这三个文件也添加到 User 组里。

image.png

我们的 main.c 中第一行中添加了头文件 #include "stm32f10x.h",将光标放在头文件名中,右键选择打开头文件。我们看到一个条件编译:

image.png

意思是如果定义了 USE_STDPERIPH_DRIVER(使用标准外设驱动)字符串,就引用 stm32f10x_conf.h。接下来打开魔术棒(工程选项),将字符串粘贴到这里:

image.png

这样才能包含标准外设库。

STM32F10X_MD 字符串 Keil 已经定义好,不需要我们手动定义。

最后别忘了把 User 和 Library 文件夹添加到头文件路径中:

image.png

顺便提一点,在这个按钮中我们可以根据喜好调整组的顺序或向组添加文件(比如把 Start 和 Library 这些不需要更改的文件往上移):

image.png

到这里,重新编译一下,检查是否有错。

其实所有的 .h 文件是可以不必加到组里的。编译一次后 Keil 中也能看到 .h 文件。这里我们还是沿用 江科大 的风格。

基于库函数的 LED 点灯

main.c 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "stm32f10x.h"                  // Device header

int main(void){
GPIO_InitTypeDef GPIO_InitStructure; // 声明放在前面以防报错(或使用C99 mode)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; // 推挽
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIO_InitStructure);
GPIO_ResetBits(GPIOC,GPIO_Pin_13); // 输出高电平
// GPIO_SetBits(GPIOC,GPIO_Pin_13); // 输出低电平
while(1){

}
}

编译并下载,观察输出高电平或低电平的实验现象。

至此,基于标准库开发 STM32 工程创建及示例介绍完毕。

如果 GPIO_InitTypeDef 放在 RCC_APB2PeriphClockCmd 之后,编译时有时候会产生这种错误:

编译错误

error: #268: declaration may not appear after executable statement in block

解决方法见:Keil 的使用技巧与问题解决

其它

移植 FreeRTOS

进一步,你可以尝试移植 FreeRTOS,详看:STM32 移植 FreeRTOS - 基于标准库

STM32 型号分类及缩写

缩写 释义 Flash 容量 型号
LD_VL 小容量产品超值系列 16~32K STM32F100
MD_VL 中容量产品超值系列 64~128K STM32F100
HD_VL 大容量产品超值系列 256~512K STM32F100
LD 小容量产品 16~32K STM32F101/102/103
MD 中容量产品 64~128K STM32F101/102/103
HD 大容量产品 256~512K STM32F101/102/103
XL 加大容量产品 大于 512K STM32F101/102/103
CL 互联型产品 - STM32F105/107

STM32F103C8T6 的 Flash 容量为 64K。

工程架构

我们可以再捋一遍我们建立的工程的架构:

image.png

startup_xx.s:程序执行最基本的文件,定义了中断向量表,中断服务函数等。复位中断为程序的入口。

本文参考