AT32定时器、PWM输出与捕获详解(四):TMR

前言

用AT32F421的基础定时器TMR6配合中断实现定时功能,用通用定时器TMR3实现PMW波的输出与捕获。本文记录了测试AT32F4单片机定时器的过程。

环境

  • VScode ( EIDE + Cortex Debug )
  • Open On-Chip Debugger 0.11.0+dev-snapshot
  • GCC toolchain
  • AT32F421C8T7系统板 & ATLink
  • USB < – > TTL 模块
  • 功能描述

    复位后,用TMR6定时2s,如果在此2s内PC13按键按下,则立即进入产生PWM的模式;如果2s没有按键按下,则进入外部PWM输入的捕获模式。

  • PWM产生:使用通用定时器TMR3的通道1——IOMUX映射到PA6输出。要求能指定PWM频率和占空比。
  • PWM捕获:使用通用定时器TMR3的通道2——IOMUX映射到PA7输入。计算出该PWM的频率和占空比。
  • 代码实现

  • pulse_capture.c
    #include "pulse_capture.h"
    
    // #define AHB_FREQ 120
    extern crm_clocks_freq_type crm_clocks_freq_struct;
    
    /**
     * @brief  generate a PWM wave with specified period and duty cycle
     * @param  priority: getting maximum accuracy in PWM dutycycle for DUTYCYCLE priority,
     *                   getting maximum accuracy in PWM period/frequency for PERIOD priority.
     * @param  period_us: in DUTYCYCLE prioity, this value must be integer in [547, 35e6] with unit us (<0.1Hz ~ 1.831kHz). 
     *                  The limitation comes from TMR3_DIV taking values between 0 and 65535 with TMR3_PR==0xFFFF.
     *                    in PERIOD prioity, this value must be integer in [1, 546] with unit us (1.831kHz ~ 60MHz). 
     *                  The limitation comes from TMR3_DIV taking values between 0 and 65535 with TMR3_PR==0xFFFF.
     * @param  duty_cycle: float value between 0 and 1.
     * @param  pulse_type: effective level (when CxDT > CVAL) is 0 for NEGATIVE pulse type,
     *                     effective level (when CxDT > CVAL) is 1 for POSITIVE pulse type.
     * @retval none
     */
    uint8_t sigle_pwm_gen(accuracy_priority_type priority, uint32_t period_us, float32_t duty_cycle, pulse_effective_type pulse_type, confirm_state one_cycle)
    {
        uint16_t div            = 0;
        uint16_t pr             = 0;
        uint32_t pulse_width    = 0;
        uint32_t current_ch_val = 0;
    
        if(priority==DUTYCYCLE)
        {
            /* 将PR设置为最大值0xFFFF以获得对占空比的最细细分 */
            pr=0xFFFF;
            /* 改变 div 以设置PWM的频率 */
            div = period_us * (float32_t)crm_clocks_freq_struct.ahb_freq/1000000 / (pr+1) + 0.5 - 1;
            if(div<0 || div>0xFFFF)
            {
                printf("param 'period_us' should be in [547, 35e6] us but got %ld, causing div==%d.\r\n",period_us,div);
                return 0xFF;
            }
        }
        else
        {
            /* 不对TMR3时钟分频 以获得对周期的最细细分 */
            div=0;
            /* 改变 pr 以设置PWM的频率 */
            pr= period_us * (float32_t)crm_clocks_freq_struct.ahb_freq/1000000 / (div+1) + 0.5 - 1;
            if(pr<0 || pr>0xFFFF)
            {
                printf("param 'period_us' should be in [1, 546] us but got %ld, causing pr==%d.\r\n",period_us,div);
                return 0xFE;
            }
        }
        tmr_base_init(TMR3, pr , div);        
        // // 设置TMR3溢出事件只能由TMR3计数溢出产生,不允许软件触发和从定时器。
        // TMR3->ctrl1_bit.ovfs=1;
        // 禁止溢出事件
        TMR3->ctrl1_bit.ovfen=1;
        tmr_cnt_dir_set(TMR3, TMR_COUNT_UP);
        tmr_clock_source_div_set(TMR3, TMR_CLOCK_DIV1);
        /* 通道 1 输出配置 */
        tmr_output_config_type tmr_oc_init_structure;
        tmr_output_default_para_init(&tmr_oc_init_structure);
        if (pulse_type == POSITIVE)
        {
            tmr_oc_init_structure.oc_mode       = TMR_OUTPUT_CONTROL_PWM_MODE_A;
            tmr_oc_init_structure.oc_idle_state = FALSE;
            // C1OUT 的有效电平为高
            tmr_oc_init_structure.oc_polarity   = TMR_OUTPUT_ACTIVE_HIGH;
        }
        else //负脉冲,有效为低电平,无效或者IDLE状态为高电平
        {
            tmr_oc_init_structure.oc_mode       = TMR_OUTPUT_CONTROL_PWM_MODE_B;
            tmr_oc_init_structure.oc_idle_state = TRUE;
            tmr_oc_init_structure.oc_polarity   = TMR_OUTPUT_ACTIVE_LOW;
        }
        tmr_oc_init_structure.oc_output_state = TRUE;
    
        if (one_cycle)
        {
            /* 单脉冲 */
            tmr_one_cycle_mode_enable(TMR3, TRUE);
        }
    
        /* 设置通道的计数值 */
        if (0 < duty_cycle && duty_cycle <= (float32_t)65535 / 65536)
        {
            pulse_width = duty_cycle * (pr + 1);
        }
        else
        {
            pulse_width = pr;
            // TOTO: 理想情况下,这里占空比应该为1,但无论d1ct取多少都不可能做到,因为PWM-A的最大占空比就是65535/65536。
        }
        /* output to TMR 3 channel 1 */
        tmr_output_channel_config(TMR3, TMR_SELECT_CHANNEL_1, &tmr_oc_init_structure);
        tmr_channel_value_set(TMR3, TMR_SELECT_CHANNEL_1, pulse_width);
    
        current_ch_val = tmr_channel_value_get(TMR3, TMR_SELECT_CHANNEL_1);
        printf("TMR 3 C1 config... current C1DT: %ld \r\n", current_ch_val);
        /* tmr it enable */
        tmr_interrupt_enable(TMR3, TMR_C1_INT, TRUE);
    
        tmr_channel_enable(TMR3, TMR_SELECT_CHANNEL_1, TRUE);
        /* 选择输入触发源 */
        // tmr_trigger_input_select(TMR3, TMR_SUB_INPUT_SEL_C2DF2);
        /* 从模式配置:选择次定时器的模式为触发模式 */
        // tmr_sub_mode_select(TMR3, TMR_SUB_TRIGGER_MODE);
    
        /*TMR3 输出总开关打开 */
        // tmr_output_enable(TMR3, TRUE);// 这是置位 刹车oen,然而TMR3没有刹车
    
        /*使能 TMR3 */
        tmr_counter_enable(TMR3, TRUE);
        // tmr_event_sw_trigger(TMR3,TMR_C1_SWTRIG);
        // tmr_event_sw_trigger(TMR3,TMR_TRIGGER_SWTRIG);
        return 1;
    }
    
    /**
      * @brief  capture the external PWM inputed from PA7 (TMR3 C2)
      * @param  none
      * @retval none
      */
    void sigle_pwm_cap(void)
    {
        tmr_input_config_type tmr_ic_init_structure;
        
        tmr_input_default_para_init(&tmr_ic_init_structure);
        tmr_ic_init_structure.input_filter_value = 0;
        tmr_ic_init_structure.input_channel_select = TMR_SELECT_CHANNEL_2;
        tmr_ic_init_structure.input_mapped_select = TMR_CC_CHANNEL_MAPPED_DIRECT;
        tmr_ic_init_structure.input_polarity_select = TMR_INPUT_RISING_EDGE;
    
        tmr_pwm_input_config(TMR3, &tmr_ic_init_structure, TMR_CHANNEL_INPUT_DIV_1);
    
        /* select the tmr3 input trigger: C2IF2 */
        tmr_trigger_input_select(TMR3, TMR_SUB_INPUT_SEL_C2DF2);
    
        /* select the sub mode: reset mode */
        tmr_sub_mode_select(TMR3, TMR_SUB_RESET_MODE);
    
        /* enable the sub sync mode */
        tmr_sub_sync_mode_set(TMR3, TRUE);
    
        /* tmr enable counter */
        tmr_counter_enable(TMR3, TRUE);
    
        /* enable the c2 interrupt request */
        tmr_interrupt_enable(TMR3, TMR_C2_INT, TRUE);
    }
    

     

  • main.c
    /**
     **************************************************************************
     * @file     main.c
     * @version  v2.0.5
     * @date     2022-09-26
     * @brief    main program
     */
    
    #include "systick.h"
    #include "usart.h"
    #include "usrkey.h"
    #include "stdio.h"
    #include "at32f421_clock.h"
    #include "pulse_capture.h"
    
    /**
     * @brief  main function.
     * @param  none
     * @retval none
     */
    // 指示当前MCU是发出PWM还是接收PWM,该变量只在main.c中使用
    static char TX = 0;
    /* 由于使用硬件的溢出标志必然导致反复进入ISR而无法在主程序中被读取,
        所以另起一全局变量作为溢出标志。该变量定义在at32f421_int.c中 
    */
    extern char TIM6OVF;
    // 接收到的PWM波的参数,随时可能被ISR修改
    __IO uint16_t PWMin_dutycycle = 0;
    __IO uint32_t PWMin_frequency = 0;
    
    // 获取系统时钟
    crm_clocks_freq_type crm_clocks_freq_struct = {0};
    
    int main(void)
    {
        system_clock_config();
        delay_init();
    
        crm_clocks_freq_get(&crm_clocks_freq_struct);
        // =============================> PC13 init <=============================
        /* PC13配置成内部上拉的输入模式 */
        gpio_init_type gpio_init_struct;
        crm_periph_clock_enable(CRM_GPIOC_PERIPH_CLOCK, TRUE);
        gpio_default_para_init(&gpio_init_struct);
        gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
        gpio_init_struct.gpio_out_type       = GPIO_OUTPUT_PUSH_PULL;
        gpio_init_struct.gpio_mode           = GPIO_MODE_INPUT;
        gpio_init_struct.gpio_pins           = GPIO_PINS_13;
        gpio_init_struct.gpio_pull           = GPIO_PULL_UP;
        gpio_init(GPIOC, &gpio_init_struct);
    
        // =============================> USART init <=============================
        uart_print_init(115200);
        /* output a message on hyperterminal using printf function */
        printf("waiting for user key (PC13) pressing down...\r\n");
        // =============================> TIM 6 init <=============================
        crm_periph_clock_enable(CRM_TMR6_PERIPH_CLOCK, TRUE);
        /* 配置定时器 TMRx_DIV 寄存器和 TMRx_PR 寄存器 */
        /* ahb_freq/24k/10k = 0.5hz */
        /* pr+1 = 10k , div+1 = 24k */
        // 注意!tmr_base_init()将会软件触发一次定时器溢出中断。
        // 然而在ISR中调用了printf(),但是此时USART尚未初始化,因此程序会卡死。
        // 所以USART初始化应在TIM初始化之前!
        tmr_base_init(TMR6, 9999, (crm_clocks_freq_struct.ahb_freq / 5000) - 1);
        /* 向上计数(默认) */
        tmr_cnt_dir_set(TMR6, TMR_COUNT_UP);
        /* 开启定时器溢出中断 */
        tmr_interrupt_enable(TMR6, TMR_OVF_INT, TRUE);
        /* 开启 NVIC 溢出中断 */
        nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
        // // 开启之前先清除中断标志位,以免反复进入ISR。(除非已在在ISR结尾清除)
        // tmr_flag_clear(TMR6, TMR_OVF_FLAG);
        nvic_irq_enable(TMR6_GLOBAL_IRQn, 1, 0);
    
        // ===========================> TX/RX selection <===========================
        // TX = 0;
        // 只进行1个计数周期
        tmr_one_cycle_mode_enable(TMR6, TRUE);
        // 之前设置TIM6时软件产生了溢出事件,开启定时前先清除全局标志
        TIM6OVF = 0;
        // 开启定时器计数
        tmr_counter_enable(TMR6, TRUE);
        // 既没有计时到时,也没有按键按下,则进入循环。
        while (TIM6OVF == 0 && TX == 0)
        {
            usrkey_state_type current_key_state;
            current_key_state = usrkey_state();
            if (current_key_state == PRESS_DOWN)
            {
                TX = 1;
                // 停止TIM6计时
                // tmr_counter_enable(TMR6, FALSE);
                tmr_reset(TMR6);
                break;
            }
        }
        // =============================> TIM 3 init <=============================
        crm_periph_clock_enable(CRM_TMR3_PERIPH_CLOCK, TRUE);
        nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
        nvic_irq_enable(TMR3_GLOBAL_IRQn, 0, 0);
        /* tmr it enable */
        tmr_interrupt_enable(TMR3, TMR_C1_INT | TMR_OVF_INT, TRUE);
        // ===============================> main loop <===============================
    
        if (TX == 1)
        {
            /* LED */
            gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
            gpio_init_struct.gpio_out_type       = GPIO_OUTPUT_PUSH_PULL;
            gpio_init_struct.gpio_mode           = GPIO_MODE_OUTPUT;
            gpio_init_struct.gpio_pins           = GPIO_PINS_13;
            gpio_init_struct.gpio_pull           = GPIO_PULL_NONE;
            gpio_init(GPIOC, &gpio_init_struct);
            /* 配置 TMR 3 输出管脚 PA6 */
            crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);
            gpio_init_struct.gpio_pins           = GPIO_PINS_6;
            gpio_init_struct.gpio_mode           = GPIO_MODE_MUX;
            gpio_init_struct.gpio_out_type       = GPIO_OUTPUT_PUSH_PULL;
            gpio_init_struct.gpio_pull           = GPIO_PULL_NONE;
            gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
            gpio_init(GPIOA, &gpio_init_struct);
    
            gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE6, GPIO_MUX_1);
            /* 在PA6产生PWM */
            uint8_t state = sigle_pwm_gen(PERIOD ,100, (float32_t) 0.2, POSITIVE, FALSE);
            
            while (1)
            {
                if(state==1)
                {
                    // LED blink
                    GPIOC->odt ^= GPIO_PINS_13;
                    delay_ms(300);
                    GPIOC->odt ^= GPIO_PINS_13;
                    delay_ms(300);
                }
            }
            
        }
        else
        {
            /* 配置 TMR 3 输入管脚 PA7 */
            crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);
            gpio_default_para_init(&gpio_init_struct);
            gpio_init_struct.gpio_pins = GPIO_PINS_7;
            gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
            gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
            gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
            gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
            gpio_init(GPIOA, &gpio_init_struct);
    
            gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE7, GPIO_MUX_1);
    
            sigle_pwm_cap();
            while (1)
            {
                printf("Frequency = %ld Hz, Dutycycle = %d%%\r\n", PWMin_frequency, PWMin_dutycycle);
            }
               
        }
    }

  • at32f421_int.c
    /**
     **************************************************************************
     * @file     at32f421_int.c
     * @version  v2.0.5
     * @date     2022-04-02
     * @brief    main interrupt service routines.
     **************************************************************************
     *                       Copyright notice & Disclaimer
     *
     * The software Board Support Package (BSP) that is made available to
     * download from Artery official website is the copyrighted work of Artery.
     * Artery authorizes customers to use, copy, and distribute the BSP
     * software and its related documentation for the purpose of design and
     * development in conjunction with Artery microcontrollers. Use of the
     * software is governed by this copyright notice and the following disclaimer.
     *
     * THIS SOFTWARE IS PROVIDED ON "AS IS" BASIS WITHOUT WARRANTIES,
     * GUARANTEES OR REPRESENTATIONS OF ANY KIND. ARTERY EXPRESSLY DISCLAIMS,
     * TO THE FULLEST EXTENT PERMITTED BY LAW, ALL EXPRESS, IMPLIED OR
     * STATUTORY OR OTHER WARRANTIES, GUARANTEES OR REPRESENTATIONS,
     * INCLUDING BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
     *
     **************************************************************************
     */
    
    /* includes ------------------------------------------------------------------*/
    #include "at32f421_int.h"
    #include "stdio.h"
    
    
    char TIM6OVF = 0;
    
    extern crm_clocks_freq_type crm_clocks_freq_struct;
    
    extern uint16_t PWMin_dutycycle;
    extern uint32_t PWMin_frequency;
    
    __IO uint16_t ic2value = 0;
    
    /** @addtogroup AT32F421_periph_template
     * @{
     */
    
    /** @addtogroup 421_LED_toggle
     * @{
     */
    
    /**
     * @brief  this function handles nmi exception.
     * @param  none
     * @retval none
     */
    void NMI_Handler(void)
    {
    }
    
    /**
     * @brief  this function handles hard fault exception.
     * @param  none
     * @retval none
     */
    void HardFault_Handler(void)
    {
        /* go to infinite loop when hard fault exception occurs */
        while (1)
        {
        }
    }
    
    /**
     * @brief  this function handles memory manage exception.
     * @param  none
     * @retval none
     */
    void MemManage_Handler(void)
    {
        /* go to infinite loop when memory manage exception occurs */
        while (1)
        {
        }
    }
    
    /**
     * @brief  this function handles bus fault exception.
     * @param  none
     * @retval none
     */
    void BusFault_Handler(void)
    {
        /* go to infinite loop when bus fault exception occurs */
        while (1)
        {
        }
    }
    
    /**
     * @brief  this function handles usage fault exception.
     * @param  none
     * @retval none
     */
    void UsageFault_Handler(void)
    {
        /* go to infinite loop when usage fault exception occurs */
        while (1)
        {
        }
    }
    
    /**
     * @brief  this function handles svcall exception.
     * @param  none
     * @retval none
     */
    void SVC_Handler(void)
    {
    }
    
    /**
     * @brief  this function handles debug monitor exception.
     * @param  none
     * @retval none
     */
    void DebugMon_Handler(void)
    {
    }
    
    /**
     * @brief  this function handles pendsv_handler exception.
     * @param  none
     * @retval none
     */
    void PendSV_Handler(void)
    {
    }
    
    /**
     * @brief  this function handles systick handler.
     * @param  none
     * @retval none
     */
    void SysTick_Handler(void)
    {
    }
    
    /**
     * @}
     */
    
    /**
     * @}
     */
    
    // /**
    //  * @brief  exint0 interrupt handler
    //  * @param  none
    //  * @retval none
    //  */
    // void EXINT15_4_IRQHandler(void)
    // {
    //     usrkey_isr();
    // }
    
    /**
     * @brief  TIM 6 overflow interrupt handler
     * @param  none
     * @retval none
     */
    void TMR6_GLOBAL_IRQHandler(void)
    {
        tmr_flag_clear(TMR6, TMR_OVF_FLAG);
        /* 增加应用程序 */
        printf("TIM 6 OVF interrupt detected!\r\n");
        TIM6OVF = 1;
    }
    
    void TMR3_GLOBAL_IRQHandler(void)
    {
        uint32_t capture = 0;
    
        if (tmr_flag_get(TMR3, TMR_C1_FLAG) != RESET)
        {
            tmr_flag_clear(TMR3, TMR_C1_FLAG);
            capture = tmr_channel_value_get(TMR3, TMR_SELECT_CHANNEL_1);
            printf("TIM 3 C1 interrupt detected!\t");
            printf("TIM 3 C1DT val: %ld \r\n", capture);
        }
    
        if (tmr_flag_get(TMR3, TMR_C2_FLAG) != RESET)
        {
            tmr_flag_clear(TMR3, TMR_C2_FLAG);
            capture = tmr_channel_value_get(TMR3, TMR_SELECT_CHANNEL_2);
            printf("TIM 3 C2 interrupt detected!\t");
            printf("TIM 3 C2DT val: %ld \r\n", capture);
    
            if (capture != 0)
            {
                /* duty cycle computation. *100 for percentage */
                PWMin_dutycycle = (tmr_channel_value_get(TMR3, TMR_SELECT_CHANNEL_1) * 100) / capture;
    
                /* frequency computation */
                PWMin_frequency = crm_clocks_freq_struct.ahb_freq / capture;
            }
            else
            {
                PWMin_dutycycle = 0;
                PWMin_frequency  = 0;
            }
        }
        // if (tmr_flag_get(TMR3, TMR_OVF_FLAG) != RESET)
        // {
        //     tmr_flag_clear(TMR3, TMR_OVF_FLAG);
        //     // printf("TIM 3 OVF interrupt detected!\r\n");
        // }
        if (tmr_flag_get(TMR3, TMR_TRIGGER_FLAG) != RESET)
        {
            tmr_flag_clear(TMR3, TMR_TRIGGER_FLAG);
            printf("TIM 3 TRIG interrupt detected!\r\n");
        }
    }

  • TODO

    由于PWM的周期(频率)与 周期寄存器pr 和分频系数寄存器div 的值有关(AT32F421的定时器都是16位的,这些值也都是16位),PWM的占空比和通道数据寄存器CxDT的值(32位)与 pr+1之比有关。

    当指定输出PWM的周期为一个浮点数时,如何由该浮点数计算得到合适的 pr 和 div 来获得“最大动态范围”和“同时在周期和占空比上的很好的精度”,是一个整数规划问题。

    很好理解当 pr 设置为 0xFFFF 时,DxDT能取值的范围就能达到最大:0~65535,此时占空比一定能达到最高的精度。

    同样也很好理解,当 div=0时,不对TMR时钟分频,因此周期可以被分的很细,此时通过 pr 设置周期,就能达到很高的精度。但这时CxDT取值范围就会变小,导致占空比的误差增大。

    首先需要给出一个衡量精度的指标(需综合周期的精度和占空比的精度),然后以 pr 和 div为参数做整数规划。具体还需深入研究,这里先挖坑不填。

    此外PWM捕获部分的计算可以改成浮点数。

    另外发现在主循环里反复printf()不是一个明智的选择,程序会没有规则地在主循环的prinft()与ISR的printf()之间转换。

    调试过程

    在用GDB调试的过程中查看TMR6的寄存器:

     单步一次:

    无法解释的问题?

    复杂的定时器一次可以产生多个中断,在产生PWM波的过程中发现,溢出中断OVF可以在IDEN寄存器bit 0 置0时(即禁止产生溢出中断)时发生。其他通道也是,明明没有使能对应的中断,却可以触发中断。

     其中

    0x4000040c是IDEN,指示哪些中断被使能或禁止。

    0x40000410是ISTS,指示当前所有的中断标志。bit[4:0]分别为C4 C3 C2 C1 OVF的中断标志。

    (TMR3在初始化 pr 的时候会软件触发一次溢出中断来把pr置入它的影子寄存器。所以TMR3在初始化完成后、开启之前,ISTS 为 0x0001,这是正常的。但后面就不正常了)

    我不理解 .JPG

    物联沃分享整理
    物联沃-IOTWORD物联网 » AT32定时器、PWM输出与捕获详解(四):TMR

    发表评论