STM32学习笔记(持续更新)丨学习心得丨代码实现

STM32学习笔记

​  本次课程采用单片机型号为STM32F103C8T6。
​  课程链接:江科大自化协 STM32入门教程


一、软件下载注册、驱动安装

1.1 软件(器件支持包)下载

​  由于新型号芯片的不断产生,需要下载器件支持包来更新Keil 5所支持的芯片型号。可以采用离线安装在线安装两种方式。

​  软件注册采用注册机方式解决。

1.2 ST-LINK驱动安装

​  插入ST-LINK到Windows电脑。打开设备管理器,在“其他设备”中“STM32-Link”图标旁会带黄色感叹号。如果没有携带,说明电脑上已经存在ST-Link的驱动。在Keil 5的文件目录中就可以找到驱动安装文件。

​  除此之外,JLink也是一种常见的调试器。在文件目录中同样可以找到对应的驱动安装文件。

1.3 USB转串口驱动安装

​  插入USB转串口模块到Windows电脑。打开设备管理器,在“其他设备”中找到“USB-SERIAL CH340”,并且如果图标旁边有感叹号,说明电脑没有安装驱动。

​  在Windows 11系统中可以在“端口”中找到“USB-SERIAL CH340”。


二、工程的建立

2.1 STM32的开发方式

​  目前STM32的开发方式主要有基于寄存器的方式,基于标准库(库函数)的方式和基于HAL库的方式。

​  基于寄存器的方式是用程序直接配置寄存器,来达到想要的功能。这种方式最底层,最直接,效率会更高一些。但是由于STM32的寄存器复杂,寄存器太多,所以不推荐这种方式。

​  基于库函数的方式是使用ST官方提供的封装好的函数,通过这些函数来间接地配置寄存器。由于ST对寄存器封装的比较好,所以这种方式既能满足对寄存器的配置,对开发人员也比较友好,有利于提高开发效率。本课程采用基于库函数的开发方式

​  基于HAL库的方式可以用图形化界面快速配置STM32, 但是这种方法隐藏了底层逻辑。推荐学习过标准库的开发方式之后,对这一种开发方式进行了解和学习。

2.2 新建工程的步骤

  1. 建立工程文件夹,在Keil中新建工程(工程文件名称为Project,存放在一个文件夹下,对该工程文件的说明写在文件夹上),选择芯片型号(本次课程选用STM32F103C8T6,故选择STM32F103C8);
  2. 工程文件夹里建立StartLibraryUser等文件夹,复制固件库中的文件到工程文件夹;
  • 配置STM32的启动文件(STM32的程序从启动文件开始执行)

    • 打开“固件库”文件夹,打开Libraries → \rightarrow CMSIS → \rightarrow CM3 → \rightarrow DeviceSupport → \rightarrow ST → \rightarrow STM32F10x → \rightarrow startup → \rightarrow arm,将文件夹中的文件复制到新建的Start文件夹中。

    • 将启动文件导入到Keil的工程中时,启动文件只能导入一个。课程所用型号需要选择startup_stm32f10x_md.s文件。

      启动文件的选择
      STM32学习笔记(持续更新)丨学习心得丨代码实现_第1张图片
        选择启动文件时,需要考量芯片的两个指标:Flash容量、型号。
        根据上表,如果使用STM32F100的型号,就选择启动文件后缀为vl的启动文件,根据Flash容量选择后缀为ld_vlmd_vlhd_vl的文件,表中其他型号同理。
      STM32学习笔记(持续更新)丨学习心得丨代码实现_第2张图片

    • 回到STM32F10x文件夹,将stm32f10x.hsystem_stm32f10x.csystem_stm32f10x.h复制到Start文件夹中,并导入到工程组Start中stm32f10x.h就是STM32外设寄存器描述文件,它的作用就同51单片机的头文件REGX52.H一样,是用来描述STM32有哪些寄存器和它对应的地址的)。

  • 配置内核寄存器的描述文件

    • 打开CM3 → \rightarrow CoreSupport,core_cm3.hcore_cm3.c就是内核的寄存器描述文件(其中还包含了一些内核的配置函数),同样将它们复制到Start文件夹中,并导入到工程组Start中
  • 配置User文件夹

    • 在Keil工程文件中新建User文件夹,添加main.c文件(注意:添加main.c文件时的路径要选择User文件夹,否则默认放在文件夹外),这时对main.c文件进行编译检查,如果没有报错和警告说明工程建立成功。 至此,基于寄存器开发的工程就建立完成了。
    • 在在“固件库”文件夹中打开Project → \rightarrow STM32F10x_StdPeriph_Template,其中stm32f10x_conf.h是用来配置库函数头文件的包含关系的,其中还包含用来参数检查的函数定义,是所有库函数都需要的。此外stm32f10x_it.hstm32f10x_it.c是用来存放中断函数的。将这三个文件复制下来,粘贴到工程的User文件夹中
  • 配置库函数Library文件夹

    • 在工程文件夹中新建Library文件夹。
    • 在“固件库”文件夹中打开Libraries → \rightarrow STM32F10x_StdPeriph_Driver(STM32标准外设驱动) → \rightarrow src,其中的文件就是库函数的源文件,其中misc.c是内核的库函数,其他都是内核外的外设库函数。将其中所有文件复制到Library文件夹中。
    • 在Libraries → \rightarrow STM32F10x_StdPeriph_Driver(STM32标准外设驱动) → \rightarrow inc 中是库函数的头文件,将其中所有文件复制到Library文件夹中。

注意事项
  需要用的文件一定要复制到工程的文件夹里面,不要添加工程文件夹外面的文件,否则外面的文件一旦挪动位置,工程里就找不到文件了。所以我们要将用的文件复制到工程中,以保证工程的独立性。

  1. 工程里对应建立Start,Library,User等同名称的分组(Group),然后将文件夹内的文件添加到对应的分组里;
  2. 点击工程选项(魔术棒)、C/C++、在Include Path内声明所有包含头文件的文件夹(Start,Library,User);
  3. 点击工程选项、C/C++、在Define内定义USE_STDPERIPH_DRIVER(宏定义,意为使用标准外设驱动这是库函数的条件编译,使用库函数开发就必须要定义
  • 该宏定义在头文件stm32f10x.h中(8296行)
#ifdef USE_STDPERIPH_DRIVER
  #include "stm32f10x_conf.h"
#endif

  其他工程在这个位置还声明了 STM32F10X_MD的字符串,但是Keil5在新建工程之后会自动声明该字符串,不需要再额外声明。

  1. 点击工程选项、Debug中下拉列表选择对应的调试器(本次课程使用ST-Link进行调试,故选择ST-Link Debugger),点击Settings,在Flash Download里勾选Reset and Run,勾选上这一项之后,下载的程序会立马复位并执行,省去了在开发板上手动按下复位的步骤。

2.3 工程文件的架构

​  工程的建立非常灵活,学会建立工程后可以依照自己的风格建立工程。
STM32学习笔记(持续更新)丨学习心得丨代码实现_第3张图片
​  根据上图,我们可以了解STM32的工程架构(总结)如下所示:
​  首先是主动执行的文件:

  • startup_xx.s 启动文件
    • 启动文件是程序执行最基本的文件,由汇编语言编写。启动文件内定义了中断向量表,中断服务函数等。
    • 在中断服务函数中有一个复位中断,是整个程序的入口。 复位中断中调用StstemInit函数和main函数。StstemInit函数定义在system_xx.c/.h文件中,这个函数的作用是设置微控制器的启动,初始化嵌入式闪存接口,锁相环,更新系统内核的时钟变量。这个函数仅在复位后需要调用。
    • 在启动文件中还定义了STM32所有的其他中断函数。这些中断函数的定义,存放在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/.hstm32f10x_adc.c/.h…文件:库函数
  • stm32f10x_conf.h文件:库函数配置文件
    • 该文件用来配置头文件的包含关系,它include了所有的库函数头文件,同时在stm32f10x.h中又include了stm32f10x_conf.h。所以,在使用这些库函数时,只需要包含stm32f10x.h这一个头文件,就相当于包含了所有的头文件

2.4 环境的优化处理

  1. 为防止出现中文乱码在设置中将Encoding改为UTF-8格式(如果打开别人的文件出现乱码,可以尝试修改其他的编码格式)
  2. 在设置中的Colors & Fonts中可以修改字体样式和大小

2.5 使用寄存器开发实操

​  下面对开发板上的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位,需要使用 &=和 |= 的操作,太过麻烦,对开发人员不友好。
​  需要注意,编程完成后最后需要多空一行,否则程序会报出一个警告。

2.6 使用库函数配置寄存器实操

​  使用库函数配置寄存器虽然代码较为复杂,但是逻辑清晰,易于操作。下面对开发板上的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 通用输入输出

  GPIO,全称为General Purpose Input Output,意为通用输入输出,可配置八种输入输出模式。引脚电平: 0 V − 3.3 V 0V-3.3V 0V3.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电压采集,模拟通信协议接收数据等。

3.1 GPIO的基本结构

STM32学习笔记(持续更新)丨学习心得丨代码实现_第4张图片
  在所有的STM32中,所有的GPIO外设都挂载在APB2外设总线上。其中GPIO外设是按照GPIOA、GPIOB、GPIOC等命名的。
  每个GPIO外设有16个引脚。以GPIOA为例,它的引脚编号分别为PA0,PA1,PA2 … PA15。其他GPIO的引脚也是这样命名的。
  在GPIO模块内部,主要包含了寄存器驱动器两个模块。寄存器就是一段特殊的存储器,内核可以通过APB2总线对寄存器进行读写,以完成输出电平和读取电平的功能。
  STM32是32位的单片机,其中的寄存器都是32位的。而端口只有16个,故寄存器只有低16位有对应的端口,高16位没有用到
  驱动器的作用是增加信号的驱动能力。寄存器只负责存储数据,需要用驱动器来增大驱动能力。

3.2 GPIO位结构及其工作模式

3.2.1 GPIO位结构

STM32学习笔记(持续更新)丨学习心得丨代码实现_第5张图片
  GPIO每一位具体的电路结构如上图所示。整体结构可以分为图上部的输入部分和图下部的输出部分。

  • 引脚和保护二极管
    • 保护二极管可以保证党引脚电平高于 V D D ( 3.3 V ) V_{DD}(3.3V) VDD(3.3V)或低于 V S S ( 0 V ) V_{SS}(0V) VSS(0V)时保护内部电路安全。
  • 输入数据寄存器和输入驱动器
    • 上拉电阻和下拉电阻:可以通过程序配置上拉输入模式下拉输入模式,其主要作用是设置一个默认的输入电平,即输入引脚空载时内部数字电路接受的电平。为了避免引脚悬空导致的数据不确定,需要配置输入端口的输入模式。如果上下开关全部断开,此时端口处于浮空输入模式
    • TTL肖特基触发器:可以理解为用肖特基管组成的施密特触发器,作用是对输入电压进行整形。对于施密特触发器,之后当输入电压高于 V T + V_{T+} VT+(正向阈值电压)或低于 V T − V_{T-} VT(负向阈值电压)时输出电压才会突变。接入施密特触发器可以有效避免因信号波动造成的输出抖动现象。
    • 经过施密特触发器整型的波形就可以写入输入数据寄存器了。
    • 模拟输入:链接到(片上外设)ADC,用于读取模拟量。
    • 复用功能输入:链接到其他需要读取端口的片上外设(例如串口的输入引脚),用于读取数字量。
  • 输出数据寄存器和输出驱动器
    • 输出数据寄存器:如果选择通过输出数据寄存器进行控制(采用基于寄存器开发的开发方法),即普通的IO口输出,对输出数据寄存器进行写操作就可以操作对应的端口了。输出数据寄存器同时控制16个端口(16位),且该寄存器只能整体读写。 如果想单独操作这个数据寄存器的某一位,需要先读出这个寄存器,然后按照按位与和按位或的方式更改该位,最后再将更改后的数据写入。
    • 位设置/清除寄存器:可以用来单独操作输出数据寄存器的某一位,而不影响其他位。如果需要对输出数据寄存器的某一位写入1,则在位设置寄存器的对应位置写1,其他位置写0(表示不需要操作)即可;如果想对某一位写0,就在位清除寄存器的对应位置写1,其他位置写0(表示不需要操作)即可。在本课程中主要采用库函数进行操作,库函数使用的就是操作位设置/清除寄存器的方法。

        在STM32中,操作寄存器的某一位还可以操作 “位带” 区域,类似于51单片机中的位寻址。在STM32中,专门分配有一段地址区域,这段地址映射了RAM和外设寄存器所有的位。读写这段地址中的数据,就相当于读写所映射位置的某一位。在本课程中不会用到该种操作方式。

    • 输出驱动器:由输出数据寄存器和其他片上外设控制,通过一个(二选一)数据选择器链接到输出控制模块。通过设置可以选择推挽、开漏或关闭三种输出方式
    • 推挽输出模式:又称为“推拉”,“互补”的输出模式。当数据寄存器(复用功能输出)为1时,P-MOS导通,N-MOS截止,输出高电平;数据选择器(复用功能输出)为0时,P-MOS截止,N-MOS导通,输出低电平。在这种模式下,高低电平均有较强的驱动能力 ,所以推挽输出模式也称作强推输出模式。在推挽输出模式下,STM32对IO口具有绝对的控制权 ,高低电平都由STM32说了算。
    • 开漏输出模式:意为N-MOS的漏极和 V D D V_{DD} VDD是开路的,即P-MOS开路(无效)。数据寄存器(复用功能输出)为1时,N-M断开,这时IO口呈现高阻模式(没有驱动能力);数据寄存器(复用功能输出)为0时,N-MOS导通,输出低电平。这种模式下只有低电平有驱动能力。 开漏输出模式可以作为通信协议的驱动方式,例如I2C。在多机通信的情况下,这个模式可以避免各个设备的相互干扰。此外,开漏输出可以通过外接上拉电源和电阻以提高端口的带载能力,或兼容一些 5 V 5V 5V 输入的设备(输出 5 V 5V 5V 的电平信号)。
    • 关闭状态:意为当引脚配置为输入模式时,两个MOS管都无效(开路),端口的电平由外部信号控制。(高阻态,没有驱动能力)

3.2.2 GPIO的八种工作模式

  通过对GPIO的端口配置寄存器进行操作,即配置弱上拉/下拉电阻开关的通断,施密特触发器是否有效,N-MOS和P-MOS是否有效,数据选择器的选择等,端口可以被配置为以下八种模式:
STM32学习笔记(持续更新)丨学习心得丨代码实现_第6张图片

  下面是各种输入模式的等效电路图:

  1. 浮空/上拉/下拉输入模式
    STM32学习笔记(持续更新)丨学习心得丨代码实现_第7张图片
  2. 模拟输入模式
    STM32学习笔记(持续更新)丨学习心得丨代码实现_第8张图片
  3. 推挽/开漏输出模式
    STM32学习笔记(持续更新)丨学习心得丨代码实现_第9张图片
  4. 复用推挽/开漏输出模式
    STM32学习笔记(持续更新)丨学习心得丨代码实现_第10张图片
      可以看到,只有模拟输入模式下,数字输入(施密特触发器)处于关闭状态。输入时输出呈现高阻态,但输出的同时端口同样可以输入(读取端口状态)

3.3 GPIO中的寄存器简介

  • 端口配置寄存器
      在一个GPIO中,每一个端口需要4位进行配置,16个端口就需要64位进行配置。由于STM32的寄存器都是32位的,故一共需要两个寄存器,分为端口配置低寄存器端口配置高寄存器。在端口配置寄存器中还可以定义GPIO输出的速度,即限制输出引脚的最大翻转频率,设计输出速度的目的是调节功率和稳定性。
  • 端口输入数据寄存器
      端口输入数据寄存器的低16位对应16个引脚,高16位没有使用。
  • 端口输出数据寄存器
      与输入数据寄存器相同,端口输出数据寄存器的低16位对应16个引脚,高16位没有使用。
  • 端口位设置/清除寄存器
      该寄存器的高16位实现位清除,低16位实现位设置。如果想对多个端口同时进行位设置和位清除,即对GPIO信号的同步性要求较高,可以只使用这一个寄存器
  • 端口位清除寄存器
      该寄存器的低16位实现位清除,高16位没有使用。它可以与上面一个寄存器配合使用,使用时两个寄存器都只使用寄存器的低16位,操作更为方便。
  • 端口配置锁定寄存器
      该寄存器可以对端口的配置进行锁定,防止意外更改。

持续更新中……

你可能感兴趣的:(stm32)