通用定时器的定时与比较输出(PWM)
定时器的基本组成
STM32的定时器
1、基本定时器
基本定时器:TIM6 和 TIM7
基本定时器基本上只有定时功能。
基本定时器TIM6和TIM7各包含一个16位自动装载计数器,由各自的可编程预分频器驱动。
预分频器分频之后的 “计数频率” ,给计数器提供时钟,计数器从0开始计数,计数到重装值时,从0再次开始计数。
通用定时器
通用定时器 :TIM2、3、4、5
在定时的功能上,输入捕获、输出比较
输入捕获:可以用来检测IO上电平变化之间的时间间隔。
输出比较:可以用来控制IO输出电平高低 的时间。
PWM、 用来进行编码器的检测
高级控制定时器
高级控制定时器:TIM1、TIM8
定时功能、 输入捕获、 输出比较、
死区时间的控制、互补PWM输出
一般我们会用于的电机控制:电桥电路电机
STM32F103R8 高级控制定时器:1个; 通用定时器:3个
STM32通用定时器---定时
通用定时器简介
- 时钟源:
外部时钟:
输入通道上来的电平
内部触发输入:将另一个定时器的作为当前定时器时钟输入
- 分频值:
可以多少分频:1-65536 216
- 计数器的计数范围
216
- 重装载值的范围
216
- 计数方式
向上计数:从0,1,2,3,4 ......装载值 (触发中断)
向下计数:装载值.......4,3,2,1,0 (触发中断)
时钟源的选择
内部时钟
如果禁止了从模式控制器(TIMx_SMCR寄存器的SMS=000),则CEN、 DIR是事实上的控制位 ,并且只能被软件修改 。
只要CEN位被写成’1’,预分频器的时钟就由内部时钟CK_INT提供。
所以说,只要不开启从模式,使能CEN位,时钟源就是内部时钟源;也就是72MHz。
stm32f103C8的通用定时器时钟
AHB的时钟选择是72MHz,APB1的分频是2分频,APB1的时钟是36MHz。APB1的分频是2分频,所以TIMx_CLK的时钟源频率是 36*2 = 72MHz。
分频系数的设置
预分频器可以将计数器的时钟频率按1到65536之间的任意值分频。它是基于一个(在TIMx_PSC寄存器中的)16位寄存器控制的16位计数器。
预分频系数的设置,就是往寄存器里面写 ( 分频值 -1 )

自动重装载值的设置
自动重装载值的时钟源是 分频后的时钟。
自动重装载值的设置,就是往寄存器里面写 (装载值 -1)
预装载配置
预装载:就相当于缓冲器。
想要改变 装载值 的时候;改变装载值,就是往寄存器里面写数据。装载值就是计数周期
1、不启用:寄存器的值会被立刻改变,计数的溢出条件会在这个周期发生改变。
2、启用:寄存器的值不会被立刻改变,而是在下一个周期才会被改变。一般为了保证延时的稳定性,要开启预装载
中断配置
定时器模式下,只需要开启 “更新中断”
更新中断的产生,是当计数器的值发生溢出(计数到装载值时)。
更新中断对应的寄存器位:CR1寄存器的第0位

使能了更新中断后,每次产生数据溢出时,程序就会进入到中断服务函数中运行。
对应的中断标志位:SR寄存器的第0位

清零方式:状态寄存器已经标明。
read / clear (rc_w0):软件可以读此位,也可以通过写’0’清除此位,写’1’对此位无影响。
寄存器配置为定时器功能
思想:配置好定时器的功能,先不开启定时器,在使用的时候打开定时器的定时功能,使用结束,关闭定时器。
//定时器的配置
void Time4_Config(u16 psc, u16 arr)
{
RCC->APB1ENR |= (1<<2);
TIM4->CR1 = 0; //向上计数
TIM4->CR1 |= (1<<7); //缓冲器
TIM4->CNT = 0;
TIM4->PSC = psc-1;
TIM4->ARR = arr-1;
TIM4->DIER |= (1<<0); //更新中断
NVIC_SetPriority(TIM4_IRQn, 6);
NVIC_EnableIRQ(TIM4_IRQn);
// TIM4->CR1 |= (1<<0); //使能计数
TIM2->CR1 &=~ (1<<0); //关闭计数
}
//中断服务函数
vu32 usTick = 0;
void TIM4_IRQHandler(void)
{
if((TIM4->SR & (1<<0)) != 0)
{
//清除中断,如何判断需不需要手动清零,就看他对应的标志位的说明。
TIM4->SR &= ~(1<<0);
usTick ++;
}
}
//us 延时的构造
void Delay_us(uint32_t time)
{
TIM2->CNT = 0;
TIM2->CR1 |= (1<<0); //使能定时器
usTick = 0;
while(usTick < time); //阻塞型延时
TIM2->CR1 &= ~(1<<0);
}
库函数配置定时器功能
//定时器的配置
void TIM4_Config(u16 psc, u16 arr)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //开时钟
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Prescaler = psc-1;
TIM_TimeBaseInitStruct.TIM_Period = arr-1;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStruct);
TIM_ARRPreloadConfig(TIM4, ENABLE);
//中断
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
NVIC_SetPriority(TIM4_IRQn, 8);
NVIC_EnableIRQ(TIM4_IRQn);
//调用延时再开启
TIM_Cmd(TIM4, DISABLE);
}
//中断服务函数的书写;
//说明:中断服务函数使用库函数的标志查询和清除函数,会将定时器的输出频率减半(1s延时=>2s延时),不知道原因在哪。所以说,中断服务函数用寄存器方式写。
vu32 us_tick = 0;
void TIM4_IRQHandler(void)
{
// if(TIM_GetFlagStatus(TIM4, TIM_FLAG_Update) == SET)
// {
// TIM_ClearFlag(TIM4, TIM_FLAG_Update);
// us_tick ++;
// }
// if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
// {
// TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
// us_tick++;
// }
if((TIM4->SR & (1<<0)) != 0)
{
TIM4->SR &= ~(1<<0);
us_tick++;
}
}
//us 延时函数
void Delay_us(uint32_t time)
{
us_tick = 0;
TIM_ClearCounter(TIM4);
TIM_Cmd(TIM4, ENABLE); //防止us_tick加溢出
while(us_tick < time);
TIM_Cmd(TIM4, DISABLE);
}
STM32通用定时器---比较输出
什么是输出比较功能
新功能:捕获/比较寄存器
时钟源 分频器 装载值 计数器 捕获/比较寄存器
如果我们将定时器设置成输出比较功能,那么捕获/比较寄存器就是 比较功能 比较寄存器器,寄存器里是一个比较值 。
时钟源 分频器 装载值
计数器
比较值
输出比较的思想
当前值(CNT)在 时钟的控制下,跟比较值(CCR) 相同的时候,就会产生一定的输出(电平的输出、中断的输出),当前值到达ARR的时候,就会产生跟新中断。
输出比较的时钟源
从上面可以看出,时钟源就是定时器的时钟源。
计数频率
计数频率,也是定时器的计数频率
PSC寄存器的值:16位
写入寄存器需要 -1
预装载值
预装载值,也是定时器的预装载值
ARR寄存器的值:16位
写入寄存器需要 -1
比较值
比较值:类似于装载值 ,我们可以设置
当计数值 = 比较值
1、输出一个有效或无效电平(OCxM 模式以及CCxP)来控制IO高低
2、设置中断标志位为1
写入寄存器 不 需要 -1
每个定时器有四个比较通道,也就是有四个“比较寄存器”,可以使用其中的一个,也可以使用多个,不同的通道产生不同的中断。
输出有效电平控制
比较 预装载使能
预装载的作用:也是缓冲作用,与上面的预装载功能相同。
OCxPE:输出比较x预装载使能 (Output compare x preload enable)
输出比较模式控制
OCxM[2:0]:输出比较x模式 (Output compare x mode)
匹配的意思:就是当前计数值与比较值相同
目前使用的功能是PWM1和PWM2
PWM1:当前值<比较值:输出 “有效电平” 当前值>比较值:输出 “无效电平”
PWM2:当前值<比较值:输出 “无效电平” 当前值>比较值:输出 “有效电平”
输出比较使能
就是将前面的 “有效电平” 和 “无效电平” 输出 到对应IO口的电平。
CCxP:输出极性的选择 CCxE:输出使能
输出极性的选择
输出极性,就是输出的有效电平极性。有效电平前面的比较结果。
输出使能的控制
1、使能,将对应的电平输出到引脚上
2、不使能,引脚上不能输出对应的电平。但是如果使能了中断,当前值 == 比较值,是会产生中断的。
定时器比较输出,控制管脚输出PWM。
(1)不使能定时器的输出
(2)在中断中,判断定时器的中断状态,只用一个比较值,控制管脚的两种状态。
使能输出,不开中断
输出PWM
配置管脚的输出模式
复用功能推挽:①复用功能,②推挽输出强高强低
使能OC的输出
不需要开中断,对应管脚上,自动产生对应的电平变化。
不使能输出,开中断
如果说,不开中断的比较输出,只能作用到 输出的对应IO上。
如果没有复用的引脚,是不能够自动输出PWM的。
所以说,为了解决这个问题,在中断中控制(在比较的过程中会产生中断),在中断中控制想要控制的IO口,状态的变化。
LED的亮度控制
PWM的原理
PWM作用到 灯和蜂鸣器...上,不同的占空比,只是改变做功的大小。
定时器的PWM格式输出
ARR的值决定周期 CCR的值决定占空
1、非中断:只能输出到对应的IO口,PWM的输出是固定的。
2、中断:可以控制任何一个管脚,输出 “PWM形式” 的波形。
呼吸灯的制作:
思路:
1、控制高低电平输出的时间
阻塞型的延时,会导致while(1)的其他内容无法运行,while(1)只能调节灯的亮度
for(u16 i=0; i<2000; i+=5) //执行2000次,每次2000us = 2ms;共:4s
{
Led_On(LED1_PORT, LED1_PIN);
Delay_us(i);
Led_Off(LED1_PORT, LED1_PIN);
Delay_us(2000-i);
}
for(u16 i=0; i<2000; i+=5)
{
Led_Off(LED1_PORT, LED1_PIN);
Delay_us(i);
Led_On(LED1_PORT, LED1_PIN);
Delay_us(2000-i);
}
2、定时器输出比较功能,自动在IO口输出 可控占空比的PWM
非中断:
①分频:72分频,计数一次1us;②ARR决定周期,不改变;③CCRx决定占空比,调节。
//IO的管脚输出配置为 -- 复用推挽输出
//打开OC输出,不开中断。
//是能定时器,自动在对应的IO口输出 可控的高低电平。
void Time3_Config(u16 psc, u16 arr, u16 ccr)
{
RCC->APB1ENR |= (1<<TIM3_CLK); //时钟是第1位
TIM3->CR1 = 0; //向上计数
TIM3->CR1 |= (1<<TIM3_ARPE); //预装载
TIM3->CCMR2 = 0;
TIM3->CCMR2 |= (1<<TIM3_OC4PE); //使能预装载
TIM3->CCMR2 |= (CH4_PWM1<<12); //PWM1
// TIM_OutputHigh(TIM3, CH4);
TIM_OutputLow(TIM3, CH4);
TIM_OutputEnable(TIM3, CH4);
TIM_ClearCounter(TIM3);
TIM_SetPrescaler(TIM3, psc);
TIM_SetAutoReload(TIM3, arr);
TIM_SetCompare4(TIM3, ccr);
TIM_Enable(TIM3);
}
//按键控制LED的亮度的变化,只需要改变CCRx寄存器的值,就可以改变输出的PWM占空比。
u16 light[5] = {1, 2000*0.2, 2000*0.5, 2000*0.7, 2000};
u8 i = 0;
while(1)
{
if(Key_TaskTime[0] > Key_TaskTime[1])
{
if(Get_KeyValue()) //按键按下
{
Led_SetLight(light[i++]);
i %= sizeof(light)/sizeof(light[0]); //==> if(i == 5) i=0;
}
Key_TaskTime[0] = 0;
}
}
//Led_SetLight()
void Led_SetLight(u16 ccr)
{
// TIM3->CR1 &=~(1<<0); //关闭
TIM3->CNT = 0;
TIM3->CCR4 = ccr;
// TIM3->CR1 |= (1<<0); //使能
}
3、中断的方式控制IO的输出
原因分析:
(1)由于在管脚上输出PWM,只能是 “复用功能的管脚” 上才能输出,未复用的引脚,无法输出PWM。一个定时器只要4个输出通道,也就对应着4个IO口。
(2)定时器的4个通道对应的IO引脚,可能连接着重要的控制端口,如果是PWM的输出,IO口的模式就是AF_PP,IO口已经不能够被ODR寄存器控制,如果有重要的控制信号到来,就会导致端口无法使用。
用这个方法,可以在任意管脚上输出 类似于 PWM的波形。
①:关闭OCx的输出使能;②:开启中断;③:判断中断,指定管脚的输出状态。
void Time3_Config(u16 psc, u16 arr, u16 ccr)
{
RCC->APB1ENR |= (1<<TIM3_CLK); //时钟是第1位
TIM3->CR1 = 0; //向上计数
TIM3->CR1 |= (1<<TIM3_ARPE); //预装载
TIM3->CCMR2 = 0;
TIM3->CCMR2 |= (1<<TIM3_OC4PE); //使能预装载
TIM3->CCMR2 |= (CH4_PWM1<<12); //PWM1
// TIM_OutputHigh(TIM3, CH4);
TIM_OutputLow(TIM3, CH4);
// TIM_OutputEnable(TIM3, CH4);
TIM_OutputDisable(TIM3, CH4);
TIM3->DIER |= (1<<4); //开启比较中断
TIM3->DIER |= (1<<0); //开启更新中断
NVIC_SetPriority(TIM3_IRQn, 2);
NVIC_EnableIRQ(TIM3_IRQn);
TIM_ClearCounter(TIM3);
TIM_SetPrescaler(TIM3, psc);
TIM_SetAutoReload(TIM3, arr);
TIM_SetCompare4(TIM3, ccr);
TIM_Enable(TIM3);
}
//中断服务函数
void TIM3_IRQHandler(void)
{
//比较中断
if((TIM3->SR & (1<<4)) != 0)
{
TIM_ClearFlag(TIM3, 4);
Led_Off(LED1_PORT, LED1_PIN);
}
//更新中断
if((TIM3->SR & (1<<0)) != 0)
{
TIM_ClearFlag(TIM3, 0);
Led_On(LED1_PORT, LED1_PIN);
}
}
//改变CCRx的值,就可以改变占空比的大小。进而改变灯的亮度
//改变方式,同上。
蜂鸣器嘀嘀音乐
音乐的三要素
1、n节拍:执行时间 ==> 每个节拍的时间是固定的,
一个音调是n个节拍,就是在一段时间输出一个固定的频率的PWM信号
2、音调:频率 ==> ARR寄存器的值
3、音量:占空比 ==> CCRx寄存器的值
节拍控制
控制节拍,就是控制 每个音调的 执行时间。
采用非阻塞的滴答定时器延时
//输组的连个元素:{起始时间, 终止时间}
vu32 Music_Time[2] = {0, 1}; //系统定时器1ms,计数一次
void SysTick_Handler(void) //1ms
{
Music_Time[0] ++;
}
if(Music_Time[0] > Music_Time[1])
{
Music_Time[0] = 0;
//改变到下一个音调的时间
Music_Time[1] = DELAY_SPACE * tune_durt[row][1]; //二维数组的第二列存放节拍时间
}
音调控制
音调的控制,就是控制 ARR寄存器的值,当PSC固定时,ARR的值就是决定着周期。
//PSC=72; T = ARR/1MHz ARR = 1M*T = 1M/f
//ARR = 1M/Freq
void Set_MusicFreq(u16 freq)
{
TIM_ClearCounter(TIM3);
TIM_SetAutoReload(TIM3, Music_Freq/freq); //音调=频率
//控制占空比不变,就是控制CCRx寄存器的值,与ARR成比例
TIM_SetCompare4(TIM3, Music_Freq/freq/40); //音强=占空
}
音量控制
控制CCR寄存器的值与ARR寄存器的值成比例。

浙公网安备 33010602011771号