本次课程采用单片机型号为STM32F103C8T6。
课程链接:江科大自化协 STM32入门教程
由于新型号芯片的不断产生,需要下载器件支持包来更新Keil 5所支持的芯片型号。可以采用离线安装和在线安装两种方式。
软件注册采用注册机方式解决。
插入ST-LINK到Windows电脑。打开设备管理器,在“其他设备”中“STM32-Link”图标旁会带黄色感叹号。如果没有携带,说明电脑上已经存在ST-Link的驱动。在Keil 5的文件目录中就可以找到驱动安装文件。
除此之外,JLink也是一种常见的调试器。在文件目录中同样可以找到对应的驱动安装文件。
插入USB转串口模块到Windows电脑。打开设备管理器,在“其他设备”中找到“USB-SERIAL CH340”,并且如果图标旁边有感叹号,说明电脑没有安装驱动。
在Windows 11系统中可以在“端口”中找到“USB-SERIAL CH340”。
目前STM32的开发方式主要有基于寄存器的方式,基于标准库(库函数)的方式和基于HAL库的方式。
基于寄存器的方式是用程序直接配置寄存器,来达到想要的功能。这种方式最底层,最直接,效率会更高一些。但是由于STM32的寄存器复杂,寄存器太多,所以不推荐这种方式。
基于库函数的方式是使用ST官方提供的封装好的函数,通过这些函数来间接地配置寄存器。由于ST对寄存器封装的比较好,所以这种方式既能满足对寄存器的配置,对开发人员也比较友好,有利于提高开发效率。本课程采用基于库函数的开发方式。
基于HAL库的方式可以用图形化界面快速配置STM32, 但是这种方法隐藏了底层逻辑。推荐学习过标准库的开发方式之后,对这一种开发方式进行了解和学习。
配置STM32的启动文件(STM32的程序从启动文件开始执行)
打开“固件库”文件夹,打开Libraries → \rightarrow → CMSIS → \rightarrow → CM3 → \rightarrow → DeviceSupport → \rightarrow → ST → \rightarrow → STM32F10x → \rightarrow → startup → \rightarrow → arm,将文件夹中的文件复制到新建的Start文件夹中。
将启动文件导入到Keil的工程中时,启动文件只能导入一个。课程所用型号需要选择startup_stm32f10x_md.s
文件。
启动文件的选择
选择启动文件时,需要考量芯片的两个指标:Flash容量、型号。
根据上表,如果使用STM32F100的型号,就选择启动文件后缀为vl
的启动文件,根据Flash容量选择后缀为ld_vl
,md_vl
,hd_vl
的文件,表中其他型号同理。
回到STM32F10x文件夹,将stm32f10x.h
、system_stm32f10x.c
、system_stm32f10x.h
复制到Start文件夹中,并导入到工程组Start中(stm32f10x.h
就是STM32外设寄存器描述文件,它的作用就同51单片机的头文件REGX52.H
一样,是用来描述STM32有哪些寄存器和它对应的地址的)。
配置内核寄存器的描述文件
core_cm3.h
、core_cm3.c
就是内核的寄存器描述文件(其中还包含了一些内核的配置函数),同样将它们复制到Start文件夹中,并导入到工程组Start中。配置User文件夹
main.c
文件(注意:添加main.c文件时的路径要选择User文件夹,否则默认放在文件夹外),这时对main.c
文件进行编译检查,如果没有报错和警告说明工程建立成功。 至此,基于寄存器开发的工程就建立完成了。stm32f10x_conf.h
是用来配置库函数头文件的包含关系的,其中还包含用来参数检查的函数定义,是所有库函数都需要的。此外stm32f10x_it.h
,stm32f10x_it.c
是用来存放中断函数的。将这三个文件复制下来,粘贴到工程的User文件夹中配置库函数Library文件夹
misc.c
是内核的库函数,其他都是内核外的外设库函数。将其中所有文件复制到Library文件夹中。注意事项
需要用的文件一定要复制到工程的文件夹里面,不要添加工程文件夹外面的文件,否则外面的文件一旦挪动位置,工程里就找不到文件了。所以我们要将用的文件复制到工程中,以保证工程的独立性。
stm32f10x.h
中(8296行)#ifdef USE_STDPERIPH_DRIVER
#include "stm32f10x_conf.h"
#endif
其他工程在这个位置还声明了 STM32F10X_MD的字符串,但是Keil5在新建工程之后会自动声明该字符串,不需要再额外声明。
工程的建立非常灵活,学会建立工程后可以依照自己的风格建立工程。
根据上图,我们可以了解STM32的工程架构(总结)如下所示:
首先是主动执行的文件:
startup_xx.s
启动文件
StstemInit
函数和main
函数。StstemInit
函数定义在system_xx.c/.h
文件中,这个函数的作用是设置微控制器的启动,初始化嵌入式闪存接口,锁相环,更新系统内核的时钟变量。这个函数仅在复位后需要调用。stm32f10x_it.c/.h
中。在这两个文件最后还可以定义用户自己写的中断函数。当然,写在别的地方也可以。system_xx.c/.h
文件:定义SystemInit
函数main.c
文件:定义main
函数stm32f10x_it.c/.h
文件:定义中断函数 下面是被动执行的文件,相当于STM32的资源:
stm32f10x.h
文件:外设寄存器描述
core_cm3.c/.h
文件:内核寄存器描述misc.c/.h
,stm32f10x_adc.c/.h
…文件:库函数stm32f10x_conf.h
文件:库函数配置文件
stm32f10x.h
中又include了stm32f10x_conf.h
。所以,在使用这些库函数时,只需要包含stm32f10x.h
这一个头文件,就相当于包含了所有的头文件 下面对开发板上的LED PC13 进行点灯操作。(通过查询数据手册寻找对应寄存器直接赋值。)
#include "stm32f10x.h" // Device header
int main(void)
{
RCC->APB2ENR = 0x00000010;
GPIOC->CRH = 0x00300000;
GPIOC->ODR = 0x00000000; // 灯亮
// GPIOC->ODR = 0x00002000; // 灯灭
while (1)
{
}
}
这种操作方式需要不断地查手册来了解每个寄存器的每一位的作用;且弊端为我们把除了PC13之外的位都配置为0,这会影响到其他端口的原有配置。如果想单独配置PC13位,需要使用 &=和 |= 的操作,太过麻烦,对开发人员不友好。
需要注意,编程完成后最后需要多空一行,否则程序会报出一个警告。
使用库函数配置寄存器虽然代码较为复杂,但是逻辑清晰,易于操作。下面对开发板上的LED PC13 进行点灯操作,初步体验GPIO的使用方法。在下一章会对GPIO作详细介绍。
#include "stm32f10x.h" // Device header
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
// RCC_APB2外设时钟控制命令,用来开启时钟,RCC_APB2Periph_GPIOC是APB2的一个外设
// 在这个函数内部一样采用了&=和|=操作来配置RCC->APB2ENR寄存器,但是经过函数包装,我们不再需要去查手册来确认寄存器内每一位的功能
// 下面配置GPIO_Init函数第二个参数需要的结构体
GPIO_InitTypeDef GPIO_InitStructure; // 定义一个结构体
// 下面是结构体的三个参数
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // GPIO模式,GPIO_Mode_Out_PP为通用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //GPIO端口
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // GPIO速度
// 下面配置端口模式
GPIO_Init(GPIOC, &GPIO_InitStructure);
// GPIO_SetBits(GPIOC, GPIO_Pin_13); // 把指定端口设置为高电平,灯灭
GPIO_ResetBits(GPIOC, GPIO_Pin_13); // 把指定端口设置为低电平,灯亮
while (1)
{
}
}
在配置结构体GPIO_InitStructure
的三个参数时,需要手动查找定义的位置。以GPIO_InitStructure.GPIO_Mode
为例,右键跳转到定义,选中GPIOMode_TypeDef
,按下Ctrl+F,点击Find next即可找到GPIOMode_TypeDef
的定义。可以看到它是一个枚举,而GPIO_InitStructure.GPIO_Mode
应该赋的值就是这里对应的一个值(根据实际情况而定)。
GPIO,全称为General Purpose Input Output,意为通用输入输出,可配置八种输入输出模式。引脚电平: 0 V − 3.3 V 0V-3.3V 0V−3.3V( V O L = 0 V , V O H = 3.3 V V_{OL}=0V, V_{OH}=3.3V VOL=0V,VOH=3.3V),部分引脚可以容忍 5 V 5V 5V(容忍的意思是可以在这个端口输入 5 V 5V 5V 的电压,也认为是高电平,具体哪些端口可以容忍 5 V 5V 5V 需要查找STM32的引脚定义。在引脚定义中带FT,意为Five Tolerate,就是可以输入 5 V 5V 5V 的端口)。
GPIO在输出模式下可控制端口输出高低电平,用以驱动LED,控制蜂鸣器,模拟通信协议输出时序等。如果控制的是功率比较大的设备,只需要再加入驱动电路即可。
GPIO在输入模式下可读取端口的高低电平(电压),用于读取按键输入,外界模块的(电平)信号输入,ADC电压采集,模拟通信协议接收数据等。
在所有的STM32中,所有的GPIO外设都挂载在APB2外设总线上。其中GPIO外设是按照GPIOA、GPIOB、GPIOC等命名的。
每个GPIO外设有16个引脚。以GPIOA为例,它的引脚编号分别为PA0,PA1,PA2 … PA15。其他GPIO的引脚也是这样命名的。
在GPIO模块内部,主要包含了寄存器和驱动器两个模块。寄存器就是一段特殊的存储器,内核可以通过APB2总线对寄存器进行读写,以完成输出电平和读取电平的功能。
STM32是32位的单片机,其中的寄存器都是32位的。而端口只有16个,故寄存器只有低16位有对应的端口,高16位没有用到。
驱动器的作用是增加信号的驱动能力。寄存器只负责存储数据,需要用驱动器来增大驱动能力。
GPIO每一位具体的电路结构如上图所示。整体结构可以分为图上部的输入部分和图下部的输出部分。
在STM32中,操作寄存器的某一位还可以操作 “位带” 区域,类似于51单片机中的位寻址。在STM32中,专门分配有一段地址区域,这段地址映射了RAM和外设寄存器所有的位。读写这段地址中的数据,就相当于读写所映射位置的某一位。在本课程中不会用到该种操作方式。
通过对GPIO的端口配置寄存器进行操作,即配置弱上拉/下拉电阻开关的通断,施密特触发器是否有效,N-MOS和P-MOS是否有效,数据选择器的选择等,端口可以被配置为以下八种模式:
下面是各种输入模式的等效电路图:
持续更新中……