STM32 C语言
在STM32中short占16位,而int占32位。具体如下。stdint关键字列举了在程序中使用的变量名来代替关键字
一,基本介绍
1,认识
2,外设
片上资源/外设
上图深颜色的是位于Cortex-M3内核里面的外设,剩下的是内核外的外设。以上为STM32F1整个系列的外设,并不是所有型号都有这些外设,比如此款STM32F103C8T6就没有后面4个外设,具体查看手册
Systick是内核里面的一个定时器,主要用来给操作系统提供定时服务(STM32可以加入操作系统,如FreeRTOS,UCOS等),如果使用操作系统就需要Systick提供定时来进行任务切换的功能。RCC可以对系统时钟进行配置,还可以使能各模块时钟,STM32中外设在上电情况下默认是没有时钟的,不给时钟的话操作外设是无效的,外设也不会工作(目的是为了降低功耗) ,所以在操作外设之前必须要先使能它的时钟(RCC完成时钟的使能)。TIM最常用的外设,分为高级定时器,通用定时器,基本定时器三种类型,常用的是通用定时器,它不仅可以完成定时中断任务,还可以完成测频率,生成PWM波形,配置成专用的编码器接口等功能。USART既支持异步串口,也支持同步串口,我们说的UART是指异步串口。RTC实时时钟在STM内部完成年月日时分秒的计时功能,还可以接外部备用电池,即使掉电也能正常运行
3,命名规则
4,引脚定义
标红色的是电源相关的引脚,蓝色的是最小系统相关的引脚,标绿色的是IO口,功能口这些引脚优先使用加粗的IO口。I/O口电平表示它能容忍的电压,FT表示5V,没有FT的只能容忍3.3V电压。主功能表示上电之后默认的功能,一般和引脚名称相同。默认复用功能就是IO口上同时连接的外设功能引脚,这个配置IO口时可以选择通用IO口或者是复用功能。重定义功能是如果有两个功能同时复用在了一个IO口上,而且也确实需要用到这两个功能,那就可以把其中一个复用功能重映射到其他端口上。
第一个引脚VBAT是备用电池供电的引脚,在这个引脚可以接3V电池,当系统电源断电时,备用电池可以给内部RTC时钟和备份寄存器提供电源。2号引脚是IO口或者侵入检测或者RTC,IO口可以根据程序输入高低电平,侵入检测可以用来做安全保障的功能(比如产品安全性比较高,可以在外壳加一些防拆的触点,然后接上电路到这个引脚上,如果有人强行拆开设备,触点断开,这个引脚的电平变化就会触发STM32侵入信号,然后清空数据来保证安全),RTC引脚可以用来输出RTC校准时钟,RTC闹钟脉冲或者秒脉冲。3,4号引脚是IO口或者接32.768KHz的RTC晶振。5,6号引脚接系统的主晶振,一般为8MHz,芯片内有锁相环电路,可以对这个8MHz频率进行倍频,最终产生72MHz的频率,作为系统的主时钟。7号NRST是系统复位引脚,N表示是低电平复位。8,9号引脚是内部模拟部分的电源,比如ADC,RC振荡器等,VSS是负极,接GND,VDD是正极。接3.3V。10~19号引脚都是IO口,其中PA0还兼具了WKUP功能,可以用于唤醒处于待机模式的STM32。20号引脚是IO口或者BOOT1引脚,BOOT引脚用来配置启动模式。21,22也为IO口。23,24号的VSS_!和VDD_!都是系统的主电源口,VSS为负极,VDD为正极,另外35,36VSS_2,VDD_2,VSS_3,VDD_3都是系统的主电源口,STM32内部采用分区供电,所以供电口比较多,使用时,VSS接地,VDD接3.3V即可。25~33都是IO口。34号,37~40号这些是IO口或者调试端口,用来调试程序和下载程序,STM32支持SWD和JITAG两种调试方式,SWD需要两根线,分别是SWDIO和SWCLK,JTAG需要5根线,分别是JTMS,JTCK,JTDI,JTDO,NJTRST。此教程使用STLINK下载调试程序,STLINK使用的是SWD的方式,所以只需要PA13和PA14这两个IO口,剩下的PA15,PB3,PB4可以作为普通IO口使用,但需要在程序中配置,否则不会作为IO口。41~43,45,46都是IO口。44号BOOT0也是用作启动配置的
5,内部系统介绍
Cortex-M3引出三条总线,分别为ICode指令总线,Dcode数据总线,System系统总线,ICode和Dcode主要用来连接Flash闪存(存储编写的程序),System总线连接SRAM,FSMC等。ABH总线挂载外设。DMA相当于CPU小秘书,主要做大量的数据搬运,将其连接在总线矩阵上,它可以和CPU一样拥有总线控制权,用于访问外设。
6,启动配置
启动配置的作用就是指定程序开始运行的位置。一般情况下程序都是在Flash程序存储器开始执行。但是在某些情况下,我们也可以让程序在别的地方执行,用于完成特殊功能。比如系统存储器启动模式,一般是在串口下载程序时使用,其里面存放的是STM32中的一段BootLoader程序(作用就是接收串口的数据,然后刷新到主闪存中),什么时候使用串口下载那?当STM32芯片的5个调试端口都被我们设置成IO口时,这就需要用到串口方式下载程序。内置SRAM启动主要用来程序调试
最后一句话的意思是BOOT只在上电后第四个上升沿之前有效,之后就无所谓了。对于20号引脚BOOT1当 第四个上升沿过去之后,它就变成了IO口功能。
7,最小系统电路
二,新建STM32工程
全过程如下:
目前STM32开发方式主要有基于寄存器的方式,基于标准库也就是库函数的方式和基于HAL库的方式。基于寄存器的方式与开发51单片机一样,用程序直接配置寄存器来达到我们想要的功能(最底层最直接,效率更高)。但是由于STM32结构复杂,寄存器太多,所以基于寄存器的方式不推荐;基于库函数的方式是使用官方提供的封装好的函数,通过调用这些函数来间接配置寄存器,由于ST对寄存器封装的比较好,所以这种方式既能满足对寄存器的配置,又能提高开发效率;基于HAL库的方式可以直接用图形化界面快速配置STM32,比较适合快速上手STM32的情况。
我们采用库函数的方式
使用库函数的方式我们需要准备一个STM32库函数的压缩包
首先新建一个存放工程的文件夹在桌面,然后打开Keil 5新建工程选择刚才新建的工程文件夹,再此文件夹内再新建一个文件夹,用于存放本次的工程。然后选择好芯片型号。之后根据以下路径找到所需文件:Libraries-CMMSIS-CM3-DeviceSupport-ST-STM32F10x-startup-arm找到的文件如下图。这些文件就是STM32的启动文件,STM32程序就是从启动文件开始执行的。将这些文件全部复制,放到文件工程文件夹下(可以在工程文件夹下新建一个start文件存放,显得更规矩)
补充:前面说过的启动文件有很多类型,选择哪一个要根据芯片型号来选择 ,以下为型号分类。如果用的是STM32F100就选择带VL的启动文件,然后再根据Flash大小选择LD MH 还收是HD。如果使用STM32F105/107的型号,直接选择CL的启动文件即可
然后回到STM32F10x文件下,如下,stm32f10x.h就是STM32外设寄存器描述文件,与51单片机头文件REGX52.H一样,剩下的两个system文件主要是用来配置时钟的,STM32主频72MHz,就是System文件的函数配置的,将这三个文件赋值,也粘贴到start文件下。
因为STM32是内核和内核外围设备组成的,而且这个内核寄存器描述和外围设备描述文件不在一起,所以还要添加一个内核寄存器的配置文件。打开CM3-CoreSupport,如下,这两个文件就是内核的寄存器描述,将这俩文件复制下来,也粘贴到start问价夹下。到此为止,工程的必要文件就复制完成了
然后回到Keil,将工程下的文件夹改名为start
然后右键选择Add Existing Files...
打开start文件
添加启动文件后缀为md.s的(只能添加这一个启动文件),然后剩下的.c和.h文件都添加进来
这样start文件夹文件就添加好了
最后我们还要再工程选项里添加上这个文件的头文件路径
把start文件路径添加进来即可
在工程文件下新建文件夹,然后回到Keil
右键添加组,
将组改名为刚才新建文件夹的名
然后再此文件夹下新建一个main.c文件
下面的路径要修改成刚才新建的工程下文件夹的路径
修改成如下
然后就可以开始编程了
注意:点击下图所示右上角小扳手,将编码格式Encoding改为UTF-8,防止中文乱码
编程完后,按照如下方式将烧录工具和STM32最小系统板接好
然后配置调试器
我们用的是STLINK,所以修改为ST-Link
再点击右侧Setting按钮,将Reset and Run给勾上(勾上后下载程序后会立马复位并执行。否则每次下载之后还要按一下复位按键才执行)
接下来为工程添加库函数。打开工程文件夹,新建文件命名为Library来存放库函数
在如下所示路径中找到库函数文件,misc是内核的库函数,其他为内核外设库函数
将其全部复制到刚才创建的Library文件下 ,然后再打开inc文件夹(如下图)可以看到库函数头文件,也复制粘贴到Library下
然后回到Keil软件添加组,并将添加的组修改名称为Library
右键组,添加已经存在的文件。将刚才创建的Library文件夹内的文件都添加进来
但是对于库函数来说,还不能直接使用。还需要再添加一个文件
按如下路径找到所需文件
stm32f10x_conf.h用来配置库函数头文件的包含关系。下面的两个后缀为it的文件用来存放中断函数
将这三个文件复制下来,粘贴到工程User目录下
然后回到Keil,在User组里将刚才的文件添加进去
在头文件右键打开
找到下方语句,它表示必须定义USE_STDPERIPH_DRIVER,下面的stm32f10x_conf.h才有效,复制USE_STDPERIPH_DRIVER
如下操作,在Define后粘贴刚才复制的
下面的头文件目录也要将刚才的User和Library路径给添加上
实现点灯操作
首先配置使能时钟,库函数里有一个函数来开启时钟,输入RCC_APB2PeriphClockCmd(,会自动出现要写的参数,这时我们可以右键进入到函数定义,在函数定义上方选择要用的参数。第一个参数是选择外设,第二个是选择新的状态
第一个参数选择RCC_APB2Periph_GPIOC,第二个参数选择ENABLE。这样GPIOC的外设时钟就配置好了
第二步是配置端口模式,用GPIO_Init函数。此函数有两个参数,第一个是选择哪个GPIO,第二个是参数的结构体。进入到函数定义,根据提示配置函数即可,因为我们用PC13口的LED,所以第一个参数写GPIOC。第二个结构体参数,我们根据注释新建一个GPIO_InitTypeDef变量,变量名为GPIO_InitStructure,然后利用GPIO_InitStructure.来调用结构体参数并配置,如下图
结构体变量有Mode,Pin,Speed三个参数
配置这三个参数,右键进入定义,根据下图红圈内的继续ctrl+f查找定义地方
就可以找到定义好的各种模式。我们选择GPIO_Mode_Out_PP(通用推挽输出)
最后,利用函数GPIO_SetBits来设置端口高电平来点灯。函数GPIO_ResetBits可以将端口置为低电平
最终代码
#include "stm32f10x.h"
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
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);
while(1)
{
}
}
调试功能:
注意:要修改程序需要退出调试模式重新编译。
点击下图红圈所示进入调试模式
进入调试界面如下,左边红圈为寄存器组和状态标志位等信息。上方黄圈为C语言翻译成的汇编程序
上方从左到右依次为复位,全速运行(程序一直运行至断点),停止全速运行。单步运行,跳过当前行单步运行,跳出当前函数单步运行,跳至光标指定行单步运行
如下图所示,可以查看STM32的所有外设,并进行查看
三,GPIO输出
1,GPIO基本介绍
GPIO叫做通用输入输出,可配置8种输入输出模式。引脚电平在0~3.3V之间,部分标注FT的可容忍5V(即可在端口输入5V,而端口最大输出电压为3.3V)。输出模式下可控制端口输出高低电平,用以驱动LED,控制蜂鸣器,模拟通信协议输出时序等。输入模式下可读取端口高低电平或电压,用于读取按键输入,外接模块电平信号输入,ADC电压采集,模拟通信协议接收数据等
2,GPIO总体结构
左边的为APB2外设总线,也就是之前介绍STM32系统结构时所说的挂载外设的APB2,STM32中,所有的GPIO都是挂载在APB2外设总线上的。GPIO外设的名称按照GPIOA,GPIOB,GPIOC来命名的,每个GPIO外设共有16个引脚(编号从0到15,PA0~PA15)。每个GPIO模块内,主要包含了寄存器和驱动器,寄存器是一种特殊的存储器,内核可以通过APB2总线对寄存器进行读写。寄存器每一位对应一个引脚,因为STM32为32位单片机,所以STM32内部的寄存器都是32位的,但端口只有16位,所以寄存器只有低16位有作用,高16位不使用。
3,GPIO位结构
整体可以分为两个部分,上面为输入部分,下面为输出部分。中间画虚线框的是驱动器部分.
输入部分:从IO引脚开始,接了两个保护二极管,这两个保护二极管可以对输入电压进行限幅,上面接VSS为3.3V,下面接VDD为0V,如果输入电压比3.3V还要高,那上方二极管就会导通,输入电压产生的电流就会直接流入VDD,而不会流入内部电路;如果输入电压比0V还要低,下方二极管就回导通,电流就会直接从VSS直接流出去,而不从内部电路汲取电流,可以保护内部电路;如果输入电压在0~3.3V之间,那两个二极管都不会导通,这时二极管对电路没有影响。上拉电阻和下拉电阻的开关可以通过程序配置,如果上面导通下面断开就是上拉输入模式(默认为高电平的输入模式),反之为下拉输入模式(默认为低电平的输入模式),如果两个都断开就是浮空输入模式,上拉和下拉作用是为了给输入提供一个默认的输入电平,同时也是为了避免引脚悬空导致的输入数据不稳定。这两个上拉和下拉电阻阻值比较大,是一种弱上拉和弱下拉,目的是尽量不影响正常的输入操作。下一个是施密特触发器(图片有误),它的作用就是对输入电压进行整形,执行逻辑是如果输入电压大于某一阈值,输出就会瞬间升为高电平;如果输入电压低于某一阈值,输出就会瞬间降为低电平,可以有效避免信号由于波动造成的信号抖动现象,接下来经过施密特触发器整形的波形就可以直接写入输入数据寄存器了,我们再用程序读取输入数据寄存器对应某一位的数据,就可以知道端口的输入电平了。最后上面还有两路线路是连接到片上外设的一些端口,模拟输入是接到ADC上的,因为ADC需要接受模拟量所以在施密特触发器前接收。另一个复用功能输入是连接到其他需要读取端口的外设上的。
输出部分:数字部分由输出数据寄存器或片上外设控制,两种控制方式通过数据选择器接到输出控制部分。如果选择通过输出数据寄存器进行控制就是普通的IO口输出,写这个数据寄存器的某一位就可以操作对应的某个端口了,它前面的位设置/清楚寄存器,这个可以用来单独操作输出数据寄存器的某一位而不影响其他位(因为输出数据寄存器需要同时控制16个端口,并且这个寄存器只能整体读写,所以需要采用特殊的操作方式只修改某一位) ,如果我们要对某一位进行置1操作,在位设置寄存器的对应位写1即可,剩下位写0,这样它内部电路会自动将输出数据寄存器中对应位置为1,而剩下写0的位则保持不变;如果想对某一位清0,就在位清除寄存器对应位写1即可。输出控制后接到了两个MOS管,这个MOS管就是一种电子开关,信号控制导通和关闭,开关负责将IO口接到VDD或VSS,这里可以选择推挽,开漏或关闭三种输出模式;推挽输出模式下P-MOS和N-MOS均有效,当数据寄存器为1时,上管导通下管断开输出直接接到VDD就是输出高电平,数据寄存器为0时输出低电平,这种模式下高低电平均有较强的驱动能力,所以推挽输出也可以叫强推输出模式,推挽输出模式下,STM32对IO口有绝对控制权。开漏输出模式下P-MOS是无效的,只有N-MOS在工作,数据寄存器为1时,下管断开,这是输出相当于断开,也就是高阻模式;数据寄存器为0时,下管导通,输出直接接到VSS,也就是输出低电平。这个开漏模式可以作为通信协议的驱动方式,比如I2C通信的引脚就是使用开漏模式,在多机通信情况下,这个模式可以避免各个设备的相互干扰;另外,开漏输出可以用于输出5V电平信号,比如在IO口外接一个上拉电阻到5V的电源,当输出低电平时,由内部VSS的N-MOS直接接VSS,输出高电平时由外部上拉电阻拉高至5V。
4,GPIO八种工作模式
通过上述对位结构的介绍,只要对GPIO的端口配置寄存器,端口就可以配置成以下8种模式
输出模式下输入模式是有效的,而输入模式下输出模式是断开的,这是因为一个端口可以有多个输入但是只能有一个输出
STM32上电后如果不初始化,默认为浮空输入模式
复用推挽输出和复用开漏输出和普通的开漏输出推挽输出差不多,只是复用的输出引脚电平由片上外设控制,电路图如下
5,GPIO控制函数使用
函数:以下函数可以在stm32f10x_gpio.h头文件中查看
void GPIO_DeInit(GPIO_TypeDef* GPIOx);//调用此函数后所指定的GPIO外设就会被复位
void GPIO_AFIODeInit(void);//可以复位AFIO外设
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);//初始化GPIO口,需要先定义一个结构体变量,然后给结构体赋值,最后调用这个函数
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);//可以把结构体变量赋一个默认值
//以下四个用来读取IO口状态
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//用来读取输入寄存器某一个端口的输入值
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);//读取整个输入寄存器,返回值为16位数据,每一位代表一个端口值
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//以下两个函数用来读取输出数据寄存器的某一位,它并不是用来读取端口的输入数据,一般用于输出模式下,用来看一下自己输出的是什么
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
//以下四个用来GPIO写入
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//将指定引脚设置为高电平
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//将指定引脚设置为低电平
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);//指定端口并根据BitVla的值设置指定端口BitVal可以写成Bit_RESET(低电平),Bit_SET(高电平)
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);//根据PortVal对16个端口同时写入
void GPIO_PinLockConfig函数:用来锁定GPIO配置的,调用这个函数,参数指定某个引脚,这个引脚的配置就会被锁定,防止意外更改。
//以下为GPIO8种工作模式
typedef enum
{ GPIO_Mode_AIN = 0x0,//模拟输入
GPIO_Mode_IN_FLOATING = 0x04,//浮空输入
GPIO_Mode_IPD = 0x28,//下拉输入
GPIO_Mode_IPU = 0x48,//上拉输入
GPIO_Mode_Out_OD = 0x14,//开漏输出
GPIO_Mode_Out_PP = 0x10,//推挽输出
GPIO_Mode_AF_OD = 0x1C,//复用开漏
GPIO_Mode_AF_PP = 0x18//复用推挽
}GPIOMode_TypeDef;
6,示例:LED灯点亮
Delay为自己编写的函数,可以直接使用
#include "stm32f10x.h"
#include "delay.h"
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//也可以按位或同时使能多个时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
//因为每个引脚都占有1位,所以要同时初始化多个引脚的话,只需要逻辑或即可
//如:GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2
//如果要同时初始化PA的所有引脚可以用GPIO_Pin_All
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
while(1)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);
Delay_ms(500);
GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);
Delay_ms(500);
}
}
四,GPIO输入
1,传感器模块简介
传感器元件(光敏电阻/热敏电阻/红外接收管等)的电阻会随外界模拟量的变化而变化,通过与定值电阻分压即可得到模拟电压输出,再通过电压比较器进行二值化即可得到数字电压输出。其电路如下
中间电路R1,N1组成分压电路,N1为传感器变化电阻,传感器变化,AO(IN)点电压 随之变化。IN+作为U1电压比较器的输入端(U1内部为电压比较器,输入电压IN+,IN-进行比较,如果大于IN-则输出D0为高电平,反之为低电平)。IN-输出端为R2可调电阻,以得到不同电压输出IN-,相当于调整阈值。
2,硬件电路
下图按下按键PA0接收到低电平,松开按键引脚处于悬空,这种悬空状态电平是不确定的。为了防止干扰,需要将此引脚设置为上拉输入模式,保证按键不按下时引脚处于高电平状态
下图接法,PA0引脚设置为浮空输入和上拉输入都可以
下图PA0要配置为下拉输入模式
下图PA0配置为下拉输入和浮空输入都可以
STM32与传感器的接法如下。DO数字输出接PA0。AO为模拟输出,暂时不用
3,按键控制
五,OLED调试工具
1,常用的调试方式
串口调试:通过串口通信,将调试信息发送到电脑端,电脑使用串口助手显示调试信息
显示屏调试:直接将显示屏连接到单片机,将调试信息打印到显示屏上
Keil调试模式:借助Keil的调试模式,可使用单步运行,设置断点,查看寄存器及变量等功能
2,基本介绍
OLED叫做有机发光二极管。OLED显示屏:性能优异的新型显示屏,具有功耗小,响应速度快,宽视角,轻薄柔韧等特点
供电:3V~5V 通信协议:I2C(如下图,四针脚一般用I2C,7针脚一般用SPI) 分辨率:128*64
3,硬件电路
四针脚OLED。SCL和SDA为I2C通信引脚,应该接到单片机I2C通信引脚上,而STM32我们用GPIO口模拟的I2C通信。
7针脚OLED。除了VCC和FND,剩下的引脚是SPI通信协议,也可以用GPIO口模拟通信协议
4,OLED驱动函数
一下图为OLED实物图和屏幕坐标图
六,中断系统
EXIT外部中断是众多能产生中断的外设之一。本节主要介绍外部中断
1,基本介绍
2,STM32中断
STM32F1系列最多为70个可屏蔽中断通道(即中断源)(10个内核中断和60个外部中断),包含EXTI(外部中断),TIM(定时器),ADC(模数转换器),USART,SPI,I2C,RTC等多个外设。使用NVIC统一管理中断,每个中断通道都有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级
下图为STM32的中断,灰色的是内核的中断(一般用不到)。第一个Reset为复位中断,当产生复位事件时,程序就会自动执行复位中断函数。非灰色部分就是STM32的外设中断了,比如第一个WWDG窗口看门狗用来检测程序运行状态的中断(程序卡死了,没有及时喂狗,窗口看门狗就会申请中断,使程序跳到看门狗中断程序里);PVD电源电压监测,如果供电电压不足,PVD电路就会申请中断。EXTI0~EXTI4,EXTI9_5和EXTI15_!0即为本节要学的外部中断的中断资源。
下表的地址就是指的中断向量。STM32中断向量表是一个存储中断处理函数地址的数组,位于Flash区的起始地址。每个数组元素对应一个中断源,其地址指向相应的中断服务程序。当中断发生时,处理器会根据中断号查找向量表,然后跳转到对应的中断服务程序执行。中断向量表的作用是解决中断函数地址不固定与中断必须跳转到固定地方执行程序之间的矛盾。由于编译器每次编译都会为中断函数随机分配地址,但硬件要求中断必须跳转到固定位置上,因此,中断向量表就作为这样一个固定的地址列表,其中包含了中断函数的地址及跳转到这些地址的程序。当中断发生时,处理器会跳转到这个固定的中断向量表,然后根据其中的信息跳转到相应的中断处理函数,从而执行中断。
下图为STM32中断框图
3,NVIC基本结构
NVIC名字叫做嵌套中断向量控制器,在STM32中统一分配中断优先级和管理中断的。NVIC是一个内核外设,是CPU小助手。NVIC有很多输入口,中断线路都可以接过去;NVIC只有一个输出口,它根据每个中断优先级分配中断的先后顺序。
当一个中断请求到达时,NVIC会确定其优先级并决定是否应该中断当前执行的程序,以便及时响应和处理中断请求。
NVIC支持256个中断(16内核和240外部)。支持256优先级,允许裁剪。
NVIC第一个作用是中断使能/失能控制,第二个是中断优先级控制(将内核中断和外部中断一起拿过来做中断判断),第三个作用是优先级分组控制,通过AIRCR寄存器对优先级进行分组,分组排序完后进入CPU
4,NVIC优先级分组
为了处理不同形式的优先级,STM32的NVIC可以对优先级进行分组,分为抢占优先级和响应优先级。
如果一个中断的抢占优先级高于正在执行的中断,那么它就可以打断当前中断,优先得到执行,数值越小,优先级越高。如果两个中断同时到达且它们的抢占优先级相同,那么响应优先级高的中断首先得到响应,数值越小,优先级越高。当多个中断同时发生时,执行顺序首先由抢占优先级决定,如果抢占优先级相同,则进一步由响应优先级决定,如果响应优先级也相同,则最终由自然优先级决定。在中断嵌套的情况下,高抢占优先级的中断可以打断低抢占优先级的中断,但高响应优先级的中断不能打断低响应优先级的中断(当它们具有相同抢占优先级时)
NVIC的中断优先级由优先级寄存器IPR的4位(0~15)决定(对应16个优先级,值越小优先级越高)(IPR有8位,实际中只使用高4位),这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级,怎么切分又由AIRCR寄存器控制(8到10位)
下图为5种分组方式,程序中自己选择。整个程序中只分一次(一般在init函数中分)
5,NVIC寄存器
中断使能寄存器,如果某一位写1,则这一位对应的中断就允许通过,一个寄存器有32位,32X8=256正好对应了前面所说的NVIC支持256个中断。中断失能寄存器,如果某一位写1,则这一位对应的中断就不允许通过。NVIC属于内核,其寄存器属于内核寄存器,需要查看STM32F10xx Cortex-M3手册,如下查看ISER寄存器
中断优先级寄存器IPR一个寄存器有8位,刚才说过,只使用高4位,而且通过寄存器AIRCR又分成了两组。共有240个寄存器,正好对应了之前说的NVIC支持256个中断(16内核和240外部)中的240个外部寄存器,因为内核优先级已将硬件确定下来了
如下图,AIRCR只关注红圈的3位
6,NVIC相关函数介绍
NVIC库函数如下,常用的是四个HAL_NVIC_EnableIRQ(使能功能),HAL_NVIC_DisableIRQ(失能功能),HAL_NVIC_SetPriorityGrouping(优先级分组),HAL_NVIC_SetPriority(优先级)。
7,NVIC配置方法
设置中断分组 -> 设置中断优先级->使能中断
设置中断分组一般在HAL_Init函数中进行,函数如下
分组方式由高亮NVIC_PRIORITYGROUP_2决定,它的取值如下
8,EXTI
(1)简介
(3)EXTI基本结构
最左边为GPIO口的外设,每个外设16个引脚,所以接16跟线。由于EXTI只有16个GPIO通道,但这里每个GPIO外设都有16个引脚,如果每个引脚都占用一个通道,那显然16个通道就不够用了,所以在这里会有一个AFIO中断引脚选择的电路模块(数据选择器),它可以在前面3个GPIO外设选择其中一个连接到后面的EXIT通道里(所以前面说的相同的Pin不能同时触发中断),同时下面4个外设PVD RTC USB等也是并列接进来的,这些加起来就组成了EXTI的20个输入信号,然后经过EXTI电路后,分为了两种输出,上面的接到NVIC是用来触发中断的(20路输入应该有20路中断输出,输出太多比较占用NVIC通道资源,所以就把外部中断9~5,和15~10给分到一个通道里,也就是说外部中断的9~5触发同一个中断函数,15~10也触发同一个中断函数,编程时在这两个中断函数里需要再根据标志位区分到底是哪个中断进来的),下方有20条通道到了其他外设,就是我们刚才说的事件响应。
(3)AFIO复用IO口
下图为上述AFIO选择中断引脚(数据选择器)的结构图,
AFIO主要用于引脚复用功能的选择和重定义。在STM32中AFIO主要完成两个任务:复用功能引脚重定义,中断引脚选择。
(4)EXTI框图
边缘检测电路检测上升沿下降沿,如果上升沿触发选择寄存器打开就可以检测上升沿,如果下降沿触发选择寄存器打开就可以检测下降沿,同时打开那就都可以检测到。经过或门后,分两路走,往上面走就是所谓的中断,请求挂起寄存器就是标志位,中断屏蔽寄存器如果置0则中断无效,为1则中断可用。往下走为事件。
常用的就是中断屏蔽寄存器,请求挂起寄存器,上升沿触发选择寄存器,下降沿触发选择寄存器。
(5) EXTI寄存器
中断屏蔽寄存器共20位,其中MR19只用于互联网产品。每个位对应每个引脚(16个GPIO+PVD,USB,RTC,ETH)
也是有20个位。某个位写1表示允许某跟线上升沿触发。
检测到中断,则对应位置1.
(6)EXTI函数
配置上升沿下降沿寄存器和中断屏蔽寄存器都是在HAL_GPIO_Init函数里面
HAL_GPIO_EXTI_IRQHandler通过查询挂起寄存器来查看那条线触发了中断
(7)编码器简介
编码器硬件电路
A相输出和B相输出接到STM32两个引脚上
9,实例
(1)初始化配置
模块初始化函数,在模块初始化函数中写入中断配置,配置外部中断参照下图(从GPIO到NVIC这一路出现的外设模块都配置好)
#include"stm32f10x.h"
void CountSensor_Init(void)
{
}
第一步:配置RCC,把这里涉及的外设的时钟都打开(EXTI和NVIC的时钟是一直打开的,不需要再开启。NVIC是内核外设不需要开启时钟)
#include"stm32f10x.h"
void CountSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APBPeriph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APBPeriph_AFIO, ENABLE);
}
第二步:配置GPIO,选择端口为输入模式
#include"stm32f10x.h"
void CountSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APBPeriph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APBPeriph_AFIO, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
像这种其他外设使用GPIO的情况,如果不清楚该配置为什么模式,可以参考手册,如下
第三步:配置AFIO,选择一路GPIO连接到后面的EXTI
AFIO外设官方并没有给它分配专门的库函数文件,它的库函数是和GPIO在一个文件里的(stm32f10x.gpio文件)
所得函数如下
void GPIO_AFIODeInit函数: 用来复位AFIO外设,调用这个函数,AFIO配置会全部清除
void GPIO_EventOutputConfig函数和GPIO_EventOutputCmd函数:这两个函数用来配置AFIO的事件输出功能
void GPIO_PinRemapConfig函数和GPIO_EXTILineConfig函数:这两个函数比较重要,前者用来进行引脚重映射,第一个参数选择重映射方式,第二个参数是新的状态;后者就是本次要使用的函数,调用这个函数就可以配置AFIO数据选择器,来选择我们想要的中断引脚
void GPIO_ETH_MediaInterfaceConfig函数:和以太网有关
可以看到GPIO_EXTILineConfig函数第一个参数可以是GPIO_PortSourceGPIOx(x为ABCD等),第二个参数GPIO_PinSourcex(x从0到15)
#include"stm32f10x.h"
void CountSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APBPeriph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APBPeriph_AFIO, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//代表我们使用PB14为中断引脚
}
第四步:配置EXTI,选择边沿触发方式,比如上升沿下降沿或者双边沿;选择触发响应方式,中断响应或事件响应
void EXTI_DeInit函数:调用它就可以把EXTI的配置都清除,恢复成上电默认的状态
void EXTI_Init函数:调用这个函数就可以根据EXTI_InitTypeDef* EXTI_InitStruct这个结构体的参数配置EXTI外设
void EXTI_StructInit函数:调用这个函数可以把参数传递的结构体变量赋一个默认值。前面这三个参数,基本所有外设都有
void EXTI_GenerateSWInterrupt函数:这个函数是用来软件触发外部中断的,调用这个函数,参数给一个指定的中断线,就能软件触发一次这个外部中断
下面四个函数,也是库函数的模板函数,很多模块都有这四个函数。这四个参数主要是查看状态寄存器的,当程序想看标志位时就可以用这四个函数。前两个是在主程序里查看或清除标志位;后两个是在中断函数里查看或清除标志位。本质上这四个函数都是对状态寄存器的读写,只不过下面两个函数只能读写与中断有关的标志位,并且对中断是否允许做出了判断,而上面两个函数只是一般的读写标志位,无额外处理,能不能触发中断的标志位都能读取(中断里用上面两个也是没问题的)
FlagStatus EXTI_GetFlagStatus函数可以获取指定的标志位是否被置1了。void EXTI_ClearFlag函数可以对置1的标志位进行清除。
有的标志位比较紧急,在置标志位后会触发中断,在中断函数里如果想查看标志位和清除标志位,那就用EXTI_GetITStatus函数获取中断标志位是否被置1.EXTI_ClearITPendingBit函数清除中断挂起标志位
我们使用EXTI_Init配置,右键跳转到定义。可以看到与GPIO初始化类似,使用结构体初始化。
所以首先定义结构体EXTI_InitTypeDef EXTI_InitStructure,然后用.引出成员
- EXTI_InitStructure.EXTI_Line:指定我们要配置的中断线,定义如下。我们要用PB14,所以选择第14个线路,也就是EXTI_Line14
- EXTI_InitStructure.EXTI_LineCmd:指定选择的中断线的新状态。可以是ENABLE和DISABLE
- EXTI_InitStructure.EXTI_Mode:指定外部中断线的模式,可以看到共两个模式,第一个是中断模式,第二个是事件模式
- EXTI_InitStructure.EXTI_Trigger:指定触发信号的有效边沿。Rising为上升沿触发,Falling为下降沿触发。
#include"stm32f10x.h"
void CountSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APBPeriph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APBPeriph_AFIO, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//代表我们使用PB14为中断引脚
//EXTI初始化配置
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_Trigger = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_LineCmd = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
}
第五步:配置NVIC。给我们这个中断选择一个合适的优先级
void NVIC_SetVectorTable函数:设置中断向量表,此函数用的不多
void NVIC_SystemLPConfig函数:系统低功耗配置 ,此函数用的不多
void NVIC_PriorityGroupConfig函数:用来中断分组,参数为中断分组方式。右键跳转定义,可以看到下图注释的几种模式(pre-emption priority就是抢占优先级)。注意:分组方式整个芯片只能用一种,所以这个分组代码整个工程只需要执行一次
void NCIV_Init函数:根据结构体里面指定的参数初始化NVIC
NVIC_InitStructure.NVIC_IRQChannel:下图翻译:指定中断通道来开启或关闭,这个参数可以是IRQn_Type里的一个值。(对于完整的STM32中断通道列表,请参考stm32f10x.h文件)。意思就是IRQn_Type的定义不在这个文件,要到stm32f10x.h中找。
可以ctrl+F搜索,在STM32F10X_MD中找到如下图所示EXTI15_10_IPQn(STM32的EXTI10到EXTI15都是合并到了这个通道)
NVIC_InitStructure.NVIC_IRQChannelCmd: 指定中断通道是使能还是失能,参数为ENABLE或DISABLE
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority和 NVIC_InitStructure.NVIC_IRQChannelSubPriority:指定所选通道的抢占优先级和响应优先级。根据分组填写0-15数字,也可以在定义中查看,如下
#include"stm32f10x.h"
void CountSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APBPeriph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APBPeriph_AFIO, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//代表我们使用PB14为中断引脚
//EXTI初始化配置
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_Line = ENABLE;
EXTI_InitStructure.EXTI_Line = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Line = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
//配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreeptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
}
(2)中断函数
中断函数在startup_stm32f10x_md.s启动文件中,找一下可以看到定义的中断向量表,其中以IRQHandler结尾的即为中断函数名字
#include"stm32f10x.h"
void CountSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APBPeriph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APBPeriph_AFIO, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//代表我们使用PB14为中断引脚
//EXTI初始化配置
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_Line = ENABLE;
EXTI_InitStructure.EXTI_Line = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Line = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
//配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreeptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
}
void EXTI15_10_IRQHandler(void){//中断函数都是无参无返回值的
//中断函数不需要声明
//这个函数EXTI10~15都能进来,所以要先判断一下是不是我们想要的EXTI14进来
//EXTI_GetITStatus()函数。之前在stm32f10x_exti.h文件中讲过
//看一下EXTI14的中断标志位是否为1。返回值为SET或者RESET
if(EXTI_GetITStatus(EXTI_Line14)==SET){
//执行中断程序代码
//中断程序结束后一定要调用一下清除中断标志位的函数,因为中断标志位为1,程序就会跳转到中
//断函数,不清除中断标志位它就会一直申请中断
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
在中断内最好不要执行耗时过长的代码 ,中断函数要简短快速。另外,最好不要在中断函数和主函数内调用相同的函数或者操作同一个硬件,尤其是硬件相关的函数;在实现功能时,在中断里操作变量或者标志位,当中断返回时再对变量进行操作,这样既能保证中断函数简短迅速,又能保证不产生冲突的硬件操作。
七,TIM定时器
STM32中功能最强大,结构最复杂的外设之一——定时器
- 分为以下四个部分:
1,定时器基本定时功能:定一个时间,让定时器每隔这个时间触发一次中断,实现每隔一段时间执行一段程序。定时器可以对输入的时钟进行计数,并在计数值到达设定值时触发中断,定时器实际上就是一个计数器。在STM32中定时器的基准时钟一般都是主频72MHz(如果对72MHz计72个数,那么1MHz也就是1us的时间)
2,定时器输出比较功能:最常见用途就是产生PWM波形,用于驱动电机等设备
3,定时器输入捕获功能:使用输入捕获这个模块来实现测量方波频率的例子
4,定时器编码器接口:使用这个编码器接口,能够更加方便地读取正交编码器的输出波形
STM32拥有16位计数器,预分频器(16位),自动重装寄存器(16位)的时基单元,在72MHz计数时钟下可以实现最大59.65s地定时。这里计数器就是用来执行计数定时的一个寄存器,每来一个时钟,计数器加一;预分频器可以对计数器的时钟进行分频,让计数更灵活;自动重装寄存器就是计数的目标值,就是我想要计多少个时钟申请中断。上述三个部分构成了定时器最核心的部分,称为时基单元。如果预分频器设置最大,自动重装也设置最大,那定时器最大定时时间为59.65s,接近一分钟(72Mhz/65535/65535,得到中断频率,再取倒数)。除此之外,STM32定时器支持级联模式,也就是一个定时器输出作为另一个定时器输入,这样加一起,最大定时时间就是59.65X65535X65535。
1,定时器分类
STM32不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能。根据复杂度和应用场景,分为高级定时器,通用定时器和基本定时器。如下图,高级定时器连接着性能更高的APB2总线。高级定时器额外功能主要是为三相无刷电机驱动设计的
STM32F103C8T6定时器资源为TIM1,TIM2,TIM3,TIM4,也就是一个高级定时器和三个通用定时器,无基本定时器 。
(1)基本定时器
其中下面的自动重装载寄存器,计数器和预分频器最重要,预分频器之前,连接的就是基准计数时钟的输入,由于基本定时器只能选择内部时钟,所以可以认为上面的线直接连接到了内部时钟CK_INT,内部时钟来源为RCC_TIMxCLK,这里的频率值一般都是系统的主频72MHz。预分频器可以对72MHz的计数时钟进行预分频,比如预分频器这个寄存器写0就是不分频(输出频率等于输入频率=72MHz),写1就是二分频(输出频率等于输入频率/2=36MHz),写2就是三分频,以此类推,所以预分频器的值和实际分频系数相差为1.由于这个预分频器是16位的,所以最大值可以写65535.也就是65536分频。自动重装寄存器用于存储目标值,当计数值等于自动重装寄存器的目标值,也就是计时时间到了,计数器就会产生一个中断信号并且清零计数器。像这种计数值等于自动重装值产生的中断我们一般把它叫做更新中断,这个更新中断之后就会通往NVIC,我们再配置好NVIC定时器通道,那定时器更新中断就可以得到CPU响应了。
除了上面说到的定时中断原理,主模式触发DAC功能(主从触发模式,它能让内部硬件在不受程序的控制下实现自动运行)。主模式触发DAC:在我们使用DAC时,可能会用DAC输出一段波形,那就需要每隔一段时间触发一次DAC,让它输出下一个电压点;正常思路是使用定时器中断,但是这样会使主程序处于频繁被中断的状态,影响主程序运行和其他中断响应,所以定时器就设计了一个主模式,使用这个主模式可以把定时器的更新事件映射到触发输出TRGO的位置,然后TRGO直接接到DAC的触发转换引脚上,这样定时器的更新就不需要再通过中断来触发DAC转换了,整个过程不需要软件参与,实现硬件的自动化,这就是主模式的作用。
(2)通用定时器
中间最核心部分还是时基单元,结构与基本定时器一样。但是通用定时器计数方式不止向上计数(从0开始增加直到记到重装值)一种,还支持向下计数模式和中央对齐模式(先从0递增,记到重装值,申请中断,然后在向下自减,减到0,申请中断),常用向上计数模式。
时基单元上面的部分就是内外时钟源选择和主从触发模式的结构,通用定时器时钟源不仅可以选择内部的72MHz的时钟,还可以选择外部时钟,第一个外部时钟就是来自TIMx_ETR引脚上的外部时钟,ETR引脚可以在引脚定义图中找到,如下。这里我们选择TIM2_ETR引脚,也就是PA0,在PA0上接一个外部方波时钟,然后配置一下内部的极性选择,边沿检测和预分频器电路,再配置一下滤波电路,滤波后的信号兵分两路,上面一路ETRF进入触发控制器,紧跟着就可以选择作为时基单元的时钟了,在STM32中,TIMx_ETR这一路也叫做"外部时钟模式2"。除了外部ETR引脚可以提供时钟外,下面还有一路TRGI可以提供时钟,这一路主要用作触发输入来使用,这个触发输入可以触发定时器的从模式(触发模式和从模式后续会讲到),本次讲的是触发输入作为外部时钟使用的情况,把TRGI当作外部时钟的输入来看,这一路就叫做"外部时钟模式1",通过这一路的外部时钟第一个就是ETR引脚的信号(ETR引脚既可以通过上面一路作为时钟,也可以通过下面一路当作时钟,这两种情况等价);第二个是ITR信号,这一部分的时钟信号是来自其他定时器的,从触发控制器右边可以看出,主模式的输出TRGO可以通过其他定时器,这个通向其他定时器就就接到了ITR引脚上了,ITR0~ITR3分别来自其他4个定时器的TRGO输出,具体连接方式可以查看手册,如下。通过ITR这一路就可以实现定时器级联,比如我们可以先初始化TIM3,然后使用主模式把它的更新事件映射到TRGO上,接着再初始化TIM2,这里选择ITR2(对应着TIM3的TRGO),然后再选择时钟为外部时钟模式1。第三个可以选择TI1F_ED(ED是边沿的意思),这里连接的是输入捕获单元的CH1引脚,也就是从CH1引脚获得时钟,ED是边沿也就是从这一路获得的时钟上升沿和下降沿均有效。最后还可以通过TI1FP1和TI2FP2获得,TI1FP1连接CH1引脚的时钟,TI2FP2连接CH2引脚的时钟。总结来说,外部时钟模式1的输入可以是ETR引脚,其他定时器,CH1引脚的边沿,CH1引脚和CH2引脚。对于时钟输入而言最常用的还是内部的72MHz,如果要使用外部时钟首选ETR引脚外部时钟模式2的输入,其他输入是为了满足特殊场景设立的,比如T1FP1,T2FP2,TI1F_ED为了测频率,输入捕获而设计
通用定时器下面部分共分为两块电路
红框内为输出比较电路,总共4个通道,分别对应CH1到CH4的引脚,可以用于输出PWM波形和驱动电机
蓝框内是输入捕获电路,也是四个通道,对应CH1到CH4的引脚,可以用于测量输入方波的频率等
中间的比较/捕获寄存器是输入捕获和输出比较电路共用的。因为输入捕获和输出比较不能同时使用,所以这里的寄存器和引脚都是共用的。关于这部分电路后续讲解,本节讲定时中断和内外时钟源选择。
(3)高级定时器
高级定时器与通用定时器相比,左上红框部分一样,主要改动的是剩下的部分。
首先是蓝框所示部分,申请中断的地方增加了一个重复次数计数器,有了这个计数器之后,就可以实现每隔几个计数周期,才发生一次更新事件和更新中断,相当于对输出信号又做了一次分频,所以对于高级定时器来说,最大定时时间为59.65X65535。剩下的部分就是高级定时器对输出比较模块的升级。
DTG(Dead Time Generate)为死区生成电路,右边的输出引脚由原来的一个变为两个互补的输出,可以输出一对互补的PWM波,这些电路是为了驱动三相无刷电机的,因为三相无刷电机的驱动电路一般需要三个桥臂,每个桥臂2个大功率管控制,总共需要6个大功率管开关来控制,所以这里的输出PWM引脚的前三路就变成了互补的输出,而第四路没什么变化。为了防止互补输出的PWM驱动桥臂时,在开关切换的瞬间可能因为器件的不理想造成短暂的直通现象,所以前面加上了死区生成电路,在开关切换的瞬间,产生一定时长的死区,让桥臂上下管全都关断,防止直通现象。
最后一部分绿框内就是刹车输入功能,这个是为了给电机驱动提供安全保障的,如果外部引脚TIMx BKIN产生了刹车信号或者内部时钟失效产生了故障,那么控制电路就会自动切断电机的输入,防止意外发生。
2,定时中断基本结构
运行控制:控制寄存器的一些位,比如启动停止,向上或向下计数等。时基单元左边是为其提供时钟的部分,而可以选择RCC提供的内部时钟,也可以选择ETR引脚提供的外部时钟2,也可以选择触发输入作为外部时钟,即外部时钟模式1,还有一个编码器模式,一般是编码器独用的模式,普通时钟用不到这个。
产生的中断信号会在状态寄存器里置一个中断标志位,这个中断会通过中断输出控制,到NVIC中申请中断。因为定时器模块有很多地方都要申请中断(可以看定时器结构图),这些中断都要经过中断输出控制,如果需要这个中断就允许,否则禁止,简单来说,中断输出控制就是一个中断输出的允许位