这次和大家一起分享一下华大HC32F460的定时器。
首先说一下定时器的分类,一共有四种定时器分别是:

  • 高级控制定时器(Timer6)有3个;
  • 通用控制定时器(Timer4)有3个;
  • 通用定时器(TimerA)有6个;
  • 通用定时器(Timer0)有2个;


再次说一下这几个定时器有什么区别:
看名字就知道高级控制定时器肯定是功能最全的

  • 高级控制定时器 6(Timer6)是一个 16 位计数宽度的高性能定时器,可用于计数产生


不同形式的时钟波形,输出以供外部使用。

  • 该定时器支持三角波和锯齿波两种波形模式,可生成各种 PWM 波形;
  • 单元间可实现软件同步计数和硬件同步计数;
  • 各基准值寄存器支持缓存功能;
  • 支持 2 相正交编码和 3 相正交编码;
  • 支持 EMB 控制。

主要看一下基本框图

 


看框图能看,Timer6 的计数时钟可以有以下几种选择:
a) PCLK0 及 PCLK0 的 4、16、64、256、1024 分频(GCONR.CKDIV[2:0]设定)
b) 内部触发事件触发输入(HCUPR[17:16]或 HCDOR[17:16]设定)
c) TIM6_TRIGA-B 的端口输入(HCUPR[11:8]或 HCDOR[11:8]设定)
d) TIM6_<t>_PWMA 和 TIM6_<t>_PWMB 的 正交编码 输入( HCUPR[7:0] 或HCDOR[7:0]设定)
计数时钟源选择 a 时为软件计数模式,计数时钟源选择 b、c、d 时为硬件计数模式。
上述描述可以看到,b、c、d 时钟互相独立,可分别设定有效或无效,并且当选择 b、c、d 时钟时,a 时钟自动无效。
定时器6的时钟不能按照任意的分频系数进行分频,只能按照特定的频率进行分频,实话实说这一点就不如STM32方便了


计数方式有两种,一个是锯齿波,一个是三角波,两种波形的方式进行计数,当达到设定值后会清零或向下计数
自动重装载值这里叫做通用周期基准值寄存器,官方给的解释是设定每轮计数的计数周期值及对应缓存值 ,说实话这个官方的解释我看了一天也没搞懂是啥意思,很容易和STM32的周期计数混淆,大家要多注意了。

看寄存器的配置就能知道这是一个16位的定时器。
定时器可以产生的中断中官方给的解释如下:

  • 通用比较基准值寄存器(GCMAR-GCMFR)共计 6 个,可分别与计数值比较产生比较匹配。
  • 计数比较匹配时,状态标志寄存器(STFLR)中的 STFLR.CMAF~STFLR.CMFF位分别会被置为 1。
  • 此时若设定中断控制寄存器(ICONR)的 INTENA~INTENF 中相应位为 1 使能中断,则对应的中断请(TMR6_U<t>_GCMA~F)也会被触发。
  • 在硬件捕获事件选择寄存器(HCPAR、HCPBR)选择的捕获输入有效条件产生时,捕获输入动作发生。
  • 此时若设置中断控制寄存器(ICONR)的 INTENA 或 INTENB 位为1 使能中断,则对应的中断请求(TMR6_U<t>_GCMA~B)被触发。
  • 2 个专用比较基准值寄存器(SCMAR-SCMBR)也可分别与计数值比较产生比较匹配。
  • 计数比较匹配时,状态标志寄存器(STFLR)中的 STFLR.CMSPAF~CMSPBF 位分别会被置为 1。此时若设定中断控制寄存器(ICONR)的 INTENSAU<D>或 INTENSBU<D>中相应位为 1 使能中断,则对应的中断请求(TMR6_U<t>_SCMA~B)也会被触发。

看上面的解释可能会云里雾里看不懂啥意思,还不如直接看中断控制寄存器,有多少中断都在中断控制器里写的明明白白,这样的话我们就能很容易的来根据需要配置中断的功能了,具体配置如下图所示




这里面比较值的设置就是根据两个比较基准寄存器的值来进行判断的,这两个寄存器分别是通用比较基准值寄存器,专用比较基准寄存器,其实还有一个是死区时间基准值寄存器

下面直接看代码,看看高级控制定时器的用法

官方给的例程挺多的,有耐心的小伙伴可以仔细分析一下MCU的TIMER6的PWM的锯齿波模式、无缓存输出功能,
看看比较输出的功能是怎么配置的

/*******************************************************************************

 * Include files

 ******************************************************************************/

#include "hc32_ddl.h"



/*******************************************************************************

 * Local type definitions ('typedef')

 ******************************************************************************/



/*******************************************************************************

 * Local pre-processor symbols/macros ('#define')

 ******************************************************************************/

/* KEY0 (SW2)*/

#define  SW2_PORT           (PortD)

#define  SW2_PIN            (Pin03)

/* KEY1 (SW4)*/

#define  SW4_PORT           (PortD)

#define  SW4_PIN            (Pin04)

/* KEY2 (SW3)*/

#define  SW3_PORT           (PortD)

#define  SW3_PIN            (Pin05)

/* KEY3 (SW5)*/

#define  SW5_PORT           (PortD)

#define  SW5_PIN            (Pin06)



/* LED0 Port/Pin definition */

#define  LED0_PORT          (PortE)

#define  LED0_PIN           (Pin06)



/* LED1 Port/Pin definition */

#define  LED1_PORT          (PortD)

#define  LED1_PIN           (Pin07)



/* LED2 Port/Pin definition */

#define  LED2_PORT          (PortB)

#define  LED2_PIN           (Pin05)



/* LED3 Port/Pin definition */

#define  LED3_PORT          (PortB)

#define  LED3_PIN           (Pin09)



/* LED0~1 toggle definition */

#define  LED0_TOGGLE()      (PORT_Toggle(LED0_PORT, LED0_PIN))

#define  LED1_TOGGLE()      (PORT_Toggle(LED1_PORT, LED1_PIN))

#define  LED2_TOGGLE()      (PORT_Toggle(LED2_PORT, LED2_PIN))

#define  LED3_TOGGLE()      (PORT_Toggle(LED3_PORT, LED3_PIN))



/*******************************************************************************

 * Global variable definitions (declared in header file with 'extern')

 ******************************************************************************/



/*******************************************************************************

 * Local function prototypes ('static')

 ******************************************************************************/



/*******************************************************************************

 * Local variable definitions ('static')

 ******************************************************************************/





/*******************************************************************************

 * Function implementation - global ('extern') and local ('static')

 ******************************************************************************/

/**

 *******************************************************************************

 ** \brief Callback function of external interrupt ch.0

 **

 ******************************************************************************/



void Timer6_OverFlow_CallBack(void)

{



}



/**

 ******************************************************************************

 ** \brief  Initialize the system clock for the sample

 **

 ** \param  None

 **

 ** \return None

 ******************************************************************************/

static void SysClkIni(void)

{

    en_clk_sys_source_t       enSysClkSrc;

    stc_clk_sysclk_cfg_t      stcSysClkCfg;

    stc_clk_mpll_cfg_t        stcMpllCfg;

    stc_clk_output_cfg_t      stcOutputClkCfg;





    MEM_ZERO_STRUCT(enSysClkSrc);

    MEM_ZERO_STRUCT(stcSysClkCfg);

    MEM_ZERO_STRUCT(stcMpllCfg);

    MEM_ZERO_STRUCT(stcOutputClkCfg);



    /* Set bus clk div. */

    stcSysClkCfg.enHclkDiv = ClkSysclkDiv1;   // 168MHz

    stcSysClkCfg.enExclkDiv = ClkSysclkDiv2;  // 84MHz



    stcSysClkCfg.enPclk0Div = ClkSysclkDiv1;  // 168MHz   (timer6 cnt)

    stcSysClkCfg.enPclk1Div = ClkSysclkDiv2;  // 84MHz    (timer6 logic)

    stcSysClkCfg.enPclk2Div = ClkSysclkDiv4;  // 42MHz

    stcSysClkCfg.enPclk3Div = ClkSysclkDiv4;  // 42MHz

    stcSysClkCfg.enPclk4Div = ClkSysclkDiv2;  // 84MHz

    CLK_SysClkConfig(&stcSysClkCfg);



    CLK_HrcCmd(Enable);       //Enable HRC



    /* MPLL config. */

    stcMpllCfg.pllmDiv = 2ul;   //HRC 16M / 2

    stcMpllCfg.plln    = 42ul;  //8M*42 = 336M

    stcMpllCfg.PllpDiv = 2ul;   //MLLP = 168M

    stcMpllCfg.PllqDiv = 2ul;   //MLLQ = 168M

    stcMpllCfg.PllrDiv = 2ul;   //MLLR = 168M

    CLK_SetPllSource(ClkPllSrcHRC);

    CLK_MpllConfig(&stcMpllCfg);



    /* flash read wait cycle setting */

    EFM_Unlock();

    EFM_SetLatency(EFM_LATENCY_4);

    EFM_Lock();



    /* Enable MPLL. */

    CLK_MpllCmd(Enable);



    /* Wait MPLL ready. */

    while(Set != CLK_GetFlagStatus(ClkFlagMPLLRdy))

    {

        ;

    }



    /* Switch system clock source to MPLL. */

    CLK_SetSysClkSource(CLKSysSrcMPLL);

}



/**

 *******************************************************************************

 ** \brief  Main function of project

 **

 ** \param  None

 **

 ** \retval int32_t return value, if needed

 **

 ******************************************************************************/

int32_t main(void)

{

    uint16_t                         u16Period;

    uint16_t                         u16Compare;

    stc_timer6_basecnt_cfg_t         stcTIM6BaseCntCfg;

    stc_timer6_port_output_cfg_t     stcTIM6PWMxCfg;

    stc_timer6_gcmp_buf_cfg_t        stcGCMPBufCfg;

    stc_port_init_t                  stcPortInit;

    stc_irq_regi_conf_t              stcIrqRegiConf;



    MEM_ZERO_STRUCT(stcTIM6BaseCntCfg);

    MEM_ZERO_STRUCT(stcTIM6PWMxCfg);

    MEM_ZERO_STRUCT(stcGCMPBufCfg);

    MEM_ZERO_STRUCT(stcPortInit);

    MEM_ZERO_STRUCT(stcIrqRegiConf);



    SysClkIni();



    PWC_Fcg2PeriphClockCmd(PWC_FCG2_PERIPH_TIM61, Enable);



    PORT_SetFunc(PortE, Pin09, Func_Tim6, Disable);    //Timer61 PWMA

    PORT_SetFunc(PortE, Pin08, Func_Tim6, Disable);    //Timer61 PWMB



    stcTIM6BaseCntCfg.enCntMode   = Timer6CntSawtoothMode;              //Sawtooth wave mode

    stcTIM6BaseCntCfg.enCntDir    = Timer6CntDirUp;                     //Counter counting up

    stcTIM6BaseCntCfg.enCntClkDiv = Timer6PclkDiv1;                     //Count clock: pclk0

    Timer6_Init(M4_TMR61, &stcTIM6BaseCntCfg);                          //timer6 PWM frequency, count mode and clk config



    u16Period = 33600u;

    Timer6_SetPeriod(M4_TMR61, Timer6PeriodA, u16Period);               //period set



    u16Compare = 10000u;

    Timer6_SetGeneralCmpValue(M4_TMR61, Timer6GenCompareA, u16Compare);    //Set General Compare RegisterA Value



    u16Compare = 20000u;

    Timer6_SetGeneralCmpValue(M4_TMR61, Timer6GenCompareB, u16Compare);    //Set General Compare RegisterB Value



    stcTIM6PWMxCfg.enPortMode = Timer6ModeCompareOutput;    //Compare output function

    stcTIM6PWMxCfg.bOutEn     = true;                       //Output enable

    stcTIM6PWMxCfg.enPerc     = Timer6PWMxCompareLow;       //PWMA port output Low level when CNTER value match PERAR

    stcTIM6PWMxCfg.enCmpc     = Timer6PWMxCompareHigh;      //PWMA port output High level when CNTER value match with GCMAR

    stcTIM6PWMxCfg.enStaStp   = Timer6PWMxStateSelSS;       //PWMA output status is decide by STACA STPCA when CNTER start and stop

    stcTIM6PWMxCfg.enStaOut   = Timer6PWMxPortOutLow;       //PWMA port output set low level when CNTER start

    stcTIM6PWMxCfg.enStpOut   = Timer6PWMxPortOutLow;       //PWMA port output set low level when CNTER stop

    stcTIM6PWMxCfg.enDisVal   = Timer6PWMxDisValLow;

    Timer6_PortOutputConfig(M4_TMR61, Timer6PWMA, &stcTIM6PWMxCfg);



    stcTIM6PWMxCfg.enPortMode = Timer6ModeCompareOutput;    //Compare output function

    stcTIM6PWMxCfg.bOutEn     = true;                       //Output enable

    stcTIM6PWMxCfg.enPerc     = Timer6PWMxCompareLow;       //PWMB port output Low level when CNTER value match PERAR

    stcTIM6PWMxCfg.enCmpc     = Timer6PWMxCompareHigh;      //PWMB port output High level when CNTER value match with GCMBR

    stcTIM6PWMxCfg.enStaStp   = Timer6PWMxStateSelSS;       //PWMB output status is decide by STACB STPCB when CNTER start and stop

    stcTIM6PWMxCfg.enStaOut   = Timer6PWMxPortOutLow;       //PWMB port output set low level when CNTER start

    stcTIM6PWMxCfg.enStpOut   = Timer6PWMxPortOutLow;       //PWMB port output set low level when CNTER stop

    stcTIM6PWMxCfg.enDisVal   = Timer6PWMxDisValLow;

    Timer6_PortOutputConfig(M4_TMR61, Timer6PWMB, &stcTIM6PWMxCfg);



    /*config interrupt*/

    /* Enable timer61 GOVF interrupt */

    Timer6_ConfigIrq(M4_TMR61, Timer6INTENOVF, true);





    stcIrqRegiConf.enIRQn = Int002_IRQn;                    //Register INT_TMR61_GOVF Int to Vect.No.002

    stcIrqRegiConf.enIntSrc = INT_TMR61_GOVF;               //Select Event interrupt of timer61

    stcIrqRegiConf.pfnCallback = &Timer6_OverFlow_CallBack;  //Callback function

    enIrqRegistration(&stcIrqRegiConf);                     //Registration IRQ



    NVIC_ClearPendingIRQ(stcIrqRegiConf.enIRQn);            //Clear Pending

    NVIC_SetPriority(stcIrqRegiConf.enIRQn, DDL_IRQ_PRIORITY_15);//Set priority

    NVIC_EnableIRQ(stcIrqRegiConf.enIRQn);                   //Enable NVIC





    /*start timer6*/

    Timer6_StartCount(M4_TMR61);



    while(1)

    {

        ;

    }



}

我们直接分析主函数部分
第一步:先开启时钟 :PWC_Fcg2PeriphClockCmd(PWC_FCG2_PERIPH_TIM61, Enable);


直接看源程序,就能找到三个时钟分别是61 62 63
这次我们用的是61也就是第一个时钟
第二步:是配置引脚为TIMER61的输出引脚 也就是端口复用

第三步:配置定时器功能和预分频系数

定时器都有什么功能呢,看源代码能看到有三个

那我们再看一下这个功能是配置的哪个寄存器呢?

这个时候如果还是没看懂,那么我们回过头来看用户手册关于这个定时器是怎么说的

以后我们如果不知道代码中配置的是什么意思了,也可以这样来寻找答案。
第四步:设置通用周期基准值寄存器,也就是重装载值

复制
u16Period = 33600u;

    Timer6_SetPeriod(M4_TMR61, Timer6PeriodA, u16Period);               //period set



这里我们用的是A通道,timer6有三个通道,分别如下图所示

第五步:设置比较

复制
    u16Compare = 10000u;

    Timer6_SetGeneralCmpValue(M4_TMR61, Timer6GenCompareA, u16Compare);    //Set General Compare RegisterA Value



    u16Compare = 20000u;

    Timer6_SetGeneralCmpValue(M4_TMR61, Timer6GenCompareB, u16Compare);    //Set General Compare RegisterB Value



看到这里我们来画个图说明一下


我们制定好波形,波形的频率就是根据预分频值算出来的,然后设置好重装载值和比较值,再设置中断类型,就可以产生响应的中断

第六步:设置输出功能

复制
stcTIM6PWMxCfg.enPortMode = Timer6ModeCompareOutput;    //Compare output function

    stcTIM6PWMxCfg.bOutEn     = true;                       //Output enable

    stcTIM6PWMxCfg.enPerc     = Timer6PWMxCompareLow;       //PWMA port output Low level when CNTER value match PERAR

    stcTIM6PWMxCfg.enCmpc     = Timer6PWMxCompareHigh;      //PWMA port output High level when CNTER value match with GCMAR

    stcTIM6PWMxCfg.enStaStp   = Timer6PWMxStateSelSS;       //PWMA output status is decide by STACA STPCA when CNTER start and stop

    stcTIM6PWMxCfg.enStaOut   = Timer6PWMxPortOutLow;       //PWMA port output set low level when CNTER start

    stcTIM6PWMxCfg.enStpOut   = Timer6PWMxPortOutLow;       //PWMA port output set low level when CNTER stop

    stcTIM6PWMxCfg.enDisVal   = Timer6PWMxDisValLow;

    Timer6_PortOutputConfig(M4_TMR61, Timer6PWMA, &stcTIM6PWMxCfg);



根据以上的代码我们就设置好了比较输出的功能,怎么算比较输出呢,就是当计数值经过比较值的时候输出引脚进行翻转,由高电平变为低电平,或者有低电平变为高电平
这样就产生了占空比可调的PWM波形输出了,看下面的图,大家就清楚了


每次遇到比较,就翻转输出,这样我们设置不同的比较值,就可以有不同占空比的PWM了,这样的控制方式对于步进电机控制上好处多多,我推荐大家可以看看硬石老师的电机开发例程和野火老师的例程,他们用的都是定时器的比较输出的功能来控制步进电机产生梯形加减速、S形加减速。
言归正传我们继续分析代码

 第七步:配置中断功能

复制

/*config interrupt*/

    /* Enable timer61 GOVF interrupt */

    Timer6_ConfigIrq(M4_TMR61, Timer6INTENOVF, true);





    stcIrqRegiConf.enIRQn = Int002_IRQn;                    //Register INT_TMR61_GOVF Int to Vect.No.002

    stcIrqRegiConf.enIntSrc = INT_TMR61_GOVF;               //Select Event interrupt of timer61

    stcIrqRegiConf.pfnCallback = &Timer6_OverFlow_CallBack;  //Callback function

    enIrqRegistration(&stcIrqRegiConf);                     //Registration IRQ



    NVIC_ClearPendingIRQ(stcIrqRegiConf.enIRQn);            //Clear Pending

    NVIC_SetPriority(stcIrqRegiConf.enIRQn, DDL_IRQ_PRIORITY_15);//Set priority

    NVIC_EnableIRQ(stcIrqRegiConf.enIRQn);                   //Enable NVIC


 

55755603f4e6404e2d.png (301.88 KB )

下载附件

2021-3-3 16:52 上传




第八步:使能定时器
    /*start timer6*/
    Timer6_StartCount(M4_TMR61);

这里我们讲解的是高级定时器的功能,剩下的定时器,就是在这个基础上进行简配得到的,所以就不再一一列举了,大家有时间可以看看手册和代码,关键的注意事项在上面都介绍了,大家应该配置没有问题,如果遇到问题了,可以在本帖留言,我看到后会及时回复 谢谢大家了。
---------------------
作者:binoo7
链接:https://bbs.21ic.com/icview-3087336-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐