目录
一、TIM定时器
- 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
- 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
- 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
- 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
1、定时器类型
2、定时中断
那如果配置定时器中断如何配置相关寄存器呢?
相关配置如图所示:1、选择外部时钟配置
2、时基单元选择
3、中断输出控制
4、NVIC配置
5、运行控制配置
1、外部时钟配置
外部时钟源选择可以用以下函数:
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,uint16_t TIM_ICPolarity, uint16_t ICFilter);
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,
2、时基单元选择
时基单元可以用TimeBaseInit函数
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
结构体内部成员:
typedef struct
{
uint16_t TIM_Prescaler;
uint16_t TIM_CounterMode;
uint16_t TIM_Period;
uint16_t TIM_ClockDivision;
uint8_t TIM_RepetitionCounter;
} TIM_TimeBaseInitTypeDef;
声明方式(一般):
TIM_TimeBaseStructure.TIM_Period = 4999;//Period即定时器周期,即ARR自动重装器值
TIM_TimeBaseStructure.TIM_Prescaler =7199;//Prescaler即PSC预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器,高级定时器才用到
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
时基单元就是定时器框图 组成部分之一。它包括三个寄存器,分别是:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)和自动装载寄存器(TIMx_ARR)。
计数器寄存器(CNT)
其计数模式分为向上计数、向下计数或者中心对齐计数;
可将时钟频率按1到65535之间的任意值进行分频,可在运行时改变其设置值;
相关函数:
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);
预分频器(PSC)
预分频器的作用是对定时器的输入时钟信号进行分频,以降低定时器计数器的计数速率。简单来说,它就像是一个缩小器,能够将高速的时钟信号缩减成定时器能够使用的更低频率的信号。
相关函数:
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
函数中的Prescaler指:预分频值 TIM_PSCReloadMode:写入的模式
自动装载寄存器(ARR)
如果TIMx_CR1寄存器中的ARPE位为0,ARR寄存器的内容将直接写入影子寄存器;如果ARPE为1,ARR寄存器的那日同将在每次的更新时间UEV发生时,传送到影子寄存器;
如果TIM1_CR1中的UDIS位为0,当计数器产生溢出条件时,产生更新事件。
相关函数:
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
3、中断输出控制
中断输出控制可以用ITConfig函数
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
4、NVIC配置
NVIC配置需要用NVIC_Init函数
5、运行控制配置
运行控制配置需要用到TIM_Cmd函数
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
3、常用库函数
状态标志位获取和清除
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
4、整体定时器中断实现步骤
①使能定时器时钟。
RCC_APB1PeriphClockCmd();
② 初始化定时器,配置ARR,PSC。
TIM_TimeBaseInit();
③开启定时器中断,配置NVIC。
void TIM_ITConfig();
NVIC_Init();
④ 使能定时器。
TIM_Cmd();
⑥ 编写中断服务函数。
TIMx_IRQHandler();
应用案例
#include "stm32f10x.h"
void Timer_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef 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的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*中断输出配置*/
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
//TIM_TimeBaseInit函数末尾,手动产生了更新事件
//若不清除此标志位,则开启中断后,会立刻进入一次中断
//如果不介意此问题,则不清除此标志位也可
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
二、定时器PWM输出
1、PWM简介
- PWM(Pulse Width Modulation)脉冲宽度调制
- 在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,通俗的讲就是等效地实现一个模拟信号的输出,常应用于电机控速等领域
- PWM参数:
- 频率 = 1 / TS 频率越快,等效模拟信号就越平稳
- 占空比 = TON / TS TON:高电平时间 TS:一个周期时间
- 分辨率 = 占空比变化步距
OC(Output Compare)输出比较
- 输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形.
- 每个高级定时器和通用定时器都拥有4个输出比较通道:
- 高级定时器的前3个通道额外拥有死区生成和互补输出的功能:
定时器的输出比较模块是怎么样输出PWM波形的?
如图所示:左边为CNT计数器和CCR1第一路的捕获/比较寄存器,当CNT>=CCR1的时候,就会给输出控制模拟信号传一个信号,然后输出模拟器就会改变oc1ref的高电平,oc1ref在这就是指信号的高低电平,ref指的是reference的缩写,指参考信号,ref第一路可前往指至主模式控制器,可以把ref映射到主模式的TRGO输出上去,第二路前往OC1P进行极限选择。
输出模式选择如下图所示:
有效电平可了解为:高电平 无效电平可了解为:低电平
相关函数:
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
2、PWM基本结构
PWM基本结构如图所示:
图中蓝色线:CNT 黄色线:ARR 红色线:CCR 绿色线:输出
如图所示:蓝色线从0开始自增到ARR,也就是99后清零继续自增, 红色线设置为30,最开始时CNT<CCR,所以置高电平,当30<=CNT<99,置低电平,溢出清零后重新循坏开始。CCR设置得高一些,输出的占空比就变大,低则变小。
图中的REF指的是频率可调,占空比可调的PWM波形
3、PWM参数计算
PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1)
PWM占空比: Duty = CCR / (ARR + 1)
PWM分辨率(占空比变化的步距): Reso = 1 / (ARR + 1)
4、定时器PWM库函数配置
输出库函数配置
void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
其结构体成员如下:
typedef struct
{
uint16_t TIM_OCMode; //PWM模式1或者模式2
uint16_t TIM_OutputState; //输出使能 OR失能
uint16_t TIM_OutputNState;
uint16_t TIM_Pulse; //比较值,写CCRx
uint16_t TIM_OCPolarity; //比较输出极性
uint16_t TIM_OCNPolarity;
uint16_t TIM_OCIdleState;
uint16_t TIM_OCNIdleState;
} TIM_OCInitTypeDef;
初始化实例:
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出极性,选择为高,若选择极性为低,则输出高低电平取反
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//输出使能
TIM_OCInitStructure.TIM_Pulse = 0;//初始的CCR值
TIM_OC2Init(TIM2, &TIM_OCInitStructure);//将结构体变量交给TIM_OC2Init,配置TIM2的输出比较通道2
- 输出比较单元配置:
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
- 单独更改CCR函数
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);
- 单独更改输出极性:
void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC2PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC2NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC3PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC3NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC4PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
- 高级定时器需要额外使用函数:
void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);
5、整体定时器PWM配置步骤
①开启TIM2的时钟和开启GPIOA的时钟
RCC_APB1PeriphClockCmd(); //通用定时器TIM2连接在APB1总线上
RCC_APB2PeriphClockCmd();
②初始化IO口为复用推挽输出
GPIO_Init();
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//选择推挽输出的原因是定时器需要来控制引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
③当选用PA15用作定时器的PWM输出引脚,需用重映射配置,故要开启AFIO时钟,同时设置重映射,而PA15又作为调试端口,故将JTAG引脚失能才可。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //若不是调试端口,此条不用写
④初始化定时器:ARR,PSC等:TIM_TimeBaseInit();
⑤初始化输出比较参数:TIM_OC1Init();
⑥使能定时器:TIM_Cmd();
⑦改变比较值CCRx,达到不同的占空比效果:TIM_SetCompare1();
6、实例
改变PWM输出占空比来改变LED的亮度,间接实现呼吸灯的现象
//PWM.c源文件
#include "stm32f10x.h"
void PWM_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO重映射*/
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//开启AFIO的时钟,重映射必须先开启AFIO的时钟
// GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE); //将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //将JTAG引脚失能,作为普通GPIO引脚使用
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
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_TimeBaseInit,配置TIM2的时基单元
/*输出比较初始化*/
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量
TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值
//则最好执行此函数,给结构体所有成员都赋一个默认值
//避免结构体初值不确定的问题
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值
TIM_OC1Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
/**
* 函 数:PWM设置CCR
* 参 数:Compare 要写入的CCR的值,范围:0~100
* 返 回 值:无
* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
* 占空比Duty = CCR / (ARR + 1)
*/
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare); //设置CCR1的值
}
//PWM.h头文件
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
#endif
//main.c源文件
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
uint8_t i; //定义for循环的变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
PWM_Init(); //PWM初始化
while (1)
{
for (i = 0; i <= 100; i++)
{
PWM_SetCompare1(i); //依次将定时器的CCR寄存器设置为0~100,PWM占空比逐渐增大,LED逐渐变亮
Delay_ms(10); //延时10ms
}
for (i = 0; i <= 100; i++)
{
PWM_SetCompare1(100 - i); //依次将定时器的CCR寄存器设置为100~0,PWM占空比逐渐减小,LED逐渐变暗
Delay_ms(10); //延时10ms
}
}
}