跳转至

STM32 中断系统

TIM 定时中断

TIM 简介

TIM (Timer) 定时器

  • 定时器可以对输入的时钟进行计数,并在数值达到设定值的时候触发中断
  • 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时。
  • 不仅具备基础的定时中断功能,还包括内外时钟源选择输入捕获输出比较编码器接口主从触发等多种功能。
  • 根据复杂程度和应用场景分为了:高级定时器通用定时器基本定时器三种类型。

TIM 定时器在 STM32 中有三种类型

类型 编号 总线 功能
高级定时器 TIM1、TIM8 APB2 拥有通用定时器的全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能
通用定时器 TIM2、TIM3、TIM4、TIM5 APB1 拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能
基本定时器 TIM6、TIM7 APB1 拥有定时中断、主从触发DAC等功能
  • STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4

时基单元

时基单元是TIM定时器最基本的组成

时基单元由:PSC 预分频寄存器、ARR 自动重装寄存器、CNT 计数器组成。

  • PSC :可以对输入的时钟信号进行预分频,用以为CNT提供脉冲信号进行计数;PSC置1则可实现2分频,置2则为3分频,以此类推。
  • CNT :经过PSC分频后输入的信号中每经过一个下降沿,CNT就会+1;最大上限为\(2^{16}\)
  • ARR :通过比较ARR设定值与CNT的差距,如果ARR的值等于CNT的值,则输出更新中断或者更新事件

时基单元的时钟来源

时基单元的输入时钟可以由不同的设备提供。

  • 内部时钟模式:此模式下,时钟信号由内部RCC总线提供时钟。
  • 外部时钟模式2\外部时钟模式1:ETR 外部时钟
  • 外部时钟模式1:ITRx 其它定时器: 可以通过配置时基单元的更新事件映射到下一个计数器的时钟输入,从而实现定时器的级联,进而扩大定时器的最大计数时间。
  • 编码器模式\外部时钟模式1:TIx 捕获通道

配置定时器中断

开启RCC时钟

 开启通用定时器TIM2的总线时钟,由于TIM2是挂载在APB1总线上的,因此需要开启APB1的总线时钟。

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

选择时基单元的时钟源

选择TIM定时器的时钟输入源,有如下两种选择。

 在定时器中,上电后如果不对此步骤进行配置,那么默认使用内部时钟;因此该条语句也可以不写。

TIM_InternalClockConfig(TIM2);  //TIM2的时基单元由内部时钟驱动

通过此函数可以将其它时钟源配置为时基单元的输入。

TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_Inverted,0x00);  //设置外部输入模式2
TIM_ETRClcokMode2Config的三个参数

选择TIM线路

设置分频系数。

  • TIM_ExtTRGPSC_OFF
  • TIM_ExtTRGPSC_DIV2
  • TIM_ExtTRGPSC_DIV4
  • TIM_ExtTRGPSC_DIV8
  • TIM_ExtTRGPolarity_Inverted 反向低电平或者下降沿有效。
  • TIM_ExtTRGPolarity_NonInverted 不反向高电平或者上升沿有效。

这个值必须是0x000x0F之间的一个值。

配置时基单元

使用TIM_TimeBaseInit函数来初始化时基单元

TIM_TimBaseInitTypeDef TIM_TimeBaseInitStructure;   //初始化结构体单元

TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //指定时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;   //计数器模式(向上计数模式)
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; //周期,ARR自动重装寄存器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;  //PSC预分频寄存器的值
// 以上两个PSC、ARR的取值范围都在0~65535之间
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;  //重复计数器的值(这里是高级寄存器拥有的功能,这里置0不用)

TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_CounterMode 配置计数器的几种模式

向上计数

TIM_CounterMode_Up

向下计数

TIM_CounterMode_Down

三种中央对齐模式

  • TIM_CounterMode_CenterAligned1
  • TIM_CounterMode_CenterAligned2
  • TIM_CounterMode_CenterAligned3

配置输出中断控制

开启TIM2的更新中断到NVIC的线路。

TIM_ClearFlag(TIM2,TIM_FLAG_Update);    //手动清楚一次中断标志位,规避一上电就触发一次中断事件的问题

TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

配置NVIC

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //选择NVIC分组

NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NCIV_IRQChannel = TIM2_IRQn;   //指定中断通道开启或者关闭
NVIC_InitStructure.NCIV_IRQChannelCmd = ENABLE;    //指定通道使能还是失能(ENABLE,DISABLE)
NVIC_InitStructure.NCIV_IRQChannelPreemptionPriority =  2;  //指定抢占优先级的优先级
NVIC_InitStructure.NCIV_IRQChannelSubPriority = 1;          //指定响应优先级的优先级

NVIC_Init(&NVIC_InitStructure);

时基单元 运行控制

 在TIM定时器配置完毕后,需要手动使能一下运行控制寄存器让定时器开始运行,否则定时器不会运行。

TIM_Cmd(TIM2,ENABLE);

TIM中断函数

void TIM2_IRQHandler (void){
    if (TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){    //判断中断标志位

        /*需要执行的代码*/

        TIM_ClearITPendingBit(TIM2,TIM_IT_Update);  //清楚中断标志位
    }

}

TIM输出比较

TIM输出比较 简介

OC (OutPut Compare) 输出比较

 输出比较可以通过比较CNTCCR寄存器值的关系,来输出电平进行置1、置0或者翻转的操作,用于输出一定频率和占空比的PWM波形

  • 每个高级定时器和通用定时器都拥有4个输出比较通道
  • 高级定时器的前3个通道额外拥有死区生成互补输出的功能。

输出比较输出PWM波

输出比较输出PWM的原理

  首先时基单元CNT在PWM输出开始的时候便不断自增运行,然后CNT的值会被送到CCR内进行比较,此时输出比较可以设定为两种模式。

CNT \(<\) CCR 触发的模式

此时模式设定为CNT \(<\) CCR的时候,REF置有效电平,也就是输出端口高电平

CNT \(\ge\) CCR

CNT \(\ge\) CCR的时候,REF置无效电平,也就是输出端口低电平


最后REF再通过极性选择输出使能最后输出到GPIO口上。

首先设CNT的值为 \(\alpha\),根据上述输出比较可以设定的两种模式有:

  • CNT \(<\) CCR 则 REF 置有效电平

  • CNT \(\ge\) CCR 则 REF 置无效电平

 那么设从程序开始运行到CNT \(=\) CCR需要的时间设为\(t_0\);CNT从0计数到ARR的值所需要的时间为\(t_1\)

  则在\(0 \sim t_0\)的时间内,是满足上面的条件的,也就是说,在\(t_0\)时间内,REF为有效电平,而在\(t_0\)时间之后,则会满足下面的条件,也就是在\(t_1 - t_0\)时间内,REF为无效电平,并且在经过\(t_1\)时间后会再次满足上面的条件。

  而此时则是一个占空比\(\frac{t_1 - t_0}{t_0}\),并且频率\(\frac{1}{t_1}Hz\)的PWM波。

而在TIM定时器输出PWM波形中,会影响PWM输出参数的量有三个。

  • ARR 自动重装分频器
  • PSC 预分频器
  • CCR 捕获/比较寄存器

因此可得如下计算式。

  • PWM的频率\(=\)TIM的更新频率 \(f = \frac{CK\_PSC}{(PSC + 1) \cdot (ARR + 1)}\)
  • 占空比 \(Duty = \frac{CCR}{ARR + 1}\)
  • PWM的分辨率 \(Reso = \frac{1}{ARR+1}\)

\(CK\_PSC\) :时钟频率


 这里如果CCR的值等于ARR,那么此时就是一个占空比为0%的PWM,此时如果CCR继续增大,则输出便没有意义,因此CCR的最大值取决于ARR的最大值,CCR取值只能在0到ARR之间,因此ARR的取值的倒数即使PWM的分辨率,这个参数可以影响占空比变化的步距,通常来讲,这个值越大越好。

配置输出PWM

开启RCC时钟

开启需要使用外设的时钟,包括TIMGPIO

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
选择时基单元的时钟源

选择内部时钟。

TIM_InternalClockConfig(TIM2);
配置时基单元
TIM_TimBaseInitTypeDef TIM_TimeBaseInitStructure;   //初始化结构体单元

TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //指定时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;   //计数器模式(向上计数模式)
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; //周期,ARR自动重装寄存器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;  //PSC预分频寄存器的值
// 以上两个PSC、ARR的取值范围都在0~65535之间
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;  //重复计数器的值(这里是高级寄存器拥有的功能,这里置0不用)

TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
配置输出比较单元
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure); //结构体TIM_OCInitStructure赋初值
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;   //设置输出比较模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;   //设置输出比较极性
TIM_OCInitStructure.TIM_OutPutState = TIM_OutputState_Enable;  //设置输出使能
TIM_OCInitStructureTIM_Pulse =;   //设置CCR
//这里CCR、PSC、ARR 三个量共同决定输出PWM的周期和占空比

TIM_OC3Init(TIM2,&TIM_OCInitStructure);

输出比较的几种模式---TIM_OCMode

TIM_OCMode_Timing

TIM_OCMode_Active

TIM_OCMode_Inactive

TIM_OCMode_Toggle

TIM_OCMode_PWM1

TIM_OCMode_PWM2

输出比较的几种模式---TIM_OCPolarity

TIM_OCPolarity_High

高极性,就是极性不翻转,REF波形直接输出

TIM_OCPolarity_Low

低极性,就是极性翻转,REF波形取反后输出

输出使能的几种模式---TIM_OutputState

TIM_OutputState_Disable

TIM_OutputState_Enable

配置GPIO

  将PWM输出的GPIO端口配置为复用推挽输出的模式,通过断开GPIO控制寄存器与GPIO引脚的连接,从而将GPIO控制权转移给片上外设。

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置GPIO输出模式为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOA,&GPIO_InitStructure);
配置时基单元运行控制

启动时基单元的运行控制就可以实现输出PWM波。

TIM_Cmd(TIM2,ENABLE);   //启动定时器

TIM 定时器输入捕获

IC (Input Capture) 输入捕获

 在输入捕获模式下,当通道输入引脚出现指定电平的跳变时,当前CNT的值将会锁存到CCR中,可用于测量PWM波形的频率占空比脉冲间隔电平持续时间等参数。

  • 每个高级定时器和通用定时器都拥有4个输入捕获通道。
  • 可配置为PWMI模式,同时测量频率占空比
  • 可配合主从触发模式,实现硬件全自动测量

  • 输入捕获测量PWM频率

通过测周法测量PWM频率

  首先外部信号通过配置好的GPIO引脚进入到输入捕获单元,输入捕获单元由滤波器、边沿检测极性选择,分频器,CCR组成。

其中,信号通过GPIO进入输入捕获单元后,流程如下。

  • 滤波器

信号通过滤波器进行滤波,滤除杂波以防止因杂波出现的判断失误。

  • 边沿检测和极性选择

 信号进入进入后,通过配置检测方式,上升沿、下降沿、双边沿等方式;每侦测到配置的信号沿之后便可以输出信号,同时极性选择可以配置输出信号的极性。

 边沿检测输出的信号通过数据选择器进入到分频器中进行分频,分频后的数据进入到CCR中触发CNT的值锁存到CCR中

 同时边沿检测输出的信号会进入主从触发器中,包含触发源选择和从模式,同时将从模式配置为Reset模式,将输出信号连接到时基单元中,可以做到每当边沿检测输出一次信号,时基单元就会自动清空数据。

 最后GPIO的信号通过输入捕获单元和时基单元加上主从触发器,共同实现了PWM输入频率的全自动硬件测量,该过程中既不占用单片机软件资源,也不会调用中断,只会在需要读取PWM频率的时候调用一次数据即可。

  • STM32输入捕获法测量PWM频率

开启RCC时钟

打开GPIO和TIM的RCC时钟。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIMx,ENABLE);

GPIO初始化

配置GPIO的模式为输入模式,一般选择上拉输入或者浮空输入

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;   //上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_x;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOx,&GPIO_InitStructure);

配置时基单元

让CNT计数器在内部时钟的驱动下自增运行。

选择内部时钟源:

    TIM_InternalClockConfig(TIMx);  //选择TIM内部时钟源

结构体配置时基单元参数:

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //指定时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;   //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;   //PSC 这个值决定了测周法的标准频率fc
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;

TIM_TimeBaseInit (TIMx,&TIM_TimeBaseInitStructure);

配置输入捕获单元

TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;    //选择需要使用的通道
TIM_ICInitStructure.TIM_ICFilter = 0xF; //配置输入捕获滤波器
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //边沿检测、极性选择(上升沿触发)
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;   //输入捕获单元预分频器(不分频)
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //选择触发信号从那个引脚输入(直连通道)

TIM_ICInit(TIM_SELECT,&TIM_ICInitStructure);
TIM_ICPolarity的几种参数

TIM_ICPolarity_Rising

TIM_ICPolarity_Falling

TIM_ICPolarity_BothEdge

TIM_ICSelection的几种参数

TIM_ICSelection_DirectTI

TIM_ICSelection_IndirectTi

TIM_ICSelection_TRC

选择从模式的触发源

触发源选择TI1FP1

TIM_SelectInputTrigger(TIMx,);
TIM_SelectInputTrigger 的8个触发源

NULL

NULL

NULL

NULL

NULL

NULL

NULL

NULL

选择触发后执行的操作

TIM_SelectSlaveMode(TIMx,);
TIM_SelectSlaveMode 的4种从模式

NULL

NULL

NULL

NULL

开启定时器

TIM_Cmd(TIM_SELECT,ENABLE);

获得频率

需要获得当前PWM波频率时,只需要读取CCR寄存器的值,然后按照\(\frac{f_c}{N}\)的公式计算即可。

通过以下函数即可返回当前输入频率:

uint16_t TIM_GetFreq (void){

    return 1000000 / TIM_GetCapture1(TIM_SELECT);

}

PWMI同时测量频率和占空比

 PWMI模式与上边的方法大致相同,但是需要在配置输入捕获单元的时候需要改成两个通道同时捕获一个引脚的模式。

也就是两个CCR同时对一个引脚进行计数。

 将上面代码中由TIM_ICInitTypeDef所定义的结构体配置下面用于初始化输入捕获单元的TIM_ICInit函数替换为如下函数。

即可实现将一个引脚的信号分配到两个通道上去。

TIM_PWMIConfig (TIM_SELECT,&TIM_ICInitStructure);

修改后的输入捕获配置代码如下。

TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;    //选择需要使用的通道
TIM_ICInitStructure.TIM_ICFilter = 0xF; //配置输入捕获滤波器
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //边沿检测、极性选择(上升沿触发)
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;   //输入捕获单元预分频器(不分频)
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //选择触发信号从那个引脚输入(直连通道)

TIM_PWMIConfig (TIMx,&TIM_ICInitStructure);

获取占空比的函数。


TIM 编码器接口

简介

Encoder Interface 编码器接口

编码器接口模式相当于使用了一个带有方向选择的外部时钟。

  • 编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT的自增或者自减,从而指示编码器的位置、旋转方向和旋转速度。

  • 每个高级定时器和通用定时器都拥有1个编码器接口。

  • 两个输入引脚借用了输入捕获的通道1和通道2。

初始化编码器接口

开启RCC时钟

开启GPIO和定时器的总线时钟。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);

配置GPIO模式

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;   //可以配置成上拉输入、下输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIO_SELECT,&GPIO_InitStructure);
配置时基单元

ARR给最大值,仅让CNT计数工作即可。

预分频器选择不分频。

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision =TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;

TIM_TimeBaseInit (TIM_SELECT,&TIM_TimeBaseInitStructure);
配置输入捕获单元
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;    //通道1
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM_SELECT,&TIM_ICInitStructure);

TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;    //通道2
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM_SELECT,&TIM_ICInitStructure);
配置编码器接口模式
TIM_EncoderInterfaceConfig(TIM_SELECT,TIM_EncoderMode_TI12,ICPolarity,ICPolarity);
TIM_EncoderMode的几种模式

仅在TI1计数

仅在TI2计数

TI1、TI2都计数

启动定时器
TIM_Cmd(TIM3,ENABLE);

频率测量的几种方式

测频法

 该方法适合测量高频信号,并且结果更新较慢,得到的数据时一段时间的平均值。

在闸门时间\(T\)内,也就是固定时间\(T\)内,对上升沿进行计次,得到\(N\)则可得频率为。

\(f = \frac{N}{T}\)

测周法

 该方法适合测量低频信号,出结果的速度取决于待测信号的频率,由于一次只测量一个周期,因此容易收到噪声的影响,波动较大。

在两个上升沿内,以标准频率\(f_c\)进行计次,得到N,则频率为。

\(f = \frac{f_c}{N}\)

中界频率

测频法与测周法误差相等的频率点

\(f_m = \sqrt{\frac{f_c}{T} }\)

中断编程

在编写中断函数的时候,出于程序的严谨性和代码的可读性应该注意以下内容。

  • 中断函数执行的代码尽可能简短,耗时少,因为中断是用于处理突发事件的,中断程序执行时间过长的话,主程序就会严重阻塞

  • 在主程序和中断程序中,尽量不要调用同一个硬件,因为硬件与软件相比,没有保护程序,由于中断是打断当前正在运行的程序,所以如果中断和主函数调用同一个硬件,那么可能会导致硬件工作出现问题。

  • 中断函数作为需要触发并且调用的函数,在中断事件初始化配置完成后,中断函数可以存在于程序的任何位置;通常将中断函数放在需要调用中断函数的位置,这样可以方便修改中断函数代码以及优化代码可读性。