stm32中的TIM

总结stm32中的TIM外设

概述

在stm32f103中有三种TIM,通用TIM、基本TIM、高级TIM。具体如下:

基本定时器

这种定时器是最简单的定时器,其结构简图如下:

在stm32F10x系列的芯片中基本定时器包括:

  • TIM6
  • TIM7

基本定时器TIM6和TIM7各包含一个16位自动装载计数器,由各自的可编程预分频器驱动。

它们可以作为通用定时器提供时间基准,特别地可以为数模转换器(DAC)提供时钟。实际上,它们在芯片内部直接连接到DAC并通过触发输出直接驱动DAC。

如何计时的:
我们把定时器设置自动重装载寄存器 ARR 的值为 1000,设置时钟预分频器为 71, 则驱动计数器的时钟:CK_CNT = CK_INT / (71+1)=1M, 则计数器计数一次的时间等于:1/CK_CNT=1us,当计数器计数到 ARR 的值 1000时, 产生一次中断,则中断一次的时间为:1/CK_CNT*ARR=1ms。

基本定时器编程

初始化结构体定义:

1
2
3
4
5
6
7
typedef struct {
TIM_Prescaler // 都有
TIM_CounterMode // TIMx,x[6,7]没有,其他都有
TIM_Period // 都有
TIM_ClockDivision // TIMx,x[6,7]没有,其他都有
TIM_RepetitionCounter // TIMx,x[1,8,15,16,17]才有
} TIM_TimeBaseInitTypeDef;

其中 TIM15/16/17 只存在与互联型产品中,在 F1 大/中/小容量型号中没有。

高级定时器

**高级控制定时器(TIM1和TIM8)**和通用定时器在基本定时器的基础上引入了外部引脚,可以实现输入捕获和输出比较功能。 高级控制定时器比通用定时器增加了可编程死区互补输出、重复计数器、带刹车(断路)功能,这些功能都是针对工业电机控制方面。

高级控制定时器时基单元包含

  • 一个16位自动重装载寄存器ARR
  • 一个16位的计数器CNT,可向上/下计数
  • 一个16位可编程预分频器PSC

预分频器时钟源有多种可选,有内部的时钟、外部时钟。还有一个8位的重复计数器RCR,这样最高可实现40位的可编程定时。

想要搞懂高级定时器就一定需要看懂其框图,如下:

这个框图中包含了高级定时器中所有功能的实现逻辑。

高级定时器的时钟源

高级定时器时钟来源有:

  • 内部时钟源CK_INT
  • 内部触发输入ITRx
  • 外部时钟输入一:外部输入引脚TIx(x=1 2 3 4)
  • 外部时钟输入二:外部触发输入ETR

内部时钟源

内部时钟CK_INT即来自于芯片内部,等于72M,一般情况下,我们都是使用内部时钟。当从模式控制寄存器TIMx_SMCR的SMS位等于000时,则使用内部时钟。

内部触发输入

内部触发输入是使用一个定时器作为另一个定时器的预分频器。硬件上高级控制定时器和通用定时器在内部连接在一起, 可以实现定时器同步或级联。主模式的定时器可以对从模式定时器执行复位、启动、停止或提供时钟。

外部时钟输入一

外部时钟输入二

如何使用–模式

使用TIM其实就是使用其不同的模式,TIM提供了下列模式:

  • 输出比较模式
  • 输入捕获模式
  • 编码器接口模式
  • PWMI(PWM输出模式)
  • PWM模式
  • 强制输出模式

下面我们详细讲一讲这些模式的电路图和使用方法,注意这些内容都在用户手册中有定义

输出比较模式

输出比较模式的工作原理:

即使根据CNTCCR之间的大小关系来输出高低电平。通过配置TIMx_CCMR1寄存器。输出比较模式又可以选择八种模式,这个八种模式在下面的图中可以找到其中PWM模式1是输出比较模式中最常用的模式之一,主要用来生成PWM波形,用以驱动电机。

首先我们来看看这个模式的框图:

通用TIM框图

高级TIM框图

可以看到图中有一个输出模式控制器,我们可以通过TIMx_CCMR1这个寄存器来对其进行控制,可以选择的模式如下:

我们使用最多的就是PWM模式,实际上PWM模式1和2是一样的。

输出PWM波形的数据计算公式:


注意,对于输出比较模式来说:

  1. 占空比永远是 50%
  2. 周期由 ARR 决定
  3. 相位由 CCR 决定

其中CK_PSC就是计数器时钟,一般是72Mhz。看你选的是哪个时钟作为其时钟源。TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的信号。

输入捕获模式

这个模式主要被用来测量PWM的各种参数–占空比、频率、分辨率等等参数

编码器接口模式

编码器接口模式的工作原理

根据下图中的两个引脚的输入,自动控制CNT计数器的自增、自减。以此来判断编码器的位置、速度、旋转方向。如何根据引脚的输入来判断计数器是加还是减:


一般我们使用的有效边沿是在TI1和TI2计数。

根据上面的图,编码器接口处有两根线,受到了两个不同的引脚的控制。 为什么需要受控于两个不同的引脚,实际上这有利消除信号源的毛刺(噪声):

根据表格,可以查出当出现了毛刺的时候,计数器中的值实际上是没有改变的。这个消除了由于毛刺带来的误差。

并且编码器接口模式是使用了输入滤波器和边沿检测器(实际上这里是极性选择,而不是边沿检测)

编码器接口模式特点如下:

  • 编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度
  • 两个输入引脚借用了输入捕获的通道1和通道2
  • 这个模式只有高级定时器和通用定时器才会有一个,所以普通定时器是没有这个模式的。

编码器接口模式基本上相当于使用了一个带有方向选择的外部时钟。这意味着计数器只在0到TIMx_ARR寄存器的自动装载值之间连续计数(根据方向,或是0到ARR计数,或是ARR到0计数)。所以在开始计数之前必须配置TIMx_ARR;同样,捕获器、比较器、预分频器、重复计数器、触发输出特性等仍工作如常。、

例子

在手册中有一个列子。我们通过学习这个例子可以知道这个模式大致如何使用。

根据上图中的配置步骤,我们可以知道使用该模式需要做:

  1. GPIO配置
  2. 选择两个通道的极性(即是否反向)
  3. 配置有效边沿
  4. 使能计数器

下面,我们使用标准库来进行编程。

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
void Encoder_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //开启TIM3的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟

/*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(GPIOA, &GPIO_InitStructure); //将PA6和PA7引脚初始化为上拉输入

/*时基单元初始化*/
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 = 1 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元

/*输入捕获初始化*/
TIM_ICInitTypeDef TIM_ICInitStructure; //定义结构体变量
TIM_ICStructInit(&TIM_ICInitStructure); //结构体初始化,若结构体没有完整赋值
//则最好执行此函数,给结构体所有成员都赋一个默认值
//避免结构体初值不确定的问题
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择配置定时器通道1
TIM_ICInitStructure.TIM_ICFilter = 0xF; //输入滤波器参数,可以过滤信号抖动
TIM_ICInit(TIM3, &TIM_ICInitStructure); //将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; //选择配置定时器通道2
TIM_ICInitStructure.TIM_ICFilter = 0xF; //输入滤波器参数,可以过滤信号抖动
TIM_ICInit(TIM3, &TIM_ICInitStructure); //将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道

/*编码器接口配置*/
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
//配置编码器模式以及两个输入通道是否反相
//注意此时参数的Rising和Falling已经不代表上升沿和下降沿了,而是代表是否反相
//此函数必须在输入捕获初始化之后进行,否则输入捕获的配置会覆盖此函数的部分配置

/*TIM使能*/
TIM_Cmd(TIM3, ENABLE); //使能TIM3,定时器开始运行
}

其中,最主要的函数TIM_EncoderInterfaceConfig()主要三个参数的含义是:

  • 第一个,选择是哪个TIM
  • 第二个,选择通道一的极性
    • @arg TIM_ICPolarity_Falling: IC Falling edge.选这个就是不反向,反之反向
    • @arg TIM_ICPolarity_Rising: IC Rising edge.
  • 第三个,选择通道二的极性

其实这个函数的最后面的两个参数在配置TIM_ICInitTypeDef结构体的时候就可以初始化了。在这个函数中初始化也是一样的效果,这就是为什么没有在TIM_ICInitTypeDef中初始化它。

对此我们可以总结编码器接口模式下的配置图:

编程相关

在标准库函数头文件stm32f10x_tim.h中对定时器外设建立了四个初始化结构体,分别为:

  • 时基初始化结构体TIM_TimeBaseInitTypeDef
  • 输出比较初始化结构体TIM_OCInitTypeDef
  • 输入捕获初始化结构体TIM_ICInitTypeDef
  • 断路和死区初始化结构体TIM_BDTRInitTypeDef

高级控制定时器可以用到所有初始化结构体,通用定时器不能使用TIM_BDTRInitTypeDef结构体, 基本定时器只能使用时基结构体。

时基初始化结构体

1
2
3
4
5
6
7
typedef struct {
uint16_t TIM_Prescaler; // 预分频器
uint16_t TIM_CounterMode; // 计数模式
uint32_t TIM_Period; // 定时器周期
uint16_t TIM_ClockDivision; // 时钟分频
uint8_t TIM_RepetitionCounter; // 重复计算器
} TIM_TimeBaseInitTypeDef;

需要注意的是,根据TIM种类的不同,时基结构体中的成员是不一样的:

  • 基本TIM类型中,只有时基结构体中的预分频器和自动重载值,就是上面的定时器周期
  • 通用TIM类型中,
1
2
3
4
5
6
7
typedef struct
{ TIM_Prescaler 都有
TIM_CounterMode TIMx,x[6,7]没有,其他都有
TIM_Period 都有
TIM_ClockDivision TIMx,x[6,7]没有,其他都有
TIM_RepetitionCounter TIMx,x[1,8,15,16,17]才有
}TIM_TimeBaseInitTypeDef;

输出比较结构体

输出比较结构体TIM_OCInitTypeDef用于输出比较模式,与TIM_OCxInit函数配合使用完成指定定时器输出通道初始化配置。高级控制定时器有四个定时器通道,使用时都必须单独设置。

1
2
3
4
5
6
7
8
9
10
typedef struct {
uint16_t TIM_OCMode; // 比较输出模式
uint16_t TIM_OutputState; // 比较输出使能
uint16_t TIM_OutputNState; // 比较互补输出使能
uint32_t TIM_Pulse; // 脉冲宽度
uint16_t TIM_OCPolarity; // 输出极性
uint16_t TIM_OCNPolarity; // 互补输出极性
uint16_t TIM_OCIdleState; // 空闲状态下比较输出状态
uint16_t TIM_OCNIdleState; // 空闲状态下比较互补输出状态
} TIM_OCInitTypeDef;

具体的含义如下:

(1) TIM_OCMode: 比较输出模式选择,总共有八种,常用的为PWM1/PWM2。它设定CCMRx寄存器OCxM[2:0]位的值。

(2) TIM_OutputState: 比较输出使能,决定最终的输出比较信号OCx是否通过外部引脚输出。它设定TIMx_CCER寄存器CCxE/CCxNE位的值。

(3) TIM_OutputNState: 比较互补输出使能,决定OCx的互补信号OCxN是否通过外部引脚输出。它设定CCER寄存器CCxNE位的值。

(4) TIM_Pulse: 比较输出脉冲宽度,实际设定比较寄存器CCR的值,决定脉冲宽度。可设置范围为0至65535。

(5) TIM_OCPolarity: 比较输出极性,可选OCx为高电平有效或低电平有效。它决定着定时器通道有效电平。它设定CCER寄存器的CCxP位的值。

(6) TIM_OCNPolarity: 比较互补输出极性,可选OCxN为高电平有效或低电平有效。它设定TIMx_CCER寄存器的CCxNP位的值。

(7) TIM_OCIdleState: 空闲状态时通道输出电平设置,可选输出1或输出0,即在空闲状态(BDTR_MOE位为0)时,经过死区时间后定时器通道输出高电平或低电平。它设定CR2寄存器的OISx位的值。

(8) TIM_OCNIdleState: 空闲状态时互补通道输出电平设置,可选输出1或输出0,即在空闲状态(BDTR_MOE位为0)时,经过死区时间后定时器互补通道输出高电平或低电平, 设定值必须与TIM_OCIdleState相反。它设定是CR2寄存器的OISxN位的值。

输入捕获结构体

输入捕获结构体TIM_ICInitTypeDef用于输入捕获模式,与TIM_ICInit函数配合使用完成定时器输入通道初始化配置。 如果使用PWM输入模式需要与TIM_PWMIConfig函数配合使用完成定时器输入通道初始化配置。

1
2
3
4
5
6
7
typedef struct {
uint16_t TIM_Channel; // 输入通道选择
uint16_t TIM_ICPolarity; // 输入捕获触发选择
uint16_t TIM_ICSelection; // 输入捕获选择
uint16_t TIM_ICPrescaler; // 输入捕获预分频器
uint16_t TIM_ICFilter; // 输入捕获滤波器
} TIM_ICInitTypeDef;

断路和死区结构体

断路和死区结构体TIM_BDTRInitTypeDef用于断路和死区参数的设置,属于高级定时器专用,用于配置断路时通道输出状态,以及死区时间。 它与TIM_BDTRConfig函数配置使用完成参数配置。这个结构体的成员只对应BDTR这个寄存器

1
2
3
4
5
6
7
8
9
typedef struct {
uint16_t TIM_OSSRState; // 运行模式下的关闭状态选择
uint16_t TIM_OSSIState; // 空闲模式下的关闭状态选择
uint16_t TIM_LOCKLevel; // 锁定配置
uint16_t TIM_DeadTime; // 死区时间
uint16_t TIM_Break; // 断路输入使能控制
uint16_t TIM_BreakPolarity; // 断路输入极性
uint16_t TIM_AutomaticOutput; // 自动输出使能
} TIM_BDTRInitTypeDef;

stm32中的TIM
https://ysc2.github.io/ysc2.github.io/2023/12/27/stm32中的TIM/
作者
Ysc
发布于
2023年12月27日
许可协议