输出频率可调的方波(占空比不可调,默认为50%)
使用定时器来产生PWM波大家应该都很熟悉,但是如何使用定时器产生频率可调的PWM波呢?这就需要使用定时器的输出比较翻转模式TIM_OCMode_Toggle
。
先大概介绍一些这个模式是如何工作的:
输出比较翻转,顾名思义,就是程序运行的过程中,会一直拿计数器的当前计数值和比较寄存器中的值进行比较,如果当前计数值和比较寄存器中的值相等了,那么就会产生输入/捕获中断,然后定时器会自动地翻转当前的电平输出状态,然后再往比较寄存器中加上一个定值(也就是程序中的CCR1_Val
和CCR2_Val
),让计数器中的值继续去追赶更新后的比较寄存器中的值,以使得定时器还是相隔相同的时间去产生下一次输入捕获中断。
先来看下定时器以及PWM波输出配置程序:
void PWMOut_Init_Adjust_Fre(void)//输出两路频率可调的PWM波
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
/* GPIOA and GPIOB clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 65535;//必须是这个数值65535
TIM_TimeBaseStructure.TIM_Prescaler = 71;//72分频,计数频率为1MHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
/* Output Compare Toggle Mode configuration: Channel1 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;//输出比较翻转模式
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR1_Val;//设置通道1的比较值(跳变值),也就是比较寄存器的初始值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Disable); //使能或者失能TIMx在CCR1上的预装载寄存器
/* Output Compare Toggle Mode configuration: Channel2 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR2_Val;//设置通道2的比较值(跳变值),也就是比较寄存器的初始值
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Disable);
TIM_Cmd(TIM3,ENABLE);
TIM_ITConfig(TIM3,TIM_IT_CC1 | TIM_IT_CC2,ENABLE);
}
上面这个配置程序完成的工作就是配置定时器得到基本参数,包括计数周期、计数计数频率以及PWM工作的模式等等。
下面再来看一下中断服务函数我们都干了啥:
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_CC1) != RESET)//TIM_IT_CC1为捕获中断
{
TIM_ClearITPendingBit(TIM3,TIM_IT_CC1);
capture = TIM_GetCapture1(TIM3);//获取当前计数器中的值
TIM_SetCompare1(TIM3,capture + CCR1_Val);//每次都让比较寄存器再加上一个翻转周期,以保证每次都是相同的翻转周期,TIM_SetCompare1()函数的作用就是把参数传递给比较寄存器当中
}
if(TIM_GetITStatus(TIM3,TIM_IT_CC2) != RESET)
{
TIM_ClearITPendingBit(TIM3,TIM_IT_CC2);
capture = TIM_GetCapture2(TIM3);
TIM_SetCompare2(TIM3,capture + CCR2_Val);
}
}
首先我们要明白这个中断是如何产生的,if(TIM_GetITStatus(TIM3,TIM_IT_CC1) != RESET)
代表的就是捕获/比较中断,也就是意味着计数器中的值和当前比较寄存器中的值相等的时候,就会产生这样一个捕获/比较中断。产生中断之后,我们首先要清零中断标志位TIM_ClearITPendingBit(TIM3,TIM_IT_CC1);
。
然后用一个全局变量capture
去获取当前计数器当中的值,然后下一步就是重要的一步了,下一步就是更新比较寄存器中的值,那么到底更新成多少,才能保证再隔相同的时间产生下一次捕获/比较中断呢?因为计数值是一直向上计数的,所以为了使下一次计数值还能和比较寄存器中的值相等,那肯定比较寄存器的值也要增加,为了使每次产生捕获/比较中断都是相同的时间间隔,所以我们就需要把当前计数器中的值加上CCR1_Val
重新赋值给比较寄存器,也就是每次都得让更新后的比较寄存器中的值比当前计数器中的值大上一个变量CCR1_Val
的值,也就是这一句代码TIM_SetCompare1(TIM3,capture + CCR1_Val);
。
如果还不理解,我再举一个简单的小栗子:
假如现在计数器的值为0,比较寄存器中的值为600(即CCR1_Val
初始化为600),此时对应的PWM输出引脚输出低电平,然后计数器开始向上计数,0,1,2,3,…,597,598,599,600,当计数器一直加到600的时候,也就是当计数器中的值与比较寄存器中的值相等的时候,就会产生一个捕获/比较中断,在中断函数里面,我们要做的就是先获取当前计数器中的值,也就是capture = TIM_GetCapture1(TIM3);
,即当前的capture =600
。然后,在把新的值capture + CCR1_Val
,也就是600+600
重新赋值给比较寄存器中,也就是TIM_SetCompare1(TIM3,capture + CCR1_Val)
,赋值完成之后,此时计数器中的值还是600,而比较寄存器中的值更新为600+600=1200
。此时定时器会自动翻转对应的PWM输出引脚的电平状态,即翻转为高电平。然后退出中断服务函数,计数器计数开始往上计数,600,601,601,…,1198,1199,1200,当计数器加到1200的时候,计数器的值又和比较寄存器中的值相等了,即又会产生一次捕获/比较中断,又进入到了中断服务函数,还是重复刚才的动作,首先清零中断标志位,然后获取当前计数器中的值capture = TIM_GetCapture1(TIM3);
,即当前的capture =1200
。然后再把当前新的值capture + CCR1_Val
,也就是1200+600
重新赋值给比较寄存器中,也就是TIM_SetCompare1(TIM3,capture + CCR1_Val)
,赋值完成之后,此时计数器中的值还是1200,而比较寄存器中的值更新为1200+600=1800
。此时定时器仍然会自动翻转对应的PWM输出引脚的电平状态,即翻转为低电平,然后退出中断服务函数,计数器计数向上计数,然后等待着下一次计数器中的值与比较寄存器中的值1800
相等时,又会产生一次捕获/比较中断,在中断里面还是重复刚才的几个动作,即清零中断标志位、获取当前计数值器中的值以及更新比较寄存器中的值,就这样,一直循环下去,就可以产生占空比为50%的方波。
注意在上面的过程中,变化的是计数器中的值和比较寄存器中的值,不变的是变量CCR1_Val
,在上面的例子中CCR1_Val一直等于600
,也就是这个CCR1_Val
控制着PWM波的频率。假如说计数频率为1MHz,也就是1us计一个数,那么PWM波输出的周期就是CCR1_Val*2us
,因为占空比为50%,所以一个PWM波的周期内高低电平的时间均为CCR1_Val
。假如说CCR1_Val=1000
,那么PWM波周期就是1000*2us=2ms
,也就是500Hz
。然后我们通过调节这个CCR1_Val
的值,就可以实时的改变PWM波输出的频率了。
再来补充一下,如果比较寄存器的值加的过程超过了65535怎么办:
结果就是,超过了也没事,反正计数器和比较寄存器中的值都是最大到65535。假如说现在比较寄存器和计数器相等了,并且都等于65530,然后比较寄存器又加上了一个数100,那么他现在的值就是94(超过65535之后,又从0开始),那么计数器也是从65530开始一个一个往上加的,它的计数过程也是65530,65531,…65535,0,1…94,也就是说超过了65535之后,他俩由于是同样的计数规则,所以说并不影响每次产生中断的时间间隔。
下篇博客介绍频率可调、占空比可调的PWM波输出
参考博客:
1、输出频率可调、占空比可调的PWM波
https://blog.csdn.net/qq_36554582/article/details/88225484
2、蓝桥杯嵌入式比赛备赛手册
https://blog.csdn.net/Zach_z/article/details/80548423