STM32中TIM定时器与PWM功能详解

STM32中TIM定时器与PWM功能详解

定时器分类

类型

使用编号

总线

描述

基本定时器

TIM6、TIM7

APB1

1,主要部分是一个带有自动重装载的

16

位累加计数器。

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——触发控制器——时基单元时钟

外部时钟源模式2

外部时钟源模式1下的时钟源有:4个时钟源,ETR引脚的信号、ITR信号、CH1引脚的边沿、CH1引脚和CH2引脚 。

模式1ETR引脚的时钟信号与模式2不同的是,它进入了TRGI(触发输入通道),再进入触发控制器。

外部时钟源模式1 :ETR时钟信号

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

外部时钟源模式1 :其他定时器时钟信号

例子:比如我可以先初始化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初始化,再封装改变占空比和分辨率的函数,最后在主程序调用函数功能。

PWM基本结构

3.3 PWM初始化

第1步:RCC开启时钟,选择时钟源。开启定时器TIM的基准时钟和GPIO外设的工作时钟,选择内部时钟源。

第2步:并配置时基单元。配置PSC预分频器、CNT计数器模式、ARR自动重装器。

第3步:配置输出比较单元。包括设置CCR值、输出比较模式、极性选择、输出使能。

第4步:配置GPIO。把PWM对应的GPIO口初始化为复用推挽输出模式。

第5步:运行控制,使能启动计数器。

PWM驱动呼吸灯

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

PWM驱动舵机

// 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 直流电机驱动实验

PWM驱动直流电机

// 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);

}

}

作者:百里与司空

❈ ❈ ❈

相关文章

✧ ✧ ✧
砍价怎么说才不尴尬?买家省钱 + 卖家应对指南 – 店托易
GitHub发行版:创建与管理软件版本的终极指南
365bet官方网站

GitHub发行版:创建与管理软件版本的终极指南

📅 07-30 👁️ 2966
摩洛哥世界杯申委会CEO:希望中国队2026年能来参赛
bt365app官方下载登录

摩洛哥世界杯申委会CEO:希望中国队2026年能来参赛

📅 07-22 👁️ 2159