[单片机芯片]

CH32L103 TIM应用

[复制链接]
216|0
手机看帖
扫描二维码
随时随地手机跟帖
L-MCU|  楼主 | 2024-7-10 13:33 | 显示全部楼层 |阅读模式
本帖最后由 L-MCU 于 2024-7-10 16:28 编辑

1、定时器介绍
CH32L103具有一个16位的高级定时器TIM1、两个16位的通用定时器TIM2、TIM3以及一个32位的通用定时器TIM4。其中,高级定时器相较于通用定时器,支持可编程死区时间的互补输出、支持使用重复计数器在确定周期后更新定时器、支持使用刹车信号将定时器复位或置其于确定状态,高级定时器更适用于电机控制等相关领域。
CH32L103具有一个32位的通用定时器,相较于16位定时器,其具有一个32位自动重装计数器。
关于CH32L103定时器的具体介绍,可见CH32L103应用手册。

2、定时器应用
(1)定时器更新中断
关于定时器更新中断的产生:
当配置向上计数模式时,定时器计数器从0向上计数到重装载值之后会产生更新事件,则会进入更新中断。计数器会重新从0开始向上计数。
当配置向下计数模式时,定时器计数器从重装载值向下计数到0之后会产生更新事件,则会进入更新中断。计数器会重新从重装载值向下计数。
定时器更新中断程序如下:
/********************************** (C) COPYRIGHT *******************************
* File Name          : main.c
* Author             : WCH
* Version            : V1.0.0
* Date               : 2023/12/26
* Description        : Main program body.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/

/*
*@Note
*USART Print debugging routine:
*USART1_Tx(PA9).
*This example demonstrates using USART1(PA9) as a print debug port output.
*
*/

#include "debug.h"

/* Global typedef */

/* Global define */

/* Global Variable */

//LED初始化
void LED_Init(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure={0};

    RCC_PB2PeriphClockCmd(RCC_PB2Periph_GPIOA, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;     //设置GPIO模式为推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_SetBits(GPIOA,GPIO_Pin_1);
}

//定时器更新中断初始化
void TIM1_INT_Init(u16 arr,u16 psc)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure={0};
    NVIC_InitTypeDef         NVIC_InitStructure={0};

    RCC_PB2PeriphClockCmd(RCC_PB2Periph_TIM1, ENABLE);        //使能TIM1时钟

    TIM_TimeBaseStructure.TIM_Period = arr;                    //重装载值
    TIM_TimeBaseStructure.TIM_Prescaler =psc;                  //预分频器值
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;    //时钟分频因子
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//TIM计数模式,向上计数模式
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);            //根据指定的参数初始化TIMx的时间基数单位

    //初始化TIM NVIC,设置中断优先级分组
    NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;         //TIM1更新中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //设置抢占优先级0
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;         //设置响应优先级3
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;            //使能通道1中断
    NVIC_Init(&NVIC_InitStructure);                            //初始化NVIC

    TIM_ITConfig(TIM1,TIM_IT_Update,ENABLE );                  //使能TIM1中断,允许更新中断

    TIM_Cmd(TIM1, ENABLE); //TIM1使能
}

void TIM1_UP_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void TIM1_UP_IRQHandler(void)
{
    static u8 i=0;

    if(TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) //检查TIM1中断是否发生。
    {
        TIM_ClearITPendingBit(TIM1,TIM_IT_Update);      //清除TIM1的中断挂起位。
        GPIO_WriteBit(GPIOA, GPIO_Pin_1, (i==0) ? (i=Bit_SET):(i=Bit_RESET));
    }
}
/*********************************************************************
* @fn      main
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   Main program.
*
* [url=home.php?mod=space&uid=266161]@return[/url]  none
*/
int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );
    printf("This is printf example\r\n");

    LED_Init();
    TIM1_INT_Init(1000-1,96-1);

    while(1)
    {
    }
}
程序中,主要对定时器自动重装值寄存器值、计数时钟预分频器值、时钟分频因子、计数模式、重复计数器值进行配置。
关于自动重装值寄存器,主要用于设置定时器计数的次数,对于TIM1、2、3,该寄存器是16位的,最大值可设置为2的16次方,即65536;对于TIM4,该寄存器是32位的,最大值可设置为2的32次方;
关于计数时钟预分频器,主要用于设置定时器预分频器的分频系数,计数器的时钟频率等于分频器的输入频率/(PSC+1)。以例程TIM1为例,系统主频96MHz,PB2总线时钟为96MHz,TIM1是挂载在PB2总线下面的,如下图,当预分频器的分频系数设置为95时,则计数器的时钟频率f为96MHz/(95+1)=1MHz,则计数一次的时间t为1/f=1us,因此计数器计数一次的时间为1us;
43125668dff250099c.png
关于时钟分频因子,主要用于死区相关应用中,此处用不到,后面用到会做具体介绍;
关于计数模式,可配置为向上计数模式、向下计数模式或中央对齐模式,中央对齐模式为计数器交替向上向下计数模式,有3种中央对齐模式;
关于重复计数器,主要用于高级定时器中,关于重复计数器的介绍,可参考下链接:
https://www.cnblogs.com/liaigu/p/17782198.html
以上就是对定时器相关配置的介绍,关于定时器更新中断,有前面介绍可知,程序中预分频器的值设置为95,因此计数一次的时间是1us。程序中,自动重装值寄存器的值为1000-1,即999,从0计数到999需要计数1000次,因此总共计数时间为1us*1000=1ms,因此1ms进一次更新中断,可以在定时器中断中翻转IO查看对应时间,如下图:
85281668dff45c4b2e.png

(2)定时器PWM输出
PWM输出模式是定时器的基本功能之一。PWM 输出模式最常见的是使用重装值确定PWM 频率,使用捕获比较寄存器确定占空比的方法。PWM输出常用的模式有两种,PWM模式1和PWM模式2,关于这两种模式的介绍如下:
PWM模式1:
当向上计数模式时,当 计数器的值<比较/捕获寄存器的值 时,对应的PWM通道输出为高电平,否则为低电平。对应波形如下,配置自动重装值寄存器的值为100,比较/捕获寄存器的值为40,因此,高电平占比40%,低电平占比60%;
38670668dff7309c84.png
当向下计数模式时,当 计数器的值>比较/捕获寄存器的值 时,对应的PWM通道输出为低电平,否则为高电平。对应波形如下,配置自动重装值寄存器的值为100,比较/捕获寄存器的值为40,因此,高电平占比40%,低电平占比60%;
63842668dff82ecf9e.png
关于向上计数与向下计数模式的区别在于:
向上计数先输出高电平
向下计数先输出低电平
PWM模式2:
当向上计数模式时,当 计数器的值<比较/捕获寄存器的值 时,对应的PWM通道输出为低电平,否则为高电平。对应波形如下,配置自动重装值寄存器的值为100,比较/捕获寄存器的值为40,因此,高电平占比60%,低电平占比40%;
46286668dff97b9c42.png
当向下计数模式时,当 计数器的值>比较/捕获寄存器的值 时,对应的PWM通道输出为高电平,否则为低电平。对应波形如下,配置自动重装值寄存器的值为100,比较/捕获寄存器的值为40,因此,高电平占比40%,低电平占比60%;
69540668dffaa7b5dd.png
关于向上计数与向下计数模式的区别在于:
向上计数先输出低电平
向下计数先输出高电平
关于PWM周期、频率的计算:
81654668dffe9a69ba.png
其中:
arr表示自动重装值寄存器的值;
psc表示预分频器寄存器的值;
PCLK表示定时器挂载总线的时钟,如下图。其中TIM1是挂载在PB2总线上的,TIM2、TIM3和TIM4是挂载在PB1总线上的。
45465668e0000d839a.png
以EVT PWM输出例程为例,设置arr值为100-1,psc的值为48000-1,PB2总线时钟为96MHz,则PWM输出的周期
T=100*48000/96MHz=50ms
则PWM输出的频率
f=1/T=20Hz
使用逻辑分析仪采样PWM输出,波形如下:
34993668e0024d4e9a.png
从波形可以看到,PWM输出波形周期为50ms,频率为20Hz,与计算结果基本一致。
关于PWM输出占空比,主要是比较/捕获寄存器值与arr的值,arr的值为100-1,即99,从0开始计数,则计数100次,比较/捕获寄存器值设置为50,即计数到50进行一次翻转,50/100,因此占空比为50%。

(3)定时器输入捕获模式和PWM输入模式
输入捕获模式可用于测量捕获脉冲或脉宽频率。PWM输入模式是输入捕获模式的一种特殊情况,PWM输入模式一般用来测量PWM输出的占空比和频率。输入捕获模式可以对TIM的4个通道进行配置,但PWM输入模式只能使用TIM的CH1和CH2信号。此外,输入捕获模式和PWM输入模式在初始化配置上也有所不同。EVT例程中,输入捕获例程实际上就是PWM输入例程。
关于EVT输入捕获例程,在对PWM输入模式通道1进行配置的时候,实际上也是对通道2进行了配置,点进TIM_PWMIConfig函数,如下图:
69562668e00592c49e.png
该函数中,对通道1和通道2同时进行配置,因此在初始化的时候配置一个即可,在库函数中会将另外一个通道配置完成。
下面例程为EVT输入捕获例程:
/********************************** (C) COPYRIGHT *******************************
* File Name          : main.c
* Author             : WCH
* Version            : V1.0.0
* Date               : 2023/12/26
* Description        : Main program body.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/

/*
*@Note
*Input capture routine:
*TIM1_CH1(PA8)
*This example demonstrates the TIM_CH1(PA8) pin floating input, which detects an
*edge transition to trigger a TIM1 capture interrupt,The rising edge triggers the
*TIM_IT_CC1 interrupt, and the falling edge triggers the TIM_IT_CC2 interrupt.
*
*/

#include "debug.h"

/*********************************************************************
* @fn      Input_Capture_Init
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   Initializes TIM1 input capture.
*
* @param   arr - the period value.
*          psc - the prescaler value.
*          ccp - the pulse value.
*
* [url=home.php?mod=space&uid=266161]@return[/url]  none
*/
void Input_Capture_Init(u16 arr, u16 psc)
{
    GPIO_InitTypeDef        GPIO_InitStructure = {0};
    TIM_ICInitTypeDef       TIM_ICInitStructure = {0};
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure = {0};
    NVIC_InitTypeDef        NVIC_InitStructure = {0};

    RCC_PB2PeriphClockCmd(RCC_PB2Periph_GPIOA | RCC_PB2Periph_TIM1, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_ResetBits(GPIOA, GPIO_Pin_8);

    TIM_TimeBaseInitStructure.TIM_Period = arr;
    TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0x00;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);

    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStructure.TIM_ICFilter = 0x00;
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;

    TIM_PWMIConfig(TIM1, &TIM_ICInitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    TIM_ITConfig(TIM1, TIM_IT_CC1 | TIM_IT_CC2, ENABLE);

    TIM_SelectInputTrigger(TIM1, TIM_TS_TI1FP1);
    TIM_SelectSlaveMode(TIM1, TIM_SlaveMode_Reset);
    TIM_SelectMasterSlaveMode(TIM1, TIM_MasterSlaveMode_Enable);
    TIM_Cmd(TIM1, ENABLE);
}

/*********************************************************************
* @fn      main
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   Main program.
*
* [url=home.php?mod=space&uid=266161]@return[/url]  none
*/
int main(void)
{
    SystemCoreClockUpdate();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );

    Input_Capture_Init(0xFFFF, 48000 - 1);

    while(1);
}

void TIM1_CC_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));

/*********************************************************************
* @fn      TIM1_CC_IRQHandler
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   This function handles TIM1  Capture Compare Interrupt exception.
*
* [url=home.php?mod=space&uid=266161]@return[/url]  none
*/
void TIM1_CC_IRQHandler(void)
{
    if( TIM_GetITStatus( TIM1, TIM_IT_CC1 ) != RESET )
    {
        printf( "CH1_Val:%d\r\n", TIM_GetCapture1( TIM1 ) );
        TIM_SetCounter( TIM1, 0 );
    }

    if( TIM_GetITStatus( TIM1, TIM_IT_CC2 ) != RESET )
    {
        printf( "CH2_Val:%d\r\n", TIM_GetCapture2( TIM1 ) );
    }

    TIM_ClearITPendingBit( TIM1, TIM_IT_CC1 | TIM_IT_CC2 );
}
若使用EVT例程对PWM输出波形进行采样,注意定时器的计数频率要在PWM输出频率的10倍以上,一般越大会越好一些。比如要采样的PWM输出频率为1KHz,周期为1ms,占空比为50%,那么定时器的计数频率可以设置在200KHz,arr值一般设置为最大,0xFFFF。psc的值则根据计数器频率进行计算,若计数器频率为200KHz,例程中PB2总线的时钟时96MHz,那么psc的值为480-1。根据上结果对例程进行修改,如下:
75230668e00b33a29e.png
修改之后对PWM输出波形进行采样,得到比较捕获寄存1的值是199,比较捕获寄存2的值是99,由手册可知,比较捕获寄存器1 的值就是PWM的周期,而比较捕获寄存器2的值就是其占空比。
57121668e00ed1c3c5.png
定时器的计数频率是200KHz,则计数一次的时间是5us。
根据比较捕获寄存器1 的值就是PWM的周期,比较捕获寄存1的值是199,那么周期大约为1000us,即1ms,与波形周期相符;
根据比较捕获寄存器2的值就是PWM的占空比,比较捕获寄存2的值是99,那么周期大约为500us,即0.5ms,与波形周期相比,占空比就是50%,与波形占空比相符;
下面例程为定时器输入捕获例程,如下:
/********************************** (C) COPYRIGHT *******************************
* File Name          : main.c
* Author             : WCH
* Version            : V1.0.0
* Date               : 2023/12/26
* Description        : Main program body.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/

/*
*@Note
*USART Print debugging routine:
*USART1_Tx(PA9).
*This example demonstrates using USART1(PA9) as a print debug port output.
*
*/

#include "debug.h"

/* Global typedef */

/* Global define */

/* Global Variable */
volatile u8 flag=0;
/*********************************************************************
* @fn      Input_Capture_Init
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   Initializes TIM1 input capture.
*
* @param   arr - the period value.
*          psc - the prescaler value.
*          ccp - the pulse value.
*
* [url=home.php?mod=space&uid=266161]@return[/url]  none
*/
void Input_Capture_Init(u16 arr, u16 psc)
{

    GPIO_InitTypeDef        GPIO_InitStructure = {0};
    TIM_ICInitTypeDef       TIM_ICInitStructure = {0};
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure = {0};
    NVIC_InitTypeDef        NVIC_InitStructure = {0};

    RCC_PB2PeriphClockCmd(RCC_PB2Periph_GPIOA | RCC_PB2Periph_TIM1, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_ResetBits(GPIOA, GPIO_Pin_8);

    TIM_TimeBaseInitStructure.TIM_Period = arr;
    TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0x00;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);

    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStructure.TIM_ICFilter = 0x00;
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_ICInit(TIM1, &TIM_ICInitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    TIM_ClearITPendingBit( TIM1, TIM_IT_CC1 );

    TIM_ITConfig(TIM1, TIM_IT_CC1 , ENABLE);

    TIM_Cmd(TIM1, ENABLE);
}


/*********************************************************************
* @fn      main
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   Main program.
*
* [url=home.php?mod=space&uid=266161]@return[/url]  none
*/
int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );
    printf("This is printf example\r\n");
    Input_Capture_Init(0xFFFF, 960 - 1);

    while(1)
    {
        if(flag==1)
        {
            flag=0;
            printf( "CH1_Val:%d\r\n", TIM_GetCapture1( TIM1 ) );
        }

    }
}


void TIM1_CC_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));

/*********************************************************************
* @fn      TIM1_CC_IRQHandler
*
* @brief   This function handles TIM1  Capture Compare Interrupt exception.
*
* @return  none
*/
void TIM1_CC_IRQHandler(void)
{
    if( TIM_GetITStatus( TIM1, TIM_IT_CC1 ) != RESET )
    {
        TIM_ClearITPendingBit( TIM1, TIM_IT_CC1 );
        TIM_SetCounter( TIM1, 0 );
        flag=1;
    }
}
该例程配置了单通道-通道1,且设置上升沿捕获,可用于捕获脉冲,在中断中将计数器的值清0并打印比较/捕获寄存器的值,注意打印通过相关标志位放在while循环中,不要放在中断中。通过该值可计算脉冲周期和频率。程序中,PB2总线时钟为96MHz,arr的值设置为0xFFFF,psc的值设置为960-1,则计数器计数一次的时间为0.01ms。程序中,打印值如下:
76283668e01740cc38.png
根据比较/捕获寄存器的值100,则计算出周期T=100*0.01ms=1ms,则频率f=1/T=1KHz。与实际相符。
49099668e01855db93.png

(4)定时器单脉冲模式
单脉冲模式可以用于让MCU响应一个特定的事件,使之在一个延迟之后产生一个脉冲,延迟和脉冲的宽度可编程。CH32L103 EVT提供了单脉冲模式例程,代码如下:
/********************************** (C) COPYRIGHT *******************************
* File Name          : main.c
* Author             : WCH
* Version            : V1.0.0
* Date               : 2023/07/08
* Description        : Main program body.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/

/*
*@Note
*Single pulse output routine:
*TIM2_CH1(PA0),TIM2_CH2(PA1)
*This routine demonstrates that in single-pulse mode, when a rising edge is detected
*on the TIM2_CH2(PA1) pin, the TIM2_CH1(PA0) outputs positive pulse.
*
*/
#include "debug.h"

/*********************************************************************
* @fn      One_Pulse_Init
*
* @brief   Initializes TIM1 one pulse.
*
* @param   arr - the period value.
*          psc - the prescaler value.
*          ccp - the pulse value.
*
* @return  none
*/
void One_Pulse_Init(u16 arr, u16 psc, u16 ccp)
{
        GPIO_InitTypeDef GPIO_InitStructure={0};
        TIM_OCInitTypeDef TIM_OCInitStructure={0};
        TIM_ICInitTypeDef TIM_ICInitStructure={0};
        TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure={0};

        RCC_PB2PeriphClockCmd( RCC_PB2Periph_GPIOA, ENABLE );
        RCC_PB1PeriphClockCmd( RCC_PB1Periph_TIM2, ENABLE );

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init( GPIOA, &GPIO_InitStructure );

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
        GPIO_Init( GPIOA, &GPIO_InitStructure);

        TIM_TimeBaseInitStructure.TIM_Period = arr;
        TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
        TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
        TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_TimeBaseInit( TIM2, &TIM_TimeBaseInitStructure);

        TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
        TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
        TIM_OCInitStructure.TIM_Pulse = ccp;
        TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
        TIM_OC1Init( TIM2, &TIM_OCInitStructure );

        TIM_ICStructInit( &TIM_ICInitStructure );
        TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
        TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
        TIM_ICInitStructure.TIM_ICFilter = 0x00;
        TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
        TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
        TIM_ICInit( TIM2, &TIM_ICInitStructure );

        TIM_SelectOnePulseMode( TIM2,TIM_OPMode_Single );
        TIM_SelectInputTrigger( TIM2, TIM_TS_TI2FP2 );
        TIM_SelectSlaveMode( TIM2, TIM_SlaveMode_Trigger );
}

/*********************************************************************
* @fn      main
*
* @brief   Main program.
*
* @return  none
*/
int main(void)
{
        SystemCoreClockUpdate();
        USART_Printf_Init(115200);
        printf("SystemClk:%d\r\n",SystemCoreClock);
        printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );

        One_Pulse_Init( 200, 48000-1, 100 );

        while(1);
}
程序中,配置TIM2 通道1产生脉冲,通道2用于输入捕获,捕获到上升沿,则通道1产生脉冲事件。
需要注意,脉冲的产生需要一定的延迟Tdelay,比如程序中配置检测到上升沿后经过Tdelay产生一个脉冲,这个Tdelay时间由比较捕获寄存器的值确定,例程中比较捕获寄存器的值是100,而定时器2计数一次的时间为1/(96MHz/(48000-1+1))=0.5ms,则延迟时间Tdelay=100*0.5=50ms。脉冲的脉宽=(200-100)*0.5=50ms。
关于此处计数一次时间的计算,TIM2是挂载在PB1总线上的,其中PB1总线时钟为HCLK/2=48MHz,具体可看系统主频配置那部分,当用于TIM2时钟时,由于是2分频,因此还要乘以2,如下图,则就是96MHz.
70279668e01da1d3dc.png
程序中,配置计数时钟预分频器的值是48000-1,则计数器的时钟频率等于96MHz/(48000-1+1)=2KHz,则计数一次的时间等于1/2KHz=0.5ms
71112668e01ecbb9d4.png
该例程测试波形如下:
17182668e0203db7c4.png
如图,通道2检测到上升沿50ms后,通道1产生1个脉冲,脉宽为50ms。

(5)定时器比较输出模式
比较输出模式的原理是在计数器的值与比较捕获寄存器的值一致时,输出特定的变化或波形。CH32L103 EVT提供了比较输出模式例程,代码如下:
/********************************** (C) COPYRIGHT *******************************
* File Name          : main.c
* Author             : WCH
* Version            : V1.0.0
* Date               : 2023/07/08
* Description        : Main program body.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/

/*
*@Note
*Output comparison mode routine:
*TIM1_CH1(PA8)
*This example demonstrates the output waveform of the TIM_CH1(PA8) pin
*in 4 output comparison modes.Output compare modes include OutCompare_Timing\
*OutCompare_Active\OutCompare_Inactive\OutCompare_Toggle.
*
*/

#include "debug.h"

/* Output Compare Mode Definition */
#define OutCompare_Timing   0
#define OutCompare_Active   1
#define OutCompare_Inactive 2
#define OutCompare_Toggle   3

/* Output Compare Mode Selection */
//#define OutCompare_MODE OutCompare_Timing
//#define OutCompare_MODE OutCompare_Active
//#define OutCompare_MODE OutCompare_Inactive
#define OutCompare_MODE OutCompare_Toggle

/*********************************************************************
* @fn      TIM1_OutCompare_Init
*
* @brief   Initializes TIM1 output compare.
*
* @param   arr - the period value.
*          psc - the prescaler value.
*          ccp - the pulse value.
*
* @return  none
*/
void TIM1_OutCompare_Init(u16 arr, u16 psc, u16 ccp)
{
        GPIO_InitTypeDef GPIO_InitStructure={0};
        TIM_OCInitTypeDef TIM_OCInitStructure={0};
        TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure={0};

        RCC_PB2PeriphClockCmd( RCC_PB2Periph_GPIOA | RCC_PB2Periph_TIM1, ENABLE );

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init( GPIOA, &GPIO_InitStructure );

        TIM_TimeBaseInitStructure.TIM_Period = arr;
        TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
        TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
        TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_TimeBaseInit( TIM1, &TIM_TimeBaseInitStructure);

#if (OutCompare_MODE == OutCompare_Timing)
        TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing;

#elif (OutCompare_MODE == OutCompare_Active)
        TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Active;

#elif (OutCompare_MODE == OutCompare_Inactive)
        TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Inactive;

#elif (OutCompare_MODE == OutCompare_Toggle)
        TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;

#endif

        TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
        TIM_OCInitStructure.TIM_Pulse = ccp;
        TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
        TIM_OC1Init( TIM1, &TIM_OCInitStructure );

        TIM_CtrlPWMOutputs(TIM1, ENABLE );
        TIM_OC1PreloadConfig( TIM1, TIM_OCPreload_Disable );
        TIM_ARRPreloadConfig( TIM1, ENABLE );
        TIM_Cmd( TIM1, ENABLE );
}

/*********************************************************************
* @fn      main
*
* @brief   Main program.
*
* @return  none
*/
int main(void)
{
        SystemCoreClockUpdate();
        USART_Printf_Init(115200);
        printf("SystemClk:%d\r\n",SystemCoreClock);
        printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );

        TIM1_OutCompare_Init( 100, 48000-1, 50 );

        while(1);
}
例程中,提供了比较输出模式下的4种配置:
第一种:冻结,即比较捕获寄存器的值与核心计数器间的比较值对OC1REF不起作用;
第二种: 强制设为有效电平,即当核心计数器与比较捕获寄存器的值相同时,强制OC1REF 为高,波形如下:
64316668e02596b787.png
第三种: 强制设为无效电平,即当核心计数器与比较捕获寄存器的值相同时,强制OC1REF 为低,波形如下:
81445668e02709c692.png
第四种,翻转,即当核心计数器与比较捕获寄存器的值相同时,翻转OC1REF的电平,波形如下:
58195668e02834423a.png
程序中计数器计数一次的时间是0.5ms,比较寄存器的值为100,因此50ms翻转一次。
关于翻转模式,可以理解为在一个周期内产生多个翻转。正常PWM输出可以理解为一个周期产生一次翻转。翻转模式下,一个周期内可以产生多次翻转。
关于翻转模式,就是将定时器输出模式设置为TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle。普通的PWM输出高低电平是根据CNT计数器中的值和CCR进行比较,若CNT<CCR则输出一种状态电平,当CNT介于CCR和ARR之间时,输出另一种状态电平。TIM_OCMode_Toggle模式就是当CNT=CCR时,输出引脚的电平会进行翻转,可以通过TIM_OCMode_Toggle模式进入中断后重新设置CCR寄存器中的值,即可以在一个周期内产生多次翻转。
TIM_OCMode_Toggle模式下,当CNT计数值=CCR时,会进入比较中断(在这里中断源是TIM_IT_CC1/2/3/4),也就是说当CNT计数值从0开始一直计数到CCR时,会触发TIM_IT_CC中断源,并进入中断,进入中断后修改CCR寄存器中的值。先将此时的CCR寄存器中的值放入到一个变量中(TIM_GetCapture1(TIM1)),再在这个变量的基础上加上相同的数,同时赋给CCR寄存器中TIM_SetCompare1(TIM1),那么在下一次CNT=CCR时又会进行翻转电平。具体程序配置如下图:
63731668e02adced13.png
举一个例子:若一开始定时器ARR为65535,CCR为9999,那么当CNT从0开始一直加到9999,假设电平为低电平,当加到9999时,会通过TIM_IT_CC中断源进入TIM中断(这个中断源只有当CNT计数值到CCR时才会触发)在中断服务函数中,将当前CCR的值放入capture这个变量中,再在capture值的基础上再加上相同的数(9999),同时作为新的CCR赋给定时器,这里叫CCR_NEW,CCR_NEW=9999+9999=19998。在CNT从9999计数19998时,此时CNT=CCR_NEM,又会进行翻转变为高电平,此时又会进入中断,在在CCR_NEW的基础上加上9999,以此类推,因此可以实现在一个周期内有多个脉冲出现,且占空比为50%。

(6)定时器互补输出和死区
比较捕获通道一般有两个输出引脚(比较捕获通道 4 只有一个输出引脚),如下图,能输出两个互补的信号(OCx 和 OCxN),OCx 和 OCxN 可以通过 CCxP 和 CCxNP 位独立地设置极性,通过 CCxE 和 CCxNE独立地设置输出使能,通过 MOE、OIS、OISN、OSSI、OSSR 位进行死区和其他的控制。
51435668e02df1ef43.png
同时使能OCx和OCxN输出将插入死区,每个通道都有一个 10 位的死区发生器。如果存在刹车电路则还要设置MOE位。OCx和OCxN由OCxREF关联产生,如果OCx和OCxN都是高有效,那么OCx与OCxREF相同,只是 OCx 的上升沿相当于OCxREF有一个延迟,OCxN与 OCxREF相反,它的上升沿相对参考信号的下降沿会有一个延迟,如果延迟大于有效输出宽度,则不会产生相应的脉冲。
下图图展示了OCx和OCxN与OCxREF的关系,并展示出死区。
23687668e02ff62b47.png
EVT例程演示了TIM1死区的3种互补输出模式:
第一种:互补输出带死区插入,波形如下图:
44706668e0310a624f.png
图中,延迟时间delay大约是10.5us,关于delay时间的计算,主要由刹车和死区寄存器(TIM1_BDTR)的位[7:0]决定的,如下图:
92110668e0322147dc.png
例程中,对死区时间设置为0xFF,即该8位均为1,则DT=(32+31)*16*TDTS,其中Tdts由控制寄存器 1(TIMx_CTLR1)位[9:8]决定,如下图:
620668e033739c80.png
例程中,该两位配置为00,即Tdts=Tck_int,Tck_int=1/96MHz,则DT=63*16/96MHz=10.5us。
第二种:死区波形延迟大于负脉冲,波形如下图:
32763668e034aea8a4.png
图中,互补通道输出一直为高电平,因为delay时间为10.5us,而程序中定时器重装载值为100,预分频器值为48-1,比较寄存器值为10,而定时器计数一次的时间为0.5us,因此有效输出宽度为5us,小于delay时间,因此不会产生相应的脉冲。
程序中通道1 PWM输出模式配置为PWM模式2,且向上计数,因此计数器值大于比较寄存器值时为有效电平,此处极性配置为低,因此有效电平为低电平,互补通道同样为低有效。
第三种:死区波形延迟大于正脉冲,波形如下图:
85300668e035b51744.png
图中,通道1输出一直为高电平,因为delay时间为10.5us,而程序中定时器重装载值为100,预分频器值为48-1,比较寄存器值为90,而定时器计数一次的时间为0.5us,因此有效输出宽度为5us,小于delay时间,因此不会产生相应的脉冲。
程序中PWM输出模式配置为PWM模式2,且向上计数,因此计数器值大于比较寄存器值时为有效电平,此处极性配置为低,因此有效电平为低电平,有效电平宽度为5us。

(7)定时器刹车信号
当产生刹车信号时,输出使能信号和无效电平都会根据MOE、OIS、OISN、OSSI和OSSR等位进行修改。但OCx和 OCxN不会在任何时间都处在有效电平。刹车事件源可以来自于刹车输入引脚,也可以是一个时钟失败事件,而时钟失败事件由CSS(时钟安全系统)产生。下为刹车信号相关代码:
/********************************** (C) COPYRIGHT *******************************
* File Name          : main.c
* Author             : WCH
* Version            : V1.0.0
* Date               : 2023/07/08
* Description        : Main program body.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/

/*
*@Note
*Complementary output and deadband insertion mode routines:
*TIM1_CH1(PA8),TIM1_CH1N(PB13)
*This example demonstrates three complementary output modes with dead zone of TIM1: complementary
*output with dead zone insertion, dead zone waveform delay Greater than the negative pulse, the dead
*zone waveform delay is greater than the positive pulse.
*
*/

#include "debug.h"

/*********************************************************************
* @fn      TIM1_Dead_Time_Init
*
* @brief   Initializes TIM1 complementary output and dead time.
*
* @param   arr - the period value.
*          psc - the prescaler value.
*          ccp - the pulse value.
*
* @return  none
*/
void TIM1_Dead_Time_Init(u16 arr, u16 psc, u16 ccp)
{
    GPIO_InitTypeDef        GPIO_InitStructure = {0};
    TIM_OCInitTypeDef       TIM_OCInitStructure = {0};
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure = {0};
    TIM_BDTRInitTypeDef     TIM_BDTRInitStructure = {0};

    RCC_PB2PeriphClockCmd(RCC_PB2Periph_GPIOA | RCC_PB2Periph_GPIOB | RCC_PB2Periph_TIM1, ENABLE);

    /* TIM1_CH1 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* TIM1_CH1N */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    /* TIM1_BK1N */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    GPIO_ResetBits(GPIOB, GPIO_Pin_12);

    TIM_TimeBaseInitStructure.TIM_Period = arr;
    TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);

    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
    TIM_OCInitStructure.TIM_Pulse = ccp;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
    TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
    TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
    TIM_OC1Init(TIM1, &TIM_OCInitStructure);

    TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
    TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
    TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1;
    TIM_BDTRInitStructure.TIM_DeadTime = 0xFF;
    TIM_BDTRInitStructure.TIM_Break = TIM_Break_Enable;
    TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;
    TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
    TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);

    TIM_CtrlPWMOutputs(TIM1, ENABLE);
    TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Disable);
    TIM_ARRPreloadConfig(TIM1, ENABLE);
    TIM_Cmd(TIM1, ENABLE);
}

/*********************************************************************
* @fn      main
*
* @brief   Main program.
*
* @return  none
*/
int main(void)
{
    SystemCoreClockUpdate();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );

    /* Complementary output with dead-time insertion */
    TIM1_Dead_Time_Init(100, 48 - 1, 50);
    /* Dead-time waveforms with delay greater than the negative pulse */
//      TIM1_Dead_Time_Init( 100, 48-1, 10 );
    /* Dead-time waveforms with delay greater than the positive pulse. */
//        TIM1_Dead_Time_Init( 100, 48-1, 90 );

    while(1);
}
程序中,对于刹车信号的配置主要是对刹车和死区寄存器(TIM1_BDTR)进行配置。程序中,使能配置开启刹车输入,且刹车输入高电平有效,即当刹车输入引脚PB12引脚检测到高电平信号时,会进行刹车,停止PWM输出,如下图:
82218668e039a329b7.png

(8)关于利用PWM实现播放音乐
利用PWM实现播放音乐,可参考下帖:
https://bbs.21ic.com/icview-3134310-1-1.html
https://bbs.21ic.com/icview-3134344-1-1.html

附件为相关例程,可以参考一下。

     

CH32L103 TIM例程.zip

4.09 MB

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

13

主题

17

帖子

0

粉丝