PWM简介
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。
RT1064 的 QTMR 定时器支持交替比较模式(CTRLy[OUTMODE]=100,y 表示通道编号,范围:0~3,下同)生成 PWM。
实现上图有几个必要条件
条件1:
CTRLx[LENGTH]=1
手册中对length位置1是这样描述的
计数直到比较,然后重新初始化。
如果计数模式为向上计数,当计数器达到COMP1值时,就成功进行比较。
如果计数模式为向下计数,则当计数器达到COMP2值时,比较就成功了。
当使用输出模式$4时,使用COMP1和COMP2的交替值来生成成功的比较。
例如,计数器计数直到达到COMP1值,重新初始化,计数直到达到COMP2值,重新初始化,计数直到达到COMP1值,等等。
在length中有这样一句话,如果使用输出模式4,那么这个模式又是什么模式呢?
解读fsl_qtmr.c中QTMR_SetupPwm的源码可以辨别出,对CTRL寄存器的OUTMODE位设置为下图红框框出的模式
CNTR的值与 CMPLD1 和 CMPLD2 交替比较,当发生匹配事件时,取反 IO 状态。
具体过程如下:
首先 CNTR 从 0 开始计数,此时 IO 输出低电平,当 CNTR 和 CMPLD1 相等时(t1),IO状态取反(高电平),同时 CNTR 清零;
然后 CNTR 从 0 开始继续计数,当 CNTR 和 CMPLD2相等时(t2),IO 状态取反(低电平),同时 CNTR 清零;
然后 CNTR 从 0 开始继续计数,当CNTR 和 CMPLD1 相等时(t3),IO 状态取反(高电平),同时 CNTR 清零;
然后 CNTR 从 0开始继续计数……
以此往复,产生PWM波
这样 CMPLD1 控制低电平占空比,CMPLD2控制高电平占空比。因此想要修改占空比和周期,只需要修改 CMPLD1 和 CMPLD2 即可。这就是 QTMR 交替比较模式 PWM 输出的原理。
注意:如果只修改 CMPLD1 或 CMPLD2,则 PWM 的占空比和频率都会发生变化!!因为你只改变了低电平/高电平的宽度。
我们仅使用 QTMR4 的通道 3 产生一路 PWM 输出。要用 QTMR 定时器的某个通道产生 PWM 输出
接下来,我们来介绍以下需要用到的几个寄存器的详细信息
寄存器介绍
通道状态&控制寄存器寄存器(TMRx_SCTRLn)
比较寄存器 2 (TMRx_COMP2n)
CMPLD1 和 CMPLD2是 COMP1 和 COMP2 的预加载寄存器,他们的值一般是相等的(COMP1=CMPLD1,COMP2=CMPLD2),如果不相等(比如修改了 CMPLD1/CMPLD2 的值),将会在下一个匹配事件发生时进行同步(同步后就相等了)。
因此,我们要修改占空比,一般通过修改 CMPLD1/CMPLD2 这两个寄存器即可。当然,我们也可以不使用 CMPLD1 和 CMPLD2,直接改变 COMP1 和 COMP2 的值来修改占空比,只不过前者所有波形都是受控的,后者在修改 COMP1/COMP2 的时候,会产生一个不受控的波形(占空比不确定),用示波器可以监控到。
比较值预加载寄存器 1(TMRx_CMPLD1n)
Timer Channel Comparator Load Register 1 (TMRx_CMPLD1n)
该寄存器是 QTMRx_COMP1y 的预加载寄存器。写入改寄存器的值,会在下一个比较匹配事件(由 QTMRx_CSCTRLy[CL1]设置)发生时加载到 COMP1 寄存器。这种操作不会干扰到当前正在进行的 PWM 输出,是一种比较安全的修改方法。
最后,通道比较值预加载寄存器 2(QTMRx_CMPLD2y)我们就不单独列出来了,它的作用和 QTMRx_CMPLD1y 完全一样。只是一个是针对 COMP2 一个是针对 COMP1。
fsl库函数及配置过程
本文用到的是 QTMR4 的通道 3(对应 IO 口:GPIO2_IO27(GPIO_B1_11))来输出 PWM
1.使能QTMR4的时钟
使用函数 CLOCK_EnableClock 使能 QTMR4 时钟,使用方法如下:
CLOCK_EnableClock(kCLOCK_Timer4)
此函数会被 QTMR 定时器初始化函数 QTMR_Init 调用,所以不需要我们显示的调用。
2.初始化GPIO
因为要输出 PWM 波形,因此需要将 GPIO_B1_11(GPIO2_IO27)设置为 QTMR4 通道 3
设置方法如下:
//配置 GPIO_B1_11 为 QTIMER4_TIMER3 的输出引脚
IOMUXC_SetPinMux(IOMUXC_GPIO_B1_11_QTIMER4_TIMER3, 0);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B1_11_QTIMER4_TIMER3, 0x10B0);
3.初始化QTMR
初始化已经在上一篇blog中有详细说明
详情可见:RT1064学习笔记-自己看-QTMR 定时器中断
使用函数 QTMR_Init 来设置,设置方法如下:
qtmr_config_t qtimer4pwm_config;
QTMR_GetDefaultConfig(&qtimer4pwm_config); //先设置为默认配置
qtimer4pwm_config.primarySource= kQTMR_ClockDivide_128; //设置第一时钟源
QTMR_Init(TMR4,kQTMR_Channel_3,&qtimer4pwm_config); //初始化 TIM4 通道 3
4.设置PWM功能
QTMR 的 PWM 功能通过函数 QTMR_SetupPwm 来设置,此函数原型如下:
/*!
* @brief Sets up Quad timer module for PWM signal output.
*
* The function initializes the timer module according to the parameters passed in by the user. The
* function also sets up the value compare registers to match the PWM signal requirements.
*
* @param base Quad Timer peripheral base address
* @param channel Quad Timer channel number
* @param pwmFreqHz PWM signal frequency in Hz
* @param dutyCyclePercent PWM pulse width, value should be between 0 to 100
* 0=inactive signal(0% duty cycle)...
* 100=active signal (100% duty cycle)
* @param outputPolarity true: invert polarity of the output signal, false: no inversion
* @param srcClock_Hz Main counter clock in Hz.
*
* @return Returns an error if there was error setting up the signal.
*/
status_t QTMR_SetupPwm(TMR_Type *base,
qtmr_channel_selection_t channel,
uint32_t pwmFreqHz,
uint8_t dutyCyclePercent,
bool outputPolarity,
uint32_t srcClock_Hz);
第一个参数TMR_Type *base
:指定要使用哪个定时器
第二个参数qtmr_channel_selection_t channel
:指定要使用QTMR的哪个通道
第三个参数uint32_t pwmFreqHz
:所需的PWM波频率,单位为 HZ
第四个参数uint8_t dutyCyclePercent
:所需PWM占空比的百分值
第五个参数bool outputPolarity
:PWM 输出极性,也就是极性是否反转。
第六个参数uint32_t srcClock_Hz
:此参数为 QTMR 的时钟源频率,比如我们设置 QTMR4 的时钟源为 IPG_CLK
的 64 分频,那么此值应该为 150MHz/64=2.34375MHz。
应用示例
假如此时 QTMR4 时钟源频率为 2.34375Mhz,要在 QTMR4 的通道 3 上产生一个频率为500K,占空比为 50%的 PWM 波形,那么就可以使用如下代码:
//PWM 频率 500K,占空比 50%
QTMR_SetupPwm(TMR4, kQTMR_Channel_3, 500000, 50, false, 234375);
5.开启QTMR4的channel3
在上一篇blog中已经说明
//通道 3 在第一时钟源的上升沿计数
QTMR_StartTimer(TMR4,kQTMR_Channel_3,kQTMR_PriSrcRiseEdge);
示例
/**
* @description: 计算2的乘方
* @param {uint8_t} time
* @return {*}
* @author: ShiShengjie
*/
uint8_t Calcu_2invo(uint8_t time)
{
uint8_t i=0;
uint8_t value=1;
if(time>7)value=7;
if(time==0)value=1;
else
{
for(i=0;i<time;i++)value*=2;
}
return value;
}
/**
* @description: QTMR4_CH3_PWM初始化
* @param {u8} prisrc 第一时钟源选择
* @param {u32} clk PWM频率
* @param {u8} duty 占空比,百分比
* @return {*}
* @author: ShiShengjie
*/
void QTMR4_CH3_PWM_Init(uint8_t prisrc,uint32_t clk, uint8_t duty)
{
uint8_t fredivi=1;
qtmr_config_t qtmr_pwm_config;
qtmr_primary_count_source_t qtmr_source;
qtmr_source = (qtmr_primary_count_source_t)prisrc;
//配置IO口
IOMUXC_SetPinMux(IOMUXC_GPIO_B1_11_QTIMER4_TIMER3, 0);
IOMUXC_SetPinConfig(IOMUXC_GPIO_B1_11_QTIMER4_TIMER3, 0x10B0);
fredivi = Calcu_2invo(prisrc-8);
//初始化QTMR
QTMR_GetDefaultConfig(&qtmr_pwm_config);
qtmr_pwm_config.primarySource = qtmr_source;
QTMR_Init(TMR4,kQTMR_Channel_3,&qtmr_pwm_config);
//初始化PWM
QTMR_SetupPwm(TMR4,kQTMR_Channel_3,clk,duty,false,
CLOCK_GetFreq(kCLOCK_IpgClk)/fredivi);
//开启计时器
QTMR_StartTimer(TMR4, kQTMR_Channel_3, kQTMR_PriSrcRiseEdge);
}
/**
* @description: 向函数传入第一时钟源、PWM频率占空比,以计算CMPLD1&2的值
* @param {uint8_t} prisrc 选择第一时钟源
* @param {uint32_t} clk PWM频率
* @param {uint8_t} duty 占空比
* @return {*}
* @author: ShiShengjie
*/
void QTMR_CH_PWM_DutySet(uint8_t prisrc,uint32_t clk,uint8_t duty)
{
uint8_t fredivi=1;
uint32_t srcclk,period,hightime,lowtime;
fredivi = Calcu_2invo(prisrc-8);
srcclk = CLOCK_GetFreq(kCLOCK_IpgClk)/fredivi;
period = (srcclk / clk);
hightime = (period*duty)/100;
lowtime = period-hightime;
TMR4->CHANNEL[kQTMR_Channel_3].CMPLD1 = lowtime;
TMR4->CHANNEL[kQTMR_Channel_3].CMPLD2 = hightime;
}
本文参照正点原子RT1052 开发指南修改编辑。
作者的软件基于逐飞部分库和fsl库开发