开发环境:
IDE:MounRiver Studio
MCU:CH32V208
脉冲宽度调制(PWM),是英文“Pulse Width Modulation” 的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制。
CH32V208的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4路的 PWM 输出,这样,CH32最多可以同时产生 30 路 PWM 输出。
每个定时器有四个通道,每一个通道都有一个捕获比较寄存器,,将寄存器值和计数器值比较,通过比较结果输出高低电平,便可以实现脉冲宽度调制模式(PWM信号)。
在上一节,讲解了定时器的相关寄存器即基本原理,本节将不再赘述。下面谈谈如何使用定时器的寄存器进行PWM输出的。若配置脉冲计数器TIMx_CNT为向上计数,而重载寄存器TIMx_ATRLR配置为N,即TIMx_CNT的当前计数值数值X在TIMx_CLK时钟源的驱动下不断累加,当TIMx_CNT的数值X大于N时,会重置TIMx_CNT数值为0重新计数。而在TIMx_CNT计数的同时,TIMx_CNT的计数值X会与比较寄存器TIMx_CHCTLRn预先存储了的数值A进行比较,当脉冲计数器TIMx_CNT的数值X小于比较寄存器TIMx_CHCTLRn的值A时,输出高电平(或低电平),相反地,当脉冲计数器的数值X大于或等于比较寄存器的值A时,输出低电平(或高电平)。如此循环,得到的输出脉冲周期就为重载寄存器TIMx_ATRLR存储的数值(N+1)乘以触发脉冲的时钟周期,其脉冲宽度则为比较寄存器TIMx_CHCTLRn的值A乘以触发脉冲的时钟周期,即输出PWM的占空比为A/(N+1)。
在PWM输出模式下,除了CNT(计数器当前值)、ATRLR(自动重装载值)之外,还多了一个值CHCTLRn(捕获/比较寄存器值)。当CNT小于CHCTLRn时,TIMx_CHx通道输出低电平;当CNT等于或大于CCDATn时,TIMx_CHx通道输出高电平。因此得到PWM的一个周期如下:
1.定时器从0开始向上计数;
2.当0-t1段,定时器计数器TIMx_CNT值小于CHCTLRn值,输出低电平;
3.t1-t2段,定时器计数器TIMx_CNT值大于CHCTLRn值,输出高电平;
4.当TIMx_CNT值达到ATRLR时,定时器溢出,重新向上计数...循环此过程。
至此一个PWM周期完成。针对PWM重点关注两个寄存器,TIMx_ATRLR寄存器确定PWM频率,TIMx_CHCTLRn寄存器确定占空比。
上文提到了PWM的输出模式,下面讲解PWM的工作模式:
这里我们仅利用 TIM3产生多路 PWM 输出。如果要产生多路输出,大家可以根据我们的代码稍作修改即可。具体不同定时器对应引脚在对应芯片数据手册的引脚说明(pin description) 中查看。
本章要实现通过TIM3实现四路方波的输出,以TIM3_CH1 输出 PWM 为例进行讲解。下面我们介绍通过库函数来配置该功能的步骤。
首先要提到的是,PWM 相关的函数设置在库函数文件ch32v20x_tim.h 和ch32v20x_tim.c文件中。
1) 开启 TIM3 时钟以及GPIO的时钟,配置 PA6为复用输出。
要使用 TIM3,我们必须先开启 TIM3 的时钟,这点相信大家看了这么多代码,应该明白了。库函数使能 TIM3 及PA6时钟的方法是:
RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA, ENABLE);
RCC_EnableAPB1PeriphClk(RCC_APB1_PERIPH_TIM3, ENABLE); //使能定时器 3 时钟
2) 初始化 TIM3,设置 TIM3 的 CNT 和 PSC。
在开启了 TIM3 的时钟之后,我们要设置 CNT 和 PSC 两个寄存器的值来控制输出 PWM 的周期。当 PWM 周期太慢(低于 50Hz)的时候,我们就会明显感觉到闪烁了。因此,PWM 周期在这里不宜设置的太小。这在库函数是通过TIM_TimeBaseInit ()函数实现的,在上一节定时器中断章节我们已经有讲解,这里就不详细讲解。
3) 设置 TIM3_CH1的 PWM 模式,使能 TIM3 的 CH2输出。
接下来,我们要设置 TIM3_CH2为 PWM 模式(默认是冻结的),在库函数中,PWM 通道设置是通过函数TIM_OC1Init ()~TIM_OC4Init ()来设置的,不同的通道的设置函数不一样,这里我们使用的是通道2,我们直接来看看结构体 TIM_OCInitTypeDef的定义:
/* TIM Output Compare Init structure definition */
typedef struct
{
uint16_t TIM_OCMode; /* Specifies the TIM mode.
This parameter can be a value of @ref TIM_Output_Compare_and_PWM_modes */
uint16_t TIM_OutputState; /* Specifies the TIM Output Compare state.
This parameter can be a value of @ref TIM_Output_Compare_state */
uint16_t TIM_OutputNState; /* Specifies the TIM complementary Output Compare state.
This parameter can be a value of @ref TIM_Output_Compare_N_state
@note This parameter is valid only for TIM1 and TIM8. */
uint16_t TIM_Pulse; /* Specifies the pulse value to be loaded into the Capture Compare Register.
This parameter can be a number between 0x0000 and 0xFFFF */
uint16_t TIM_OCPolarity; /* Specifies the output polarity.
This parameter can be a value of @ref TIM_Output_Compare_Polarity */
uint16_t TIM_OCNPolarity; /* Specifies the complementary output polarity.
This parameter can be a value of @ref TIM_Output_Compare_N_Polarity
@note This parameter is valid only for TIM1 and TIM8. */
uint16_t TIM_OCIdleState; /* Specifies the TIM Output Compare pin state during Idle state.
This parameter can be a value of @ref TIM_Output_Compare_Idle_State
@note This parameter is valid only for TIM1 and TIM8. */
uint16_t TIM_OCNIdleState; /* Specifies the TIM Output Compare pin state during Idle state.
This parameter can be a value of @ref TIM_Output_Compare_N_Idle_State
@note This parameter is valid only for TIM1 and TIM8. */
} TIM_OCInitTypeDef;
这里我们讲解一下与我们要求相关的几个成员变量:
参数TIM_OCMode设置模式是 PWM 还是输出比较,这里我们是 PWM 模式。
参数TIM_OutputState用来设置比较输出使能,也就是使能 PWM 输出到端口。
参数TIM_OCPolarity用来设置极性是高还是低。
其他的参数TIM_OutputNState,TIM_OCNPolarity,TIM_OCIdleState和TIM_OCNIdleState是高级定时器 TIM1才用到的。
4) 使能 TIM3。
TIM_ARRPreloadConfig (TIM3, ENABLE); // 使能TIM3重载寄存器ATRLR
在完成以上设置了之后,我们需要使能 TIM3。使能 TIM3 的方法前面已经讲解过:
TIM_Cmd (TIM3, ENABLE); //使能定时器3
最后看下主函数代码:
/**
* @brief Main program
* @param None
* @retval None
*/
int main(void)
{
ST_BSP_LED_Dev BSP_LED_Dev0 = LED_DEV0_CONFIG;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
SysTick_Init();
BSP_LED_Init(&BSP_LED_Dev0);
/* TIM3 PWM波输出初始化,并使能TIM3 PWM输出 */
BSP_PWM_Init(999,0);
while( 1 )
{
Delay_ms(500);
BSP_LED_Toggle( &BSP_LED_Dev0);
Delay_ms(500);
}
}
是不是很简单,这里进行了PWM初始化,并对TIM3进行使能。
/**
* [url=home.php?mod=space&uid=2666770]@Brief[/url] TIM3初始化
* [url=home.php?mod=space&uid=3142012]@param[/url] Arr 定时周期
Psc 预分频
* @retval None
*/
/*****************************************************
TIM3 输出PWM信号初始化,只要调用这个函数
TIM3的四个通道就会有PWM信号输出
*****************************************************/
void BSP_PWM_Init(uint16_t Arr,uint16_t Psc)
{
PWM_GPIO_Config();
TIM_Mode_Config(Arr,Psc);
}
PWM_GPIO_Config ()函数没什么说的,就是配置相应的GPIO,最核心的就是TIM_Mode_Config()函数,其代码如下:
/**
* @brief 配置TIM复用输出PWM时用到的I/O
* @param None
* @retval None
*/
static void PWM_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 设置TIM3CLK 为 72MHZ */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
/* GPIOA and GPIOB clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
/*GPIOA Configuration: TIM3 channel 1 and 2 as alternate function push-pull */
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);
/*GPIOB Configuration: TIM3 channel 3 and 4 as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/**
* @brief 配置TIM输出的PWM信号的模式,如周期、极性、占空比
* @param None
* @retval None
*/
/*******************************************************
* TIMxCLK/CK_PSC --> TIMxCNT --> TIMx_ATRLR --> TIMxCNT 重新计数
* TIMx_CCR(电平发生变化)
* 信号周期=(TIMx_ATRLR +1 ) * 时钟周期
* 占空比=TIMx_CCR/(TIMx_ATRLR +1)
******************************************************/
static void TIM_Mode_Config(uint16_t Arr, uint16_t Psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
/* PWM信号电平跳变值 */
uint16_t CCR1_Val = 500;
uint16_t CCR2_Val = 375;
uint16_t CCR3_Val = 250;
uint16_t CCR4_Val = 125;
/* -----------------------------------------------------------------------
TIM3 Channel1 duty cycle = (TIM3_CCR1/ TIM3_ARR+1)* 100% = 50%
TIM3 Channel2 duty cycle = (TIM3_CCR2/ TIM3_ARR+1)* 100% = 37.5%
TIM3 Channel3 duty cycle = (TIM3_CCR3/ TIM3_ARR+1)* 100% = 25%
TIM3 Channel4 duty cycle = (TIM3_CCR4/ TIM3_ARR+1)* 100% = 12.5%
----------------------------------------------------------------------- */
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = Arr; //当定时器从0计数到999,即为1000次,为一个定时周期
TIM_TimeBaseStructure.TIM_Prescaler = Psc; //设置预分频:不预分频
//TIM_TimeBaseStructure.TIM_Period = 999; //当定时器从0计数到999,即为1000次,为一个定时周期
//TIM_TimeBaseStructure.TIM_Prescaler = 0; //设置预分频:不预分频
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ; //设置时钟分频系数:不分频(这里用不到)
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
/* PWM1 Mode configuration: Channel1 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //配置为PWM模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR1_Val; //设置跳变值,当计数器计数到这个值时,电平发生跳变
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //当定时器计数值小于CCR1_Val时为高电平
TIM_OC1Init(TIM3, &TIM_OCInitStructure); //使能通道1
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
/* PWM1 Mode configuration: Channel2 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR2_Val; //设置通道2的电平跳变值,输出另外一个占空比的PWM
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //使能通道2
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
/* PWM1 Mode configuration: Channel3 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR3_Val; //设置通道3的电平跳变值,输出另外一个占空比的PWM
TIM_OC3Init(TIM3, &TIM_OCInitStructure); //使能通道3
TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);
/* PWM1 Mode configuration: Channel4 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR4_Val; //设置通道4的电平跳变值,输出另外一个占空比的PWM
TIM_OC4Init(TIM3, &TIM_OCInitStructure); //使能通道4
TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_CtrlPWMOutputs(TIM3, ENABLE );
TIM_ARRPreloadConfig(TIM3, ENABLE); // 使能TIM3重载寄存器ATRLR
/* TIM3 enable counter */
TIM_Cmd(TIM3, ENABLE); //使能定时器3
}
根据前面的参数配置,我们可以算出PWM的输出周期:
PWM=1/(Tclk/(psc+1))*(arr+1)
这里我们 arr=999 psc=0 Tclk=144Mhz ,
PWM=1/(144Mhz/(1))*(999+1)=1/144ms
因此PWM的输出频率144KHz,周期是6.94us。
PWM的占空比为:
Dutycycle=(TIMx_CCR/ TIMx_ATRLR+1)* 100%
PWM自动重装值为999,四个通道的跳变值分别为500,375,250,125。因此,TIM3的四个通道的占空比分别为50%,37.5%,25%,12.5%。
值得注意的是,这里的系统时钟是144Mhz,当然也可以配置成其他频率,在文件system_ch32v20x.c中修改。
在前面我们输出了TIM3 的通道 1(PA6)、2(PA7)、3(PB0)、4(PB1)不同占空比的 PWM 信号。接下来就看看PWM的输出,PWM 信号可以通过示波器看到。下面笔者就是用逻辑分析仪查看波形。
首先笔者使用的逻辑分析仪是Kingst LA5016,当然啦,其他的也可以,关于逻辑分析仪的相关使用笔者这里就不介绍了,可以查看官方资料。
首先将通道 1(PA6)、2(PA7)、3(PB0)、4(PB1)分别接到逻辑分析仪的CH0 – CH3,然后下载程序到板子中,打开Kingst VIS,然后进行采样。
我们就可以看到不同通道的实际周期,占空比等信息。从上图可以看到,实际测量的频率和占空比和理论是相符的。
更多回帖