定时器二
1、通用定时器
1.1、输出比较功能
stm32f103c8t6的通用定时器为TIM2,TIM3,TIM4。而通用定时器拥有基本定时器的所有功能,并且增加的如下的功能:
(1)多种时钟源选择
(2)向上计数(加),向下计数(减),向上/向下(先加后减)。当然我们使用的时候更喜欢向上计数
(3)输出比较,用于测量波形的周期/测量脉冲的宽度
(4)输入捕获,用于PWM波形的生成
(5)支持针对定位的增量(正交)编码器和霍尔传感器电路。
下图为通用定时器的结构框图(时钟来源,输入捕获,输出比较,从模式):
实验:使用TIM2的输出比较CH1输出PWM波形实现呼吸灯。实物连接如下图所示:
①TIM2.c文件的代码如下:
c
#include "stm32f10x.h"
/**
* 通用定时器的输出比较模式输出PWM波形的初始化
*/
void TIM2_Init(void)
{
/* 1、开启时钟 */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;//开启GPIOA的时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;//开启TIM2的时钟
/* 2、配置GPIOA0(TIM2_CH1)引脚输出模式:复用推挽输出(MODE0 = 11,CNF0 = 10)*/
GPIOA->CRL |= GPIO_CRL_MODE0;
GPIOA->CRL &= ~GPIO_CRL_CNF0_0;
GPIOA->CRL |= GPIO_CRL_CNF0_1;
/* 3、定时器TIM2的配置 */
/* 3.0、选择时钟源,默认的是内部时钟源ABP1*/
/* 3.1、预分频器的设置 */
TIM2->PSC = 7200 - 1;//每隔0.1ms来一个脉冲,计数一次
/* 3.2、自动重装载寄存器的设置 */
TIM2->ARR = 100 - 1;//一个周期的时间为10ms
/* 3.3、计数器的计数方向:默认向上计数*/
TIM2->CR1 &= ~TIM_CR1_DIR;//向上计数
/* 4、配置输出比较 */
/* 4.1、配置捕获比较寄存器的值 */
TIM2->CCR1 = 50;
/* 4.2、配置通道CH1的输出比较模式: TIMx_CCMR1_CC1S = 00(输出模式)
TIMx_CCMR1_OC1M = 110(PWM1模式)
*/
TIM2->CCMR1 &= ~TIM_CCMR1_CC1S;//CH1配置为输出模式
TIM2->CCMR1 &= ~TIM_CCMR1_OC1M_0;
TIM2->CCMR1 |= TIM_CCMR1_OC1M_1;
TIM2->CCMR1 |= TIM_CCMR1_OC1M_2;
/* 4.3、使能通道1:TIMx_CCER_CC1E = 1 */
TIM2->CCER |= TIM_CCER_CC1E;//使能通道CH1
/* 5、使能计数器 */
TIM2->CR1 |= TIM_CR1_CEN;
}
/**
* 更改占空比函数
*/
void Set_PWMDuty(uint8_t Data)
{
TIM2->CCR1 = Data;
}
②主函数文件的代码如下:
c
#include "stm32f10x.h"
#include "TIM2.h"
#include "OLED.h"
#include "Delay.h"
int main(void)
{
uint8_t Data = 0;
TIM2_Init();
Set_PWMDuty(Data);
while(1)
{
for(uint8_t i = 0; i<100; i++)
{
Set_PWMDuty(++Data);
Delay_ms(10);
}
for(uint8_t i = 0; i<100; i++)
{
Set_PWMDuty(--Data);
Delay_ms(10);
}
}
}
实物效果展示如下:
呼吸灯
1.2、输入捕获功能
此功能可以捕获输入通道上输入信号的上升沿或下降沿,多用于测量PWM的周期/频率,也可以测量占空比,只要测量出连续的一个上升沿和一个下降沿的时间间隔,然后除以周期即可。
测量的方法如下:①只要测量出连续的两个上升沿或连续的两个下降沿的时间间隔。②在1s时间内捕获了N个上升沿,那么则1s内有N-1个周期,计算出周期,那么就能计算出频率。
通用定时器能产生中断的部位如下:
1.2.1、测量PWM波形的周期和频率
实验:使用TIM3的输入捕获CH1通道测量出TIM2产生的PWM波形的周期与频率
①TIM3.c文件的代码如下;
c
#include "stm32f10x.h"
/**
* 通用定时器TIM3的初始化,使用输出捕获通道CH1测量TIM2产生的PWM波形的频率和周期
*/
void TIM3_Init(void)
{
/* 1、开启时钟 */
/* 1.1、开启定时器TIM3和CH1通道引脚(PA6)的时钟 */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;//开启GPIOA的时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;//开启TIM3的时钟
/* 2、配置CH1通道引脚为浮空输入模式:MODE6 = 00,CNF6 = 01 */
GPIOA->CRL &= ~GPIO_CRL_MODE6;
GPIOA->CRL |= GPIO_CRL_CNF6_0;
GPIOA->CRL &= ~GPIO_CRL_CNF6_1;
/* 3、配置基本定时器TIM3 */
/* 3.1、选择时钟源 */
//默认为内部时钟源,所以无需代码配置
/* 3.2、配置预分频PSC,分频系数为72 */
TIM3->PSC = 72 - 1;//则一个脉冲的时间为1us
/* 3.3、配置重装在值,为了防止溢出,配置最大*/
TIM3->ARR = 65536 - 1;
/* 3.4、配置计数器为向上计数:TIMx_CR1_DIR = 0 */
TIM3->CR1 &= ~TIM_CR1_DIR;
/* 4、配置输入捕获相关寄存器 */
/* 4.1、TIMx_CH1引脚连到TI1输入:TIMx_CR2_TI1S = 0 */
TIM3->CR2 &= ~TIM_CR2_TI1S;
/* 4.2、输入滤波器的配置,配置为不滤波:TIMx_CCMR1_IC1F = 0000 */
TIM3->CCMR1 &= ~TIM_CCMR1_IC1F;
/* 4.3、将CH1通道配置为输入捕获模式,且IC1映射到TI1::TIMx_CCMR1_CC1S = 01 */
TIM3->CCMR1 |= TIM_CCMR1_CC1S_0;
TIM3->CCMR1 &= ~TIM_CCMR1_CC1S_1;//IC1映射在TI1上
/* 4.4、配置为捕获上升沿:TIMx_CCER_CC1P = 0 */
TIM3->CCER &= ~TIM_CCER_CC1P;
/* 4.5、输入捕获中的预分频的配置(不分频):TIMx_CCMR1_IC1PSC = 00 */
TIM3->CCMR1 &= ~TIM_CCMR1_IC1PSC;
/* 4.6、开启中断请求:TIMx_DIER_CC1IE = 1 */
TIM3->DIER |= TIM_DIER_CC1IE;
/* 4.7、使能捕获/比较寄存器:TIMx_CCER_CC1E = 1*/
TIM3->CCER |= TIM_CCER_CC1E;
/* 5、配置NVIC */
NVIC_SetPriorityGrouping(4);
NVIC_SetPriority(TIM3_IRQn,0);
NVIC_EnableIRQ(TIM3_IRQn);
/* 6、使能计数器:TIMx_CR1_CEN = 1*/
TIM3->CR1 |= TIM_CR1_CEN;
}
/**
* 中断服务函数:在捕获到第一个上升沿,让计数器里面的值变为0
* 当捕获到第二个上升沿时,计数器里面的值拍照到捕获/比较寄存器(CCR)里面(硬件自动完成的)
*/
uint16_t t = 0;
void TIM3_IRQHandler(void)
{
static uint8_t flag = 0;//第一个一个标志位,用来判断是否为第一个上升沿
if(TIM3->SR & TIM_SR_CC1IF)
{
/* 清除标志位:TIMx_SR_CC1IF*/
TIM3->SR &= ~TIM_SR_CC1IF;
flag++;
/* 判断是否为第一次上升沿,若是那么就清除计数器里面的值*/
if(flag == 1)
{
TIM3->CNT = 0;//将计数器里面的值变为0
TIM3->CCR1 = 0;//将捕获/比较寄存器(CCR)里面的值也变为0
}
else if(flag == 2)
{
t = TIM3->CCR1;//将计数器里面的值拍照到捕获/比较寄存器(CCR)里面
TIM3->CNT = 0;//将计数器里面的值变为0
TIM3->CCR1 = 0;//将捕获/比较寄存器(CCR)里面的值也变为0
flag = 0;//让flag = 0;
}
}
}
/**
* 对将捕获/比较寄存器(CCR)里面的值进行处理,获取PWM波形的周期(us)
*/
double Get_PWMCycle(void)
{
return t ;//返回为us
}
/**
* 对将捕获/比较寄存器(CCR)里面的值进行处理,获取PWM波形的频率(Hz)
*/
double Get_PWMFrequency(void)
{
return 1000000.0 / t;//返回为Hz
}
TIM2.c文件的代码如下:
c
#include "stm32f10x.h"
/**
* 通用定时器的输出比较模式输出PWM波形的初始化
*/
void TIM2_Init(void)
{
/* 1、开启时钟 */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;//开启GPIOA的时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;//开启TIM2的时钟
/* 2、配置GPIOA0(TIM2_CH1)引脚输出模式:复用推挽输出(MODE0 = 11,CNF0 = 10)*/
GPIOA->CRL |= GPIO_CRL_MODE0;
GPIOA->CRL &= ~GPIO_CRL_CNF0_0;
GPIOA->CRL |= GPIO_CRL_CNF0_1;
/* 3、定时器TIM2的配置 */
/* 3.0、选择时钟源,默认的是内部时钟源ABP1*/
/* 3.1、预分频器的设置 */
TIM2->PSC = 7200 - 1;//每隔0.1ms来一个脉冲,计数一次
/* 3.2、自动重装载寄存器的设置 */
TIM2->ARR = 100 - 1;//一个周期的时间为10ms
/* 3.3、计数器的计数方向:默认向上计数*/
TIM2->CR1 &= ~TIM_CR1_DIR;//向上计数
/* 4、配置输出比较 */
/* 4.1、配置捕获比较寄存器的值 */
TIM2->CCR1 = 50;
/* 4.2、配置通道CH1的输出比较模式: TIMx_CCMR1_CC1S = 00(输出模式)
TIMx_CCMR1_OC1M = 110(PWM1模式)
*/
TIM2->CCMR1 &= ~TIM_CCMR1_CC1S;//CH1配置为输出模式
TIM2->CCMR1 &= ~TIM_CCMR1_OC1M_0;
TIM2->CCMR1 |= TIM_CCMR1_OC1M_1;
TIM2->CCMR1 |= TIM_CCMR1_OC1M_2;
/* 4.3、使能通道1:TIMx_CCER_CC1E = 1 */
TIM2->CCER |= TIM_CCER_CC1E;//使能通道CH1
/* 5、使能计数器 */
TIM2->CR1 |= TIM_CR1_CEN;
}
/**
* 更改占空比函数
*/
void Set_PWMDuty(uint8_t Data)
{
TIM2->CCR1 = Data;
}
③主函数文件的代码如下:
c
#include "stm32f10x.h"
#include "TIM2.h"
#include "TIM3.h"
#include "OLED.h"
int main(void)
{
uint8_t Data = 0;
OLED_Init();
OLED_ShowString(1,1,"Cycle:00000us");
OLED_ShowString(2,1,"Freq:0000Hz");
TIM2_Init();//TIM2的初始化,产生了一个周期为10ms的PWM波形
TIM3_Init();//TIM3的初始化,开启CH1输出捕获
Set_PWMDuty(Data);
while(1)
{
OLED_ShowNum(1,7,Get_PWMCycle(),5);
OLED_ShowNum(2,6,Get_PWMFrequency(),4);
for(uint8_t i = 0; i<100; i++)
{
Set_PWMDuty(++Data);
Delay_ms(10);
}
for(uint8_t i = 0; i<100; i++)
{
Set_PWMDuty(--Data);
Delay_ms(10);
}
}
}
实物演示效果如下:
测量PWM周期和频率
1.2.2、定时器的从模式
使用TIM3的输入捕获CH1通道测量出TIM2产生的PWM波形的周期与频率这个实验,捕获到第一个上升沿的时候我们使用中断的方式,在中断函数里面通过CPU执行代码让计数器里面的值变为0。这样的方式测量出来的一个周期的CRR的值是有误差的。
因为在第一个上升沿来的时候触发中断,在执行中断函数的时候,PWM波形不断的"走",当执行到清零计数器的代码的时候,PWM波形已经走了一会了。所以不是在检测到PWM波形的上升沿的同一时间就立马把计数器里面的数据清零。但是,将计数器中的数据拍照到CRR中是由硬件执行的,在检测到上升沿时,硬件立马会把计数器中的数据拍照到CRR寄存器中。所以,使用上面的实验的方法,测量出来的周期一般比实际周期小。
为了解决这一问题:使用定时器的从模式。从模式的功能都是由硬件自动完成的,无需CPU执行代码。
如上图所示:TRGI为从模式触发信号,其中触发信号的来源如图中蓝色框中的8种,如下图为8中信号来源。
从模式的工作方式:
实验:通过定时器从模式自动测量PWM波的周期和频率和PWM波的占空比。
从模式的触发信号选择TI1FP1,从模式的工作模式为复位模式。将IC1配置为检测上升沿,且IC1映射在TI1FP1。将IC2配置为检测下降沿,且IC2映射在TI1FP2。即来一个上升沿时,通过从模式硬件自动启动复位(计数器清零),来一个下降沿计数器里面的值拍照到捕获/比较寄存器2中CCR2。再来一个上升沿将计数器里面值拍照到CCR1中。则一个周期 = CCR1里面的数值,占空比 = CCR2/CCR1
①TIM3.c文件的代码如下:
c
#include "stm32f10x.h"
/**
* 通用定时器TIM3的初始化,使用输出捕获通道CH1测量TIM2产生的PWM波形的频率和周期
*/
void TIM3_Init(void)
{
/* 1、开启时钟 */
/* 1.1、开启定时器TIM3和CH1通道引脚(PA6)的时钟 */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;//开启GPIOA的时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;//开启TIM3的时钟
/* 2、配置CH1通道引脚为浮空输入模式:MODE6 = 00,CNF6 = 01 */
GPIOA->CRL &= ~GPIO_CRL_MODE6;
GPIOA->CRL |= GPIO_CRL_CNF6_0;
GPIOA->CRL &= ~GPIO_CRL_CNF6_1;
/* 3、配置基本定时器TIM3 */
/* 3.1、选择时钟源 */
//默认为内部时钟源,所以无需代码配置
/* 3.2、配置预分频PSC,分频系数为72 */
TIM3->PSC = 72 - 1;//则一个脉冲的时间为1us
/* 3.3、配置重装在值,为了防止溢出,配置最大*/
TIM3->ARR = 65536 - 1;
/* 3.4、配置计数器为向上计数:TIMx_CR1_DIR = 0 */
TIM3->CR1 &= ~TIM_CR1_DIR;
/* 4、配置输入捕获相关寄存器 */
/* 4.1、TIMx_CH1引脚连到TI1输入:TIMx_CR2_TI1S = 0 */
TIM3->CR2 &= ~TIM_CR2_TI1S;
/* 4.2、输入滤波器的配置,配置为不滤波:TIMx_CCMR1_IC1F = 0000 */
TIM3->CCMR1 &= ~TIM_CCMR1_IC1F;
/* 4.3、将CH1通道配置为输入捕获模式,且IC1映射到TI1:TIMx_CCMR1_CC1S = 01
将CH2通道配置为输入捕获模式,且IC2映射到TI1:TIMx_CCMR1_CC2S = 10*/
TIM3->CCMR1 |= TIM_CCMR1_CC1S_0;
TIM3->CCMR1 &= ~TIM_CCMR1_CC1S_1;//IC1映射在TI1FP1上
TIM3->CCMR1 &= ~TIM_CCMR1_CC2S_0;
TIM3->CCMR1 |= TIM_CCMR1_CC2S_1;//IC2映射在TI1上
/* 4.5、配置IC1为捕获上升沿:TIMx_CCER_CC1P = 0
配置IC2为捕获下降沿:TIMx_CCER_CC2P = 0*/
TIM3->CCER &= ~TIM_CCER_CC1P;
TIM3->CCER |= TIM_CCER_CC2P;
/* 4.6、输入捕获中IC1的预分频的配置(不分频):TIMx_CCMR1_IC1PSC = 00
输入捕获中IC2的预分频的配置(不分频):TIMx_CCMR1_IC2PSC = 00 */
TIM3->CCMR1 &= ~TIM_CCMR1_IC1PSC;
TIM3->CCMR1 &= ~TIM_CCMR1_IC2PSC;
/* 4.7、捕获/比较寄存器1使能:TIMx_CCER_CC1E = 1
捕获/比较寄存器2使能:TIMx_CCER_CC2E = 1*/
TIM3->CCER |= TIM_CCER_CC1E;
TIM3->CCER |= TIM_CCER_CC2E;
/* 5、配置从模式 */
/* 5.1、选择从模式的触发信号为TI1FP1:TIMx_SMCR_TS = 101 */
TIM3->SMCR |= (TIM_SMCR_TS_0|TIM_SMCR_TS_2);
TIM3->SMCR &= ~TIM_SMCR_TS_1;
/* 5.2、配置从模式的工作模式:复位模式 TIMx_SMCR_SMS = 100 */
TIM3->SMCR |= TIM_SMCR_SMS_2;
TIM3->SMCR &= ~(TIM_SMCR_SMS_1|TIM_SMCR_SMS_0);
/* 6、使能计数器:TIMx_CR1_CEN = 1*/
TIM3->CR1 |= TIM_CR1_CEN;
}
/**
* 对将捕获/比较寄存器(CCR1)里面的值进行处理,获取PWM波形的周期(us)
*/
double Get_PWMCycle(void)
{
return TIM3->CCR1;//返回为us
}
/**
* 对将捕获/比较寄存器(CCR)里面的值进行处理,获取PWM波形的频率(Hz)
*/
double Get_PWMFrequency(void)
{
return 1000000.0 / TIM3->CCR1;//返回为Hz
}
/**
* 对将捕获/比较寄存器(CCR1)和(CCR2)里面的值进行处理,获取PWM波形占空比(%)
*/
double Get_PWMDutycycle(void)
{
return ((TIM3->CCR2) * 100) / TIM3->CCR1;//返回为%
}
②TIM2.c文件的代码如下:
c
#include "stm32f10x.h"
/**
* 通用定时器的输出比较模式输出PWM波形的初始化
*/
void TIM2_Init(void)
{
/* 1、开启时钟 */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;//开启GPIOA的时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;//开启TIM2的时钟
/* 2、配置GPIOA0(TIM2_CH1)引脚输出模式:复用推挽输出(MODE0 = 11,CNF0 = 10)*/
GPIOA->CRL |= GPIO_CRL_MODE0;
GPIOA->CRL &= ~GPIO_CRL_CNF0_0;
GPIOA->CRL |= GPIO_CRL_CNF0_1;
/* 3、定时器TIM2的配置 */
/* 3.0、选择时钟源,默认的是内部时钟源ABP1*/
/* 3.1、预分频器的设置 */
TIM2->PSC = 7200 - 1;//每隔0.1ms来一个脉冲,计数一次
/* 3.2、自动重装载寄存器的设置 */
TIM2->ARR = 100 - 1;//一个周期的时间为10ms
/* 3.3、计数器的计数方向:默认向上计数*/
TIM2->CR1 &= ~TIM_CR1_DIR;//向上计数
/* 4、配置输出比较 */
/* 4.1、配置捕获比较寄存器的值 */
TIM2->CCR1 = 50;
/* 4.2、配置通道CH1的输出比较模式: TIMx_CCMR1_CC1S = 00(输出模式)
TIMx_CCMR1_OC1M = 110(PWM1模式)
*/
TIM2->CCMR1 &= ~TIM_CCMR1_CC1S;//CH1配置为输出模式
TIM2->CCMR1 &= ~TIM_CCMR1_OC1M_0;
TIM2->CCMR1 |= TIM_CCMR1_OC1M_1;
TIM2->CCMR1 |= TIM_CCMR1_OC1M_2;
/* 4.3、使能通道1:TIMx_CCER_CC1E = 1 */
TIM2->CCER |= TIM_CCER_CC1E;//使能通道CH1
/* 5、使能计数器 */
TIM2->CR1 |= TIM_CR1_CEN;
}
/**
* 更改占空比函数
*/
void Set_PWMDuty(uint8_t Data)
{
TIM2->CCR1 = Data;
}
③主函数文件的代码如下:
c
#include "stm32f10x.h"
#include "TIM2.h"
#include "TIM3.h"
#include "OLED.h"
#include "Delay.h"
int main(void)
{
uint8_t Data = 0;
OLED_Init();
OLED_ShowString(1,1,"Cycle:00000us");
OLED_ShowString(2,1,"Freq:0000Hz");
OLED_ShowString(3,1,"Duty:000%");
TIM2_Init();//TIM2的初始化,产生了一个周期为10ms的PWM波形
TIM3_Init();//TIM3的初始化,开启CH1输出捕获
Set_PWMDuty(Data);
while(1)
{
OLED_ShowNum(1,7,Get_PWMCycle(),5);
OLED_ShowNum(2,6,Get_PWMFrequency(),4);
OLED_ShowNum(3,6,Get_PWMDutycycle(),3);
for(uint8_t i = 0; i<100; i++)
{
Set_PWMDuty(++Data);
OLED_ShowNum(3,6,Get_PWMDutycycle(),3);//显示一下占空比
Delay_ms(20);
}
for(uint8_t i = 0; i<100; i++)
{
Set_PWMDuty(--Data);
OLED_ShowNum(3,6,Get_PWMDutycycle(),3);
Delay_ms(20);
}
}
}
实物演示效果如下:
占空比
2、高级定时器
高级定时器有2个分别为TIM1和TIM8,而stm32f103c8t6只有一个高级定时器(TIM1)。高级定时器有通用定时器的所有功能外,还具备了以下的功能:
(1)重复计数器
(2)死区时间可编程的互补输出
(3)刹车输入信号
高级定时器的结构图如下:
如上图所示:①计数器CNT溢出1次时,重复计数器减1,当重复计数器溢出时(从0减到重复计数器的重装值),才会产生UI和U信号,若开启中断,才会执行中断。
重复计数器值为N,则CNT计数器溢出N+1次才会产生一次更新事件。
②死区时间可编程的互补输出:而互补输出一般情况下用于电机驱动,互补信号:频率周期相等,相位相差180°
③刹车输入信号:通过BKIN引脚输入/事件来控制定时器的比较输出。
高级定时器能产生中断的部位:
❄【注】①使用高级定时器的捕获/比较寄存器的通道输出波形,则要使能TIMx_BDTR_MOE位
②计数器使用的是向下计数+开启UP中断(计数器溢出中断),则在使能计数器前,将状态寄存器TIMx_SR_UIF位置0。否则一上电就会进入一次中断。(因为向下计数,初始前计数器里面的值为0,定时器初始化后,重装载寄存器里面的值会转载到计数器里面,计数器里面的值有0变为N,相当于一次溢出,产生一个更新事件,若开启了中断,则会立马进入中断复位函数)