通用定时器的定时与比较输出(PWM)

定时器的基本组成

image-20201121231034703

STM32的定时器

1、基本定时器

基本定时器:TIM6 和 TIM7

基本定时器基本上只有定时功能。

基本定时器TIM6和TIM7各包含一个16位自动装载计数器,由各自的可编程预分频器驱动。

预分频器分频之后的 “计数频率” ,给计数器提供时钟,计数器从0开始计数,计数到重装值时,从0再次开始计数。

image-20201121231718870

通用定时器

通用定时器 :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。

image-20201122203946041

分频系数的设置

预分频器可以将计数器的时钟频率按1到65536之间的任意值分频。它是基于一个(在TIMx_PSC寄存器中的)16位寄存器控制的16位计数器。

预分频系数的设置,就是往寄存器里面写 ( 分频值 -1

image-20201122204810310

自动重装载值的设置

自动重装载值的时钟源是 分频后的时钟

自动重装载值的设置,就是往寄存器里面写 (装载值 -1)

image-20201122205057997

预装载配置

预装载:就相当于缓冲器。

想要改变 装载值 的时候;改变装载值,就是往寄存器里面写数据。装载值就是计数周期

1、不启用:寄存器的值会被立刻改变,计数的溢出条件会在这个周期发生改变。

2、启用:寄存器的值不会被立刻改变,而是在下一个周期才会被改变。一般为了保证延时的稳定性,要开启预装载

中断配置

定时器模式下,只需要开启 “更新中断”

更新中断的产生,是当计数器的值发生溢出(计数到装载值时)。

更新中断对应的寄存器位:CR1寄存器的第0位

image-20201122210141815

使能了更新中断后,每次产生数据溢出时,程序就会进入到中断服务函数中运行。

对应的中断标志位:SR寄存器的第0位

image-20201122210307479

清零方式:状态寄存器已经标明。

image-20201122210456525

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通用定时器---比较输出

什么是输出比较功能

​ 新功能:捕获/比较寄存器

​ 时钟源 分频器 装载值 计数器 捕获/比较寄存器

如果我们将定时器设置成输出比较功能,那么捕获/比较寄存器就是 比较功能 比较寄存器器,寄存器里是一个比较值 。

​ 时钟源 分频器 装载值

​ 计数器

​ 比较值

输出比较的思想

image-20201122212813423

当前值(CNT)在 时钟的控制下,跟比较值(CCR) 相同的时候,就会产生一定的输出(电平的输出、中断的输出),当前值到达ARR的时候,就会产生跟新中断。

输出比较的时钟源

从上面可以看出,时钟源就是定时器的时钟源。

计数频率

计数频率,也是定时器的计数频率

PSC寄存器的值:16位

写入寄存器需要 -1

预装载值

预装载值,也是定时器的预装载值

ARR寄存器的值:16位

写入寄存器需要 -1

比较值

比较值:类似于装载值 ,我们可以设置

当计数值 = 比较值
1、输出一个有效或无效电平(OCxM 模式以及CCxP)来控制IO高低
2、设置中断标志位为1

写入寄存器 不 需要 -1

image-20201122213622215

每个定时器有四个比较通道,也就是有四个“比较寄存器”,可以使用其中的一个,也可以使用多个,不同的通道产生不同的中断。

image-20201122213801792 image-20201122213829771

输出有效电平控制

image-20201122214144737

比较 预装载使能

预装载的作用:也是缓冲作用,与上面的预装载功能相同。

OCxPE:输出比较x预装载使能 (Output compare x preload enable)

输出比较模式控制

OCxM[2:0]:输出比较x模式 (Output compare x mode)

image-20201122214450874

匹配的意思:就是当前计数值与比较值相同

目前使用的功能是PWM1和PWM2

PWM1:当前值<比较值:输出 “有效电平” 当前值>比较值:输出 “无效电平”

PWM2:当前值<比较值:输出 “无效电平” 当前值>比较值:输出 “有效电平”

输出比较使能

就是将前面的 “有效电平” 和 “无效电平” 输出 到对应IO口的电平。

image-20201122214948279

CCxP:输出极性的选择 CCxE:输出使能

输出极性的选择

输出极性,就是输出的有效电平极性。有效电平前面的比较结果。

image-20201122215113558

输出使能的控制

image-20201122215213160

1、使能,将对应的电平输出到引脚上

2、不使能,引脚上不能输出对应的电平。但是如果使能了中断,当前值 == 比较值,是会产生中断的。

定时器比较输出,控制管脚输出PWM。

(1)不使能定时器的输出

(2)在中断中,判断定时器的中断状态,只用一个比较值,控制管脚的两种状态。

使能输出,不开中断

输出PWM

配置管脚的输出模式

复用功能推挽:①复用功能,②推挽输出强高强低

使能OC的输出

不需要开中断,对应管脚上,自动产生对应的电平变化。

不使能输出,开中断

如果说,不开中断的比较输出,只能作用到 输出的对应IO上。

如果没有复用的引脚,是不能够自动输出PWM的。

所以说,为了解决这个问题,在中断中控制(在比较的过程中会产生中断),在中断中控制想要控制的IO口,状态的变化。

image-20201122215840016

LED的亮度控制

PWM的原理

PWM作用到 灯和蜂鸣器...上,不同的占空比,只是改变做功的大小。

image-20201122232532706

定时器的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寄存器的值成比例。

posted @ 2020-11-23 08:56  啊振不坏  阅读(4113)  评论(0)    收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css