STM32利用定时器主从和RCR方式输出可控数量的PWM波
程序员文章站
2022-03-14 08:05:35
...
近期在学习和培训时重新熟悉了一下这方面的内容,一方面自己也是整理了一下,一方面也和新人小白们分享一下经验,大神轻喷。
话不多说,直接进入正题。首先知道正常的PWM波是用一个定时器输出的,配置好以后就开始源源不断地输出波形。那么如何控制波形脉冲的个数呢?这里我提出三种设计思路:
- 一个定时器输出PWM波,另一个定时器也通过对时间的计数功能来判断波形个数。比如要输出的波形周期是12.5 ,输出100个,那么把第二个定时器的溢出周期定为1.25就好了,然后去做中断处理。
- 利用定时器的主从模式,即一个是主定时器,一个是从定时器,主定时器产生的更新触发传递给从定时器进行计数,这样保证计数更加精准,CPU计算也会相比1简单一些。
- 利用高级定时器的重复计数功能去做。利用到了高级定时器的RCR寄存器,该寄存器在库函数配置的时候对应的是TIM_TimeBaseInitStruct->TIM_RepetitionCounter,但是缺点在于该寄存器只有8位,最大只能输出256个脉冲。当然,要输出更多也不是不可以,那就是在中断里再做累加和判断。
第一种方法非常普通,本文中我介绍一下后面两种方法的实现。
用定时器的主从模式来做
这里先上代码,再做解释
pwm.c文件:
#include "pwm.h"
void TIM1_PWM_Init(u16 arr,u16 psc)//主定时器
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);//TIM1时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); //GPIO外设时钟使能
//设置该引脚为复用输出功能,输出TIM1 CH1的PWM脉冲波形
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //TIM_CH1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 80K
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 不分频
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_Pulse = arr/2-1; //设置待装入捕获比较寄存器的脉冲值,即占空比50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC1Init(TIM1, &TIM_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
TIM_SelectMasterSlaveMode(TIM1, TIM_MasterSlaveMode_Enable);//主从模式
TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update);//TIM1更新产生触发,把TIM1的CR2的MMS位置为010
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); //CH1预装载使能
TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器
}
void TIM2_PWM_Init(u16 PulseNum)//从定时器
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseStructure.TIM_Period = PulseNum-1;//设置输出的波形数量
TIM_TimeBaseStructure.TIM_Prescaler =0; TIM_TimeBaseStructure.TIM_ClockDivision=0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_SelectInputTrigger(TIM2, TIM_TS_ITR0);//设置SMCR寄存器的TS位,进行触发选择
TIM2->SMCR|=0x07; //设置SMCR寄存器的SMS位,选择外部时钟源模式1
TIM_ITConfig(TIM2,TIM_IT_Update,DISABLE); //定时器2更新先失能
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
//进入TIM2中断意味着脉冲数量够了,之后停止输出
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
TIM_CtrlPWMOutputs(TIM1,DISABLE);
TIM_Cmd(TIM1, DISABLE);
TIM_Cmd(TIM2, DISABLE);
TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);
}
}
void Pulse_output(u16 arr,u16 psc,u16 PulseNum)
{
TIM2_PWM_Init(PulseNum);
TIM_Cmd(TIM2, ENABLE);
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
TIM1_PWM_Init(arr,psc);
TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主输出使能
TIM_Cmd(TIM1, ENABLE);//先使能TIM2再使能TIM1,保证每个脉冲都被检测到。
}
文件里面包括了4个函数,TIM1_PWM_Init(u16 arr,u16 psc)和 TIM2_PWM_Init(u16 PulseNum)都是配置的函数,Pulse_output(u16 arr,u16 psc,u16 PulseNum)是入口函数,它调用了两个配置函数并先后使能,它也在主函数里被调用。
要注意的一点是,由于TIM1是高级定时器,所以一定需要一个主输出使能以后才会真正有输出。pwm.h文件是在声明这几个函数,就没什么好说的了。
主函数main.c
#include "delay.h"
#include "sys.h"
#include "pwm.h"
int main(void)
{ delay_init(); //延时函数初始化
while(1)
{Pulse_output(899,0,100);
delay_ms(10); }
}
主函数就十分简单了,我这里设置波形的周期是12.5 ,每次输出100个(用了1.25),然后每10重复一次。
通过波形仿真可以看到效果
再放大一点看,一次是100个脉冲
用高级定时器的重复计数功能来做
首先要注意的是对于STM32F103系列的来说只有TIM1和TIM8有重复计数功能。
利用这个功能来做的时候就只用一个定时器就好了。重复计数的本质就是设置定时器溢出多少次再产生一个更新中断。RCR的基本知识参考手册里也有
这里也直接贴上代码
pwm.c:
void TIM1_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);//
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); //使能GPIO外设时钟使能
//设置该引脚为复用输出功能,输出TIM1 CH1的PWM脉冲波形
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //TIM_CH1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 80K
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 不分频
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseStructure.TIM_RepetitionCounter = 10;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_Pulse = arr/2-1; //设置待装入捕获比较寄存器的脉冲值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC1Init(TIM1, &TIM_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
TIM_ITConfig(TIM1,TIM_IT_Update,ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主输出使能
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); //CH1预装载使能
TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器
TIM_Cmd(TIM1, ENABLE); //使能TIM1
}
主函数直接调用这个函数就OK了。改变数量就是改变TIM_TimeBaseStructure.TIM_RepetitionCounter的值,但由于RCR只有8位,所以它最大255