开发环境:
IDE:MounRiver Studio
MCU:GD32VF103RB
1 PWM输出的工作原理脉冲宽度调制(PWM),是英文“Pulse WidthModula
tion” 的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟
电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制。
GD32 的定时器除了 TIMER5 和 6(基本定时器)。其他的定时器都可以用来产生 PWM 输出。每个定时器有四个通道,每一个通道都有一个捕获比较寄存器,,将寄存器值和计数器值比较,通过比较结果输出高低电平,便可以实现脉冲宽度调制模式(PWM信号)。
关于定时器的相关寄存器的基本原理,请看参考手册。下面谈谈如何使用定时器的寄存器进行PWM输出的。若配置脉冲计数器TIMERx_CNT为向上计数,而重载寄存器TIMERx_CAR配置为N,即TIMERx_CNT的当前计数值数值X在CK_TIMER时钟源的驱动下不断累加,当TIMERx_CNT的数值X大于N时,会重置TIMERx_CNT数值为0重新计数。而在TIMERx_CNT计数的同时,TIMERx_CNT的计数值X会与比较寄存器TIMERx_CHxCV预先存储了的数值A进行比较,当脉冲计数器TIMERx_CNT的数值X小于比较寄存器TIMERx_CHxCV的值A时,输出高电平(或低电平),相反地,当脉冲计数器的数值X大于或等于比较寄存器的值A时,输出低电平(或高电平)。如此循环,得到的输出脉冲周期就为重载寄存器TIMERx_CAR存储的数值(N+1)乘以触发脉冲的时钟周期,其脉冲宽度则为比较寄存器TIMERx_CHxCV的值A乘以触发脉冲的时钟周期,即输出PWM的占空比为A/(N+1)。
估计很多初学者看了上面的一段话都很蒙圈,没关系,下面以向上计数模式为例进行讲解。
在PWM输出模式下,除了CNT(计数器当前值)、CAR(自动重装载值)之外,还多了一个值CHxCV(捕获/比较寄存器值)。当CNT小于CHxCV时,CHxCV通道输出低电平;当CNT等于或大于CHxCV时,CHxCV通道输出高电平。因此得到PWM的一个周期如下:
1.定时器从0开始向上计数;2.当0-t1段,定时器计数器CNT值小于CHxCV值,输出低电平;3.t1-t2段,定时器计数器CNT值大于CHxCV值,输出高电平;4.当CNT值达到CAR时,定时器溢出,重新向上计数...循环此过程。
至此一个PWM周期完成。针对PWM重点关注两个寄存器,
CAR寄存器确定PWM频率,CHxCV寄存器确定占空比。
上文提到了PWM的输出模式,下面讲解PWM的工作模式:l PWM模式1(向上计数) :计数器从0计数加到自动重装载值(CAR),然后重新从0开始计数,并且产生一个计数器溢出事。l PWM模式2(向下计数) :计数器从自动重装载值(CAR)减到0,然后重新从重装载值(CAR)开始递减,并且产生一个计数器溢出事件。
这里我们仅利用 TIMER2产生多路 PWM 输出。如果要产生多路输出,大家可以根据我们的代码稍作修改即可。具体不同定时器对应引脚在对应芯片数据手册的引脚说明(pin description) 中查看。
关于PWM的相关寄存器请参看GD32VF103参考手册。
2 PWM输出实现2.1 PWM代码分析本章要实现通过TIMER2实现2路方波的输出,以TIMER2_CH0 输出 PWM 为例进行讲解。下面我们介绍通过库函数来配置该功能的步骤。1) 开启 TIMER2 时钟以及GPIO的时钟,配置 PA6为复用输出。要使用 TIMER2,我们必须先开启 TIMER2的时钟,这点相信大家看了这么多代码,应该明白了。库函数使能 TIMER2及PA6时钟的方法是:/* enable the GPIOA clock */rcu_periph_clock_enable(RCU_GPIOA);//Enable TIMER2 clockrcu_periph_clock_enable(RCU_TIMER2); 库函数设置 AFIO 时钟的方法是:/* 开启复用功能时钟 */rcu_periph_clock_enable(RCU_AF); 2) 初始化 TIMER2,设置 TIMER2的 CAR 和 PSC。在开启了 TIMER2 的时钟之后,我们要设置 CAR 和 PSC 两个寄存器的值来控制输出 PWM 的周期。这在库函数是通过timer_init()函数实现的,在上一节定时器中断章节我们已经有讲解,这里就不详细讲解,调用的格式为:/* TIMER2 configuration */timer_init_struct.prescaler = 0;timer_init_struct.alignedmode = TIMER_COUNTER_EDGE;timer_init_struct.counterdirection = TIMER_COUNTER_UP;timer_init_struct.period = 999;timer_init_struct.clockdivision = TIMER_CKDIV_DIV1;timer_init_struct.repetitioncounter= 0;timer_init(TIMER2,&timer_init_struct); 3) 设置 TIMER2_CH0的 PWM 模式,使能 TIMER2的 CH0输出。接下来,我们要设置 TIMER2_CH0为 PWM 模式(默认是冻结的),在库函数中,PWM通道设置是通过函数 timer_channel_output_config()来设置的,我们直接来看看结构体 timer_oc_parameter_struct的定义:/* channel output parameterstructure definitions */typedef struct { uint16_t outputstate; /*!< channel outputstate */ uint16_t outputnstate; /*!< channel complementaryoutput state */ uint16_t ocpolarity; /*!< channel outputpolarity */ uint16_t ocnpolarity; /*!< channelcomplementary output polarity */ uint16_t ocidlestate; /*!< idle state ofchannel output */ uint16_t ocnidlestate; /*!< idle state ofchannel complementary output */} timer_oc_parameter_struct; 该结构体主要配置通道的状态,极性等,还需要设置占空比等配置,不同的通道需要分别设置。/* PWM Mode configuration:Channel0 */timer_channel_output_config(TIMER2,TIMER_CH_0, &timer_oc_init_struct); /* 通道2占空比设置 */timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_0, CH0CV_Val);/* PWM模式0 */timer_channel_output_mode_config(TIMER2,TIMER_CH_0,TIMER_OC_MODE_PWM0);/* 不使用输出比较影子寄存器 */timer_channel_output_shadow_config(TIMER2,TIMER_CH_0,TIMER_OC_SHADOW_DISABLE); 4) 使能 TIM3。我们需要使能 TIMER2。使能 TIMER2的方法前面已经讲解过:timer_enable(TIMER2);最后看下主函数代码:/* Includes*********************************************************************/
#include "gd32vf103.h"
#include "gd32vf103r_led_start.h"
#include "gd32vf103r_systick_start.h"
#include "gd32vf103r_timx_start.h"
/*!
brief main function
param[in] none
param[out] none
retval none
*/
int main(void)
{
/* configure the TIMER peripheral */
timer2_init();
while(1)
{
}
} 是不是很简单,这里进行了PWM初始化,最核心的就是timer2_init()函数,其代码如下:
/* Includes*********************************************************************/
#include "gd32vf103r_timx_start.h"
/*
brief configure PWM GPIO
param[in] none
param[out] none
retval none
*/
static void timer_gpio_init(void)
{
/* enable the GPIOA clock */
rcu_periph_clock_enable(RCU_GPIOA);
/* configure GPIOA port */
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
/* enable the GPIOB clock */
rcu_periph_clock_enable(RCU_GPIOB);
/* configure GPIOB port */
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);
}
/*
brief configure the TIMER peripheral
param[in] none
param[out] none
retval none
*/
void timer2_init(void)
{
/* TIMER2 configuration: generate PWM signals with different duty cycles */
/* 定义一个定时器初始化结构体 */
timer_parameter_struct timer_init_struct;
/* 定义一个定时器输出比较参数结构体*/
timer_oc_parameter_struct timer_oc_init_struct;
/* PWM信号电平跳变值 */
uint16_t CH0CV_Val = 250;
uint16_t CH2CV_Val = 500;
/* -----------------------------------------------------------------------
TIMER2 Channel0 duty cycle = (TIMER2_CH0CV/ TIMER2_CAR+1)* 100% = 25%
TIMER2 Channel2 duty cycle = (TIMER2_CH2CV/ TIMER2_CAR+1)* 100% = 50%
----------------------------------------------------------------------- */
// gpio init
timer_gpio_init();
//Enable TIMER2 clock
rcu_periph_clock_enable(RCU_TIMER2);
/* 开启复用功能时钟 */
rcu_periph_clock_enable(RCU_AF);
timer_deinit(TIMER2);
/* TIMER2 configuration */
timer_init_struct.prescaler = 0;
timer_init_struct.alignedmode = TIMER_COUNTER_EDGE;
timer_init_struct.counterdirection = TIMER_COUNTER_UP;
timer_init_struct.period = 999;
timer_init_struct.clockdivision = TIMER_CKDIV_DIV1;
timer_init_struct.repetitioncounter = 0;
timer_init(TIMER2, &timer_init_struct);
/* PWM初始化 */
timer_oc_init_struct.outputstate = TIMER_CCX_ENABLE; /* 通道使能 */
timer_oc_init_struct.outputnstate = TIMER_CCXN_DISABLE; /* 通道互补输出使能(定时器2无效) */
timer_oc_init_struct.ocpolarity = TIMER_OC_POLARITY_HIGH; /* 通道极性 */
timer_oc_init_struct.ocnpolarity = TIMER_OCN_POLARITY_HIGH;/* 互补通道极性(定时器2无效)*/
timer_oc_init_struct.ocidlestate = TIMER_OC_IDLE_STATE_LOW;/* 通道空闲状态输出(定时器2无效)*/
timer_oc_init_struct.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;/*互补通道空闲状态输出(定时器2无效) */
/* PWM Mode configuration: Channel0 */
timer_channel_output_config(TIMER2, TIMER_CH_0, &timer_oc_init_struct);
/* 通道0占空比设置 */
timer_channel_output_pulse_value_config(TIMER2, TIMER_CH_0, CH0CV_Val);
/* PWM模式0 */
timer_channel_output_mode_config(TIMER2,TIMER_CH_0,TIMER_OC_MODE_PWM0);
/* 不使用输出比较影子寄存器 */
timer_channel_output_shadow_config(TIMER2,TIMER_CH_0,TIMER_OC_SHADOW_DISABLE);
/* PWM Mode configuration: Channel1 */
timer_channel_output_config(TIMER2, TIMER_CH_2, &timer_oc_init_struct);
/* 通道2占空比设置 */
timer_channel_output_pulse_value_config(TIMER2, TIMER_CH_2, CH2CV_Val);
/* PWM模式0 */
timer_channel_output_mode_config(TIMER2,TIMER_CH_2,TIMER_OC_MODE_PWM0);
/* 不使用输出比较影子寄存器 */
timer_channel_output_shadow_config(TIMER2,TIMER_CH_2,TIMER_OC_SHADOW_DISABLE);
/* 自动重装载影子比较器使能 */
timer_auto_reload_shadow_enable(TIMER2);
/* TIMER2 enable */
timer_enable(TIMER2);
}
2.2 PWM周期、占空比分析根据前面的参数配置,我们可以算出PWM的输出周期:
PWM=1/(Tclk/(psc+1))*(arr+1)
这里我们 arr=999 psc=0 Tclk=108Mhz ,
PWM=1/(108Mhz/(1))*(999+1)=1/108ms
因此PWM的输出频率108KHz,周期是9.26us。
PWM的占空比为:
Dutycycle=(CHxCV/CAR+1)* 100%
PWM自动重装值为999,2个通道的跳变值分别为250,500。因此,TIMER2的2个通道的占空比分别为25%,50%。 3 PWM输出的实验现象在前面我们输出了TIM3 的通道 0(PA6)、2(PB0)不同占空比的 PWM 信号。接下来就看看PWM的输出,PWM 信号可以通过示波器看到,下面笔者就是用逻辑分析仪查看波形。
首先笔者使用的逻辑分析仪是Kingst LA5016,当然啦,其他的也可以,关于逻辑分析仪的相关使用笔者这里就不介绍了,可以查看官方资料。
首先将通道 0(PA6)、2(PB0)分别接到逻辑分析仪的CH0 – CH3,然后下载程序到板子中,打开Kingst VIS,然后进行采样。
我们就可以看到不同通道的实际周期,占空比等信息。