STM32中TIM定时器与PWM功能详解
类型 | 使用编号 | 总线 | 描述 |
基本定时器 | TIM6、TIM7 | APB1 |
1,主要部分是一个带有自动重装载的 2,时基单元包含: 计数器寄存器【TIMx_CNT, Count Up Timer】 预分频寄存器【TIMx_PSC, Prescaler】 自动重装载寄存器【TIMx_ARR, Auto-Reload Register】 3,计数器时钟由内部时钟【CK_INT, Clock Internal |
通用定时器 |
TIM2、TIM3 TIM4、TIM5 |
APB1 | |
高级定时器 | TIM1、TIM8 | APB2 |
STM32F103C8T6定时器资源:只有一个高级定时器 TIM1 和三个通用定时器 TIM2、TIM3、TIM4。没有基本定时器。
1、基本定时器

时钟:单片机时钟电路可以分为内部时钟和外部时钟两种类型。内部时钟:单片机内部集成了一个时钟发生器,能够产生稳定的时钟信号。外部时钟:单片机需要外部连接一个时钟源,例如晶体振荡器或者外部时钟信号发生器。
基本定时器的时基单元有三部分组成:
计数器寄存器【TIMx_CNT, Count Up Timer】
预分频寄存器【TIMx_PSC, Prescaler】
自动重装载寄存器【TIMx_ARR, Auto-Reload Register】
首先,基本定时器时钟(基准计数时钟频率)来源于内部时钟,频率值为系统主频:72MHz,即图中CK_PSC=72MHz。
预分频器对基准计数时钟(72MHz)进行预分频。如果预分频寄存器写0,分频系数为0+1=1,该分频器为不分频或者1分频,则分频器输出频率CK_CNT=72MHz。如果预分频寄存器写1,分频系数为1+1=2,该分频器为2分频,则分频器输出频率CK_CNT=72MHz/2=36MHz。
实际分频系数=预分频器的值+1;CK_CNT= CK_PSC/(PSC+1);
如上图下半部分:预分频寄存器值从0变到1,定时器不会立即就响应,会有一个缓冲器来同步修改定时器时钟,定时器时钟根据写入的值对频率进行减半,此时CK_CNT=36MHz。计数器会根据这个时钟频率来进行计数。
计数器的工作为:从0到往上计数,直到与自动重装载寄存器的值相等,产生中断或者事件。
设预分频寄存器值PSC=7200-1 ,自动重装寄存器ARR=10000-1,分频器时钟频率=CK_PSC=72MHz。
则计数器的时钟频率CK_CNT = CK_PSC / (PSC+1) = 72MHz/7200 = 10000Hz ,即一秒钟可以计数10000次,又因为自动重装寄存器ARR值为 10000-1 ,于是当计数器从0开始计数到10000,花费1秒。此时产生中断。
计数器溢出频率 CK_CNT_OV= CK_CNT / (ARR+1) = CK_PSC / (PSC+1) / (ARR+1) 。
2、通用定时器
通用定时器的时钟信号不仅仅可以通过内部时钟获得,还可以通过外部时钟获得。而不同的外部时钟源模式下有多种不同的外部时钟输入源。通用定时器的时钟信号输入可分为内部时钟源、外部时钟源模式1和外部时钟源模式2。

外部时钟源模式2:一个时钟源,通过ETR信号——分频器ETRP——滤波去毛刺ETRF——触发控制器——时基单元时钟

外部时钟源模式1下的时钟源有:4个时钟源,ETR引脚的信号、ITR信号、CH1引脚的边沿、CH1引脚和CH2引脚 。
模式1ETR引脚的时钟信号与模式2不同的是,它进入了TRGI(触发输入通道),再进入触发控制器。

ITRx(0-3)信号是来自其他定时器的,是其他定时器的TRGO输出。主要应用于主从设备级联。

例子:比如我可以先初始化TIM3,然后使用主模式把它的更新事件映射到TRGO上,接着再初始化TIM2,这里选择ITR2,对应的就是TIM3的TRGO(与ITR2连接),然后后面再选择时钟为外部时钟模式1,这样TM3的更新事件就可以驱动TM2的时基单元,也就实现了定时器的级联。
外部时钟模式1的CH1引脚的边沿、CH1引脚和CH2引脚 的时钟源以及高级定时器后面再讲。
3,实验学习
3.1 初始化定时器:
第1步:RCC开启时钟。同时开启定时器的基准时钟和外设的工作时钟。
第2步:选择时基单元的时钟源。这里选择内部时钟源。
第3步:配置时基单元。包括PSC预分频器、CNT计数器模式、ARR自动重装器。
第4步:配置输出中断控制。允许更新中断输出到NVIC。
第5步:配置NVIC。在NVIC中打开定时器中断的通道,分配一个优先级。
第6步:运行控制,使能计数器。
#include "stm32f10x.h" // Device header
void Timer_Init(void) // 定时器初始化函数
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); // 第1步:RCC开启时钟
TIM_InternalClockConfig(TIM2); // 选择内部时钟源2
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; // 时基单元参数结构体
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure); // 配置时基单元
TIM_ClearFlag(TIM2,TIM_FLAG_Update); // 清除中断标志位
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); // 使能中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // NVIC优先级分组
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // NVIC通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure); // NVIC通道初始化
TIM_Cmd(TIM2,ENABLE); // 启动定时器
}
/*
void TIM2_IRQHandler(void) // 中断函数
{
if(TIM_GetFlagStatus(TIM2,TIM_FLAG_Update) == SET)
{
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
*/
3.2 PWM输出比较 OC(Output Compare):
通过 捕获/比较寄存器 CCR(Capture Compare Register)的值 与计数器的值做大小比较,对输出电平进行0或1的翻转,输出一定频率和占空比的PWM波形。
如上图,CNT计数累加,累加到ARR值停止,又继续从0开始。 在累加过程中,CNT值比CCR值小的时段对应输出高电平,比CCR值大的部分对应输出低电平。
PWM参数:
(1) PWM频率 Freq = CK_PSC / ( PSC + 1 ) / ( ARR + 1 )
(2) PWM占空比 Duty = t1 / Ts = CCR / ( ARR + 1 )
(3) PWM分辨率 Reso = 1 / ( ARR + 1 )
PWM分辨率为占空比的变化维度大小,如占空比可由1%的大小变化。
一般情况下,是通过想要输出的PWM波形呈现什么特性,来决定ARR、PSC和CCR值的设置。
如想输出PWM波形 频率为1KHz 、占空比为50%、分辨率为1% ,则先由(3)可知需要先将ARR值设置为100-1,再由(2)可得CCR的值为50 ,最后由时钟频率(72MHz)和(1)式将PSC的值设为 720-1。
如何使用PWM来设置输出具有一定占空比的信号波形。首先需要先进行PWM初始化,再封装改变占空比和分辨率的函数,最后在主程序调用函数功能。

3.3 PWM初始化
第1步:RCC开启时钟,选择时钟源。开启定时器TIM的基准时钟和GPIO外设的工作时钟,选择内部时钟源。
第2步:并配置时基单元。配置PSC预分频器、CNT计数器模式、ARR自动重装器。
第3步:配置输出比较单元。包括设置CCR值、输出比较模式、极性选择、输出使能。
第4步:配置GPIO。把PWM对应的GPIO口初始化为复用推挽输出模式。
第5步:运行控制,使能启动计数器。

void PWM_Init()
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); // 启动定时器 TIM2 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); // 启动 GPIOA 时钟
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); // GPIO初始化
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; // ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; // PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure); // 初始化时基单元
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure); // 初始化结构体 成员为默认值
TIM_OCInitStructure.TIM_OCMode= TIM_OCMode_PWM1; // 输出比较模式 PWM1
TIM_OCInitStructure.TIM_OCPolarity= TIM_OCPolarity_High; // 输出比较极性
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能
TIM_OCInitStructure.TIM_Pulse= 50 ; // CRR
TIM_OC1Init(TIM2,&TIM_OCInitStructure); // 初始化输出比较单元
TIM_Cmd(TIM2,ENABLE); // 定时器使能
}
void PWM_SetCompare1(uint16_t Compare) // 设置CRR的值来改变输出引脚的PWM波形
{
TIM_SetCompare1(TIM2,Compare);
}
// PWM.h 就不多赘述了
// main.c :CRR从0到100 改变PWM占空比 延时输出 再反过来
#include "stm32f10x.h"
#include "Delay.h" // Device header
#include "PWM.h"
uint8_t i;
int main(void)
{
PWM_Init();
while(1)
{
for(i=0;i<100;i++)
{
PWM_SetCompare1(i);
Delay_ms(10);
}
for(i=0;i<100;i++)
{
PWM_SetCompare1(100 - i);
Delay_ms(10);
}
}
}
3.4 舵机驱动实验
要求按一下键,舵机旋转30度。
舵机要求周期为 20ms,则PWM输出频率为 1/20ms = 50Hz。
舵机要求高电平时间为 0.5ms—2.5ms,需要求出计数器范围,再通过占空比求出CCR范围
设PSC=72,则计数器的时钟频率CK_CNT = CK_PSC / (PSC+1) = 72MHz/72 = 1MHz , 即一秒钟可以计数1M。又需要PWM输出频率 = CK_CNT / (ARR +1) = 50Hz,得ARR=20K。
计数器1s计数1M,计数20K需要0.02s=20ms。舵机要求高电平时间范围为 0.5ms—2.5ms,对应计数时间0—20ms,相当于计数0—20K范围中0.5K—2.5K (500—2500)。
因此在PWM_Init(); 中将ARR值设为20000 – 1;PSC值为72 – 1;CCR默认为0。
TIM_TimeBaseInitStructure.TIM_Period = 100 – 1; // ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 – 1; // PSC
TIM_OCInitStructure.TIM_Pulse= 0 ; // CCR

// Servo.c
#include "stm32f10x.h"
#include "PWM.h"
void Servo_Init(void)
{
PWM_Init();
}
void Servo_SetAngle(float Angle)
{
PWM_SetCompare2( Angle / 180 * 2000 + 500);
}
// main.c
#include "stm32f10x.h"
#include "Delay.h"
#include "PWM.h"
#include "Servo.h"
#include "Key.h"
uint8_t KeyNum;
float Angle=0;
int main(void)
{
OLED_Init();
Key_Init();
Servo_Init();
OLED_ShowString(1, 1, "Angle:");
while(1)
{
KeyNum = Key_GetNum();
if(KeyNum == 1)
{
Angle += 30;
if(Angle > 180)
{
Angle = 0;
}
}
Servo_SetAngle(Angle);
OLED_ShowString(1, 7, Angle, 3);
}
}
3.5 直流电机驱动实验

// Motor.h
void Motor_Init(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_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
PWM_Init;
}
void Motor_SetSpeed(uint8_t Speed)
{
if(Speed >= 0)
{
GPIO_SetBits(GPIOA,GPIO_Pin_4);
GPIO_ResetBits(GPIOA,GPIO_Pin_5);
PWM_SetCompare1(Speed); // 使用PWM初始化函数设置0-100占空比
}
else
{
GPIO_ResetBits(GPIOA,GPIO_Pin_4);
GPIO_SetBits(GPIOA,GPIO_Pin_5);
PWM_SetCompare1(-Speed);
}
}
作者:百里与司空