本帖最后由 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; 关于时钟分频因子,主要用于死区相关应用中,此处用不到,后面用到会做具体介绍; 关于计数模式,可配置为向上计数模式、向下计数模式或中央对齐模式,中央对齐模式为计数器交替向上向下计数模式,有3种中央对齐模式; 关于重复计数器,主要用于高级定时器中,关于重复计数器的介绍,可参考下链接: https://www.cnblogs.com/liaigu/p/17782198.html 以上就是对定时器相关配置的介绍,关于定时器更新中断,有前面介绍可知,程序中预分频器的值设置为95,因此计数一次的时间是1us。程序中,自动重装值寄存器的值为1000-1,即999,从0计数到999需要计数1000次,因此总共计数时间为1us*1000=1ms,因此1ms进一次更新中断,可以在定时器中断中翻转IO查看对应时间,如下图:
(2)定时器PWM输出 PWM输出模式是定时器的基本功能之一。PWM 输出模式最常见的是使用重装值确定PWM 频率,使用捕获比较寄存器确定占空比的方法。PWM输出常用的模式有两种,PWM模式1和PWM模式2,关于这两种模式的介绍如下: PWM模式1: 当向上计数模式时,当 计数器的值<比较/捕获寄存器的值 时,对应的PWM通道输出为高电平,否则为低电平。对应波形如下,配置自动重装值寄存器的值为100,比较/捕获寄存器的值为40,因此,高电平占比40%,低电平占比60%; 当向下计数模式时,当 计数器的值>比较/捕获寄存器的值 时,对应的PWM通道输出为低电平,否则为高电平。对应波形如下,配置自动重装值寄存器的值为100,比较/捕获寄存器的值为40,因此,高电平占比40%,低电平占比60%; 关于向上计数与向下计数模式的区别在于: 向上计数先输出高电平 向下计数先输出低电平 PWM模式2: 当向上计数模式时,当 计数器的值<比较/捕获寄存器的值 时,对应的PWM通道输出为低电平,否则为高电平。对应波形如下,配置自动重装值寄存器的值为100,比较/捕获寄存器的值为40,因此,高电平占比60%,低电平占比40%; 当向下计数模式时,当 计数器的值>比较/捕获寄存器的值 时,对应的PWM通道输出为高电平,否则为低电平。对应波形如下,配置自动重装值寄存器的值为100,比较/捕获寄存器的值为40,因此,高电平占比40%,低电平占比60%; 关于向上计数与向下计数模式的区别在于: 向上计数先输出低电平 向下计数先输出高电平 关于PWM周期、频率的计算: 其中: arr表示自动重装值寄存器的值; psc表示预分频器寄存器的值; PCLK表示定时器挂载总线的时钟,如下图。其中TIM1是挂载在PB2总线上的,TIM2、TIM3和TIM4是挂载在PB1总线上的。 以EVT PWM输出例程为例,设置arr值为100-1,psc的值为48000-1,PB2总线时钟为96MHz,则PWM输出的周期 T=100*48000/96MHz=50ms 则PWM输出的频率 f=1/T=20Hz 使用逻辑分析仪采样PWM输出,波形如下: 从波形可以看到,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函数,如下图: 该函数中,对通道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。根据上结果对例程进行修改,如下: 修改之后对PWM输出波形进行采样,得到比较捕获寄存1的值是199,比较捕获寄存2的值是99,由手册可知,比较捕获寄存器1 的值就是PWM的周期,而比较捕获寄存器2的值就是其占空比。 定时器的计数频率是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。程序中,打印值如下: 根据比较/捕获寄存器的值100,则计算出周期T=100*0.01ms=1ms,则频率f=1/T=1KHz。与实际相符。
(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. 程序中,配置计数时钟预分频器的值是48000-1,则计数器的时钟频率等于96MHz/(48000-1+1)=2KHz,则计数一次的时间等于1/2KHz=0.5ms 该例程测试波形如下: 如图,通道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 为高,波形如下: 第三种: 强制设为无效电平,即当核心计数器与比较捕获寄存器的值相同时,强制OC1REF 为低,波形如下: 第四种,翻转,即当核心计数器与比较捕获寄存器的值相同时,翻转OC1REF的电平,波形如下: 程序中计数器计数一次的时间是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时又会进行翻转电平。具体程序配置如下图: 举一个例子:若一开始定时器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 位进行死区和其他的控制。 同时使能OCx和OCxN输出将插入死区,每个通道都有一个 10 位的死区发生器。如果存在刹车电路则还要设置MOE位。OCx和OCxN由OCxREF关联产生,如果OCx和OCxN都是高有效,那么OCx与OCxREF相同,只是 OCx 的上升沿相当于OCxREF有一个延迟,OCxN与 OCxREF相反,它的上升沿相对参考信号的下降沿会有一个延迟,如果延迟大于有效输出宽度,则不会产生相应的脉冲。
下图图展示了OCx和OCxN与OCxREF的关系,并展示出死区。 EVT例程演示了TIM1死区的3种互补输出模式: 第一种:互补输出带死区插入,波形如下图: 图中,延迟时间delay大约是10.5us,关于delay时间的计算,主要由刹车和死区寄存器(TIM1_BDTR)的位[7:0]决定的,如下图: 例程中,对死区时间设置为0xFF,即该8位均为1,则DT=(32+31)*16*TDTS,其中Tdts由控制寄存器 1(TIMx_CTLR1)位[9:8]决定,如下图: 例程中,该两位配置为00,即Tdts=Tck_int,Tck_int=1/96MHz,则DT=63*16/96MHz=10.5us。 第二种:死区波形延迟大于负脉冲,波形如下图: 图中,互补通道输出一直为高电平,因为delay时间为10.5us,而程序中定时器重装载值为100,预分频器值为48-1,比较寄存器值为10,而定时器计数一次的时间为0.5us,因此有效输出宽度为5us,小于delay时间,因此不会产生相应的脉冲。 程序中通道1 PWM输出模式配置为PWM模式2,且向上计数,因此计数器值大于比较寄存器值时为有效电平,此处极性配置为低,因此有效电平为低电平,互补通道同样为低有效。 第三种:死区波形延迟大于正脉冲,波形如下图: 图中,通道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输出,如下图:
(8)关于利用PWM实现播放音乐 利用PWM实现播放音乐,可参考下帖: https://bbs.21ic.com/icview-3134310-1-1.html https://bbs.21ic.com/icview-3134344-1-1.html
附件为相关例程,可以参考一下。
|