目录
一、通用定时器介绍
在 整个STM32F10xxx系列中,通用定时器有:TIM2/3/4/5四个。它们拥有基本定时器所有的功能,并在此基础上增加了如下功能:
- 多种时钟源
- 向上计数、向下计数、向上/向下计数
- 输入捕获
- 输出比较
- PWM生成
- 支持针对定位的增量(正交)编码器和霍尔传感器电路
tips:
- IM1、TIM8 是高级定时器
- F103高容量系列和互联型系列还有 基本定时器 TIM6、TIM7
通用定时器功能框图

这个框图中也包含了基本定时器的相关结构:时基单元、触发控制器。在此基础上增加了时钟源、输入捕获、输出比较相关的硬件电路模块。
1.通用定时器的时钟来源
(1)内部时钟, 一般为72MHz
HSE外部晶振(8MHz)
→ PLL倍频(9倍频,8MHz×9=72MHz)
→ 系统时钟(SYSCLK=72MHz)
→ AHB总线预分频(默认1分频)
→ AHB时钟=72MHz
→ APB1总线预分频(默认2分频)
→ APB1时钟=36MHz
→ 定时器时钟倍频规则:若APB1预分频系数>1,则定时器时钟=APB1时钟×2;若=1,则等于APB1时钟
→ 最终通用定时器内部时钟=36MHz×2=72MHz
(2)外部时钟源模式1
使用定时器自身通道的输入信号作为时钟源,每个定时器有4个输入通道,在 STM32F1xx 系列通用定时器 中。
外部时钟源模式 1(External Clock Mode 1)仅支持使用通道 1(TI1)和通道 2(TI2)的信号作为触发输入 , 不支持通道 3、4 。

(3)外部时钟源模式2
使用定时器的特殊引脚ETR引脚的信号作为时钟源
大容量芯片(STM32F103ZET6等系列)中每个通用定时器都有一个ETR引脚,比如TIM3定时器的ETR引脚是PD2。
中小容量(C8T6)TIM3_ETR 不存在 PD2! C8T6 只有 TIM2_ETR=PA0、TIM4_ETR=PE0。
ETR引脚信号经过极性选择、边缘检测、预分频器、输入滤波得到ETR信号,传递给触发控制器。


(4)内部触发输入
使用一个定时器作为另一个定时器的预分频器

2.外部时钟源的功能
- 一般用于定时器的级联
- 大部分情况下,内部时钟源足够使用
- 不配置时钟源的情况下,默认选择的就是内部时钟源
二、计数器的3种计数模式
1.向上计数
从0开始增加,一直加到自动重装载寄存器的值
2.向下计数
从自动重装载寄存器的值开始计数,直至减到0
3.中央对齐模式
从0开始向上计数,一直计数到自动重装载寄存器的值-1
再来一个时钟信号会产生更新事件,然后继续从自动重装载寄存器的值向下计数

向上计数模式就可以覆盖大部分应用场景,芯片默认计数方向都是向上计数。
三、PWM介绍
PWM(pulse-width modulation,脉冲宽度调制)
简称脉宽调制,通过调整信号中的高电平的占比时间(占空比)来调整输出功率的大小,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。
STM32中通用定时器的脉冲宽度调制模式可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的信号。
PWM通常用于 控制电机转速 、 LED亮度调节 等应用。要求被控制的电路必须要有一定的 惯性。
所谓惯性指的是电路中的 负载的状态不会发生突变。比如电机断电了也不会立即停止。LED灯是利用了人类眼睛的余晖(视觉暂留)效应(大概是0.1s)。
注意:PWM并不等同于数模转换。
1.PWM的3个参数
(1)周期
连续的两个上升沿或连续的两个下降沿之间的宽度,用T表示。
(2)频率
周期的倒数
(3)占空比
高电平宽度t除以周期T。占空比决定了输出功率的大小。
在使用PWM驱动惯性电器时,频率和周期确定好之后一般不再改变,通过改变占空比来调整输出功率。
五、使用通用定时器输出PWM波形
通用定时器的功能很强大,针对不同功能配置了众多的寄存器,我们在学习时可以按照功能来学习寄存器,这样更有利于对通用定时器的掌握。以下是使用通用定时器产生PWM波形的相关寄存器。
1. 控制计数方向
控制寄存器1(TIMx_CR1)
DIR:(direction)方向控制位
用于控制计数方向,0:向上计数;1:向下计数。当计数模式为中央对齐模式或编码器模式时,该位为只读。


2. 配置通道方向和输出比较模式
每个定时器有4个输入输出通道,4个通道每个通道由一个CCR寄存器控制,可以同时实现4路比较,下图是4路输入输出的系统框图:

使用定时器的输入输出功能时,GPIO端口配置成复用推挽输出模式即可,因为在输出模式下,输入功能也是打开的。下图是STM32中TIM3定时器的输入输出引脚对应表。

通用定时器中的通道可用于输入(捕获模式)或输出(比较模式),通道的方向由相应的CCxS定义。
CCxS的不同,会导致CCMR1寄存器不同控制位的作用发生变化,在使用时需要特别注意。
2.1.捕获/比较模式寄存器1(TIMx_CCMR1)

2.2.输出比较模式:CCxS为00

2.3.输入捕获模式:CCxS不为00

3.通用定时器的输出比较功能
通用定时器的输出比较功能主要是用来控制输出方波的,大多数时候都是用于输出PWM方波。当然也可以输出其他波形。但是只能是方波。
主要通过控制CCR寄存器的值实现方波的占空比。

输出方波的基本原理:
以通道1为例:
- 假设计数器向上计数,重装载寄存器的值为99(计数100后溢出)
- 假设捕获/比较寄存器的值为60
- 比较寄存器的值和计数器的值进行大小比较
- 根据比较结果(> = < )不同,产生不同输出:高电平或低电平
3.1.输出比较模式下的比较值
在PWM模式1的工作模式下,CCR的值就代表了占空比的值。
捕获比较寄存器1(TIMx_CCR1)


4.输出比较的8种模式
由CCMR1寄存器的3个控制位OC1M[2,0]控制。假设计数器的值是CNT,比较寄存器1的值是CCR = 60。
模式1:OC1M[2,0]:000 输出冻结,啥都不输出,与比较结果无关。
模式2:OC1M[2,0]:001 强制输出高电平。一旦CNT==CCR,强制输出高电平,再也不改变了。
模式3:OC1M[2,0]:010 强制输出低电平,一旦CNT==CCR,强制输出低电平,再也不改变了。
模式4:OC1M[2,0]:011 输出翻转,一旦CNT==CCR,则翻转输出。高->低,低->高。

这个时候,得到的方波是一个占空比为50%的方波,这是为什么呢?为什么不是60%?
原因是:
电平每次发生翻转都是在CNT == CCR的时候,这个周期是固定的。
输出的波形是溢出频率的一半。
这种模式可以用于调整相位。
模式5:OC1M[2,0]:100 强制输出低电平,与比较结果无关。
模式6:OC1M[2,0]:101 强制输出高电平,与比较结果无关。
模式7:OC1M[2,0]:110 PWM模式1
CNT < CCR 输出高电平
CNT >= CCR 输出低电平
输出波形如下:

输出占空比为60%的PWM方波
溢出频率等于PWM频率
周期也相同
模式8:OC1M[2,0]:111PWM模式2
CNT < CCR 输出低电平
CNT >= CCR 输出高电平

周期等于溢出周期
频率等于溢出率
占空比为40%
最常用的是模式7,OC1M[2,0]:110,这个时候占空比和CCR值是对应的。
5.配置通道极性和使能
捕获/比较使能寄存器(TIMx_CCER)


CC1P输入输出捕获1输出极性:
为0:OC1高电平有效
为1:OC1低电平有效
影响的是PWM模式时的通道输出的有效电平,严格来讲,PWM模式输出低电平有效更为合理,因为是低电平点亮LED。
6.通用定时器寄存器映像表

六、代码编写
以下代码基于STM32F103C8T6芯片实现,使用芯片的GPIOA1引脚连接LED实现PWM驱动LED实现呼吸灯效果。
文件名:tim2.h
cpp
#ifndef __TIME2_H
#define __TIME2_H
#include "stm32f10x.h"
// 函数声明
// 初始化
void TIM2_Init(void);
// 开启定时器,开始输出PWM方波
void TIM2_Start(void);
// 停止定时器,停止输出PWM方波
void TIM2_Stop(void);
// 设置PWM占空比
void SET_DutyCycle(uint8_t dutycycle);
#endif
文件名:tim2.c
cpp
#include "tim2.h"
// 初始化
void TIM2_Init(void)
{
// 1.开启时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
// 2.设置GPIOA1引脚的工作模式,PWM方波输出,引脚需要工作在复用功能推挽输出模式:CNF-10;MODE-11;
GPIOA->CRL &= GPIO_CRL_CNF1_0;
GPIOA->CRL |= GPIO_CRL_CNF1_1;
GPIOA->CRL |= GPIO_CRL_MODE1;
// 3.设置TIM2定时器,为了使得LED刷新频率可以达到合适的效果,需要考虑人眼的余晖效应,这里将频率定为100Hz
// 3.1.设置定时器的输入频率,假设ARR的值为99(100次计数),输出100Hz的频率,在输入的时钟频率为72MHz的前提下,PSC的值的计算过程为:
// 输出频率 = 1 / [(1 / (定时器输入时钟频率 / (PSC+1))) * (ARR + 1)]
// 由频率计算公式推到出周期计算公式:
// 输出时钟周期 = (1 / (定时器输入时钟频率 / (PSC+1))) * (ARR + 1)
// 已知:输如频率 = 72MHz ARR+1 = 100; 计算PSC
// 1/100Hz = (1 / (72MHz / (PSC+1))) * 100
// 72MHz / (PSC+1) = 100Hz * 100
// 72MHz / (100Hz * 100) = (PSC+1)
// PSC+1 = 7200
// PSC = 7199
// 不开启预装载功能(默认)
TIM2->CR1 &= ~TIM_CR1_ARPE;
TIM2->PSC = 7199;
// 3.2.设置预装载寄存器的值,为了方便调整PWM的占空比,这个值设定为99,也就是计数100次后溢出。
TIM2->ARR = 99;
// 3.3.设置定时器的计数方向,0代表向上计数
TIM2->CR1 &= ~TIM_CR1_DIR;
// 3.4.设置定时器的通道方向,输出,CCxS-00
TIM2->CCMR1 &= ~TIM_CCMR1_CC2S;
// 3.5.配置定时2器通道2的输出模式:PWM模式1
TIM2->CCMR1 &= ~TIM_CCMR1_OC2M_0;
TIM2->CCMR1 |= TIM_CCMR1_OC2M_1;
TIM2->CCMR1 |= TIM_CCMR1_OC2M_2;
// 3.6.配置一个默认的占空比:99%,因为LED是共阳,因此占空比高代表LED暗。
TIM2->CCR2 = 99;
// 3.7.开启定时器通道输出使能
TIM2->CCER |= TIM_CCER_CC2E;
}
// 开启定时器,开始输出PWM方波
void TIM2_Start(void)
{
TIM2->CR1 |= TIM_CR1_CEN;
}
// 停止定时器,停止输出PWM方波
void TIM2_Stop(void)
{
TIM2->CR1 &= ~TIM_CR1_CEN;
}
// 设置PWM占空比
void SET_DutyCycle(uint8_t dutycycle)
{
TIM2->CCR2 = dutycycle;
}
文件名:main.c
cpp
#include "tim2.h"
#include "delay.h"
int main(void)
{
// 初始化定时器2
TIM2_Init();
// 开启定时器
TIM2_Start();
// 定义PWM占空比变化的方向,0代表向上计数,1代表向下计数
uint8_t dir = 0;
// 定义占空比的初始值
uint8_t dutycycle = 99;
while(1)
{
// 确定方向
if(dutycycle == 99)
{
dir = 1;
}
else if (dutycycle == 0)
{
dir = 0;
}
// 占空比变化
if(dir == 0)
{
dutycycle++;
}
else
{
dutycycle--;
}
// 占空比调整
SET_DutyCycle(dutycycle);
Delay_nms(20);
}
}