stm32高级定时器实现pwm互补输出

简介

stm32设备一般都有很多类型的定时器,常见的有systick timer、基本定时器、通用定时器、高级定时器、看门狗定时器、RTC等等,本文简单介绍高级定时器是如何实现pwm互补输出。

详细

我这里使用的device是stm32f103rc,他有两个高级定时器TIM1、TIM8;下面选择TIM1来实现上述功能。

高级定时器的使用主要涉及以下几个结构体:

  • timebase,用于配置计数器
typedef struct
{
  	uint16_t TIM_Prescaler;     //定时器时钟的分频值 ,取值为0x0000到0xffff   
  
  	uint16_t TIM_CounterMode;   //指定计数器的工作模式   
  	
  	uint16_t TIM_Period;        //计数周期,就是一个定时周期计多少个数,取值为0x0000到0xffff

  	uint16_t TIM_ClockDivision;   //时钟分频因子,这个参数表示定时器的时钟和数字滤波器的时钟的频率比值,用来产生数字滤波器的时钟,简单来说就是internal clk分频之后得到数字滤波器的时钟

  uint8_t TIM_RepetitionCounter; //重复计数器,只用于TIM1和TIM8, 计数器每溢出一次,重复计数器就减1;这个参数的值表示边沿对齐模式pwm信号周期的个数或者中心对齐模式pwm信号半周期的个数,取值为0x00到 0xFF。
                                    
} TIM_TimeBaseInitTypeDef

关于重复计数器,下面会详细说明。

  • TIM_OCInitTypeDef
uint16_t TIM_OCMode;        /*!< Specifies the TIM mode.
                                   This parameter can be a value of @ref TIM_Output_Compare_and_PWM_modes */

  uint16_t TIM_OutputState;   /*!< Specifies the TIM Output Compare state.
                                   This parameter can be a value of @ref TIM_Output_Compare_state */

  uint16_t TIM_OutputNState;  /*!< Specifies the TIM complementary Output Compare state.
                                   This parameter can be a value of @ref TIM_Output_Compare_N_state
                                   @note This parameter is valid only for TIM1 and TIM8. */

  uint16_t TIM_Pulse;         /*!< Specifies the pulse value to be loaded into the Capture Compare Register. 
                                   This parameter can be a number between 0x0000 and 0xFFFF */

  uint16_t TIM_OCPolarity;    /*!< Specifies the output polarity.
                                   This parameter can be a value of @ref TIM_Output_Compare_Polarity */

  uint16_t TIM_OCNPolarity;   /*!< Specifies the complementary output polarity.
                                   This parameter can be a value of @ref TIM_Output_Compare_N_Polarity
                                   @note This parameter is valid only for TIM1 and TIM8. */

  uint16_t TIM_OCIdleState;   /*!< Specifies the TIM Output Compare pin state during Idle state.
                                   This parameter can be a value of @ref TIM_Output_Compare_Idle_State
                                   @note This parameter is valid only for TIM1 and TIM8. */

  uint16_t TIM_OCNIdleState;  /*!< Specifies the TIM Output Compare pin state during Idle state.
                                   This parameter can be a value of @ref TIM_Output_Compare_N_Idle_State
                                   @note This parameter is valid only for TIM1 and TIM8. */

这个结构体用来配置输出比较通道的功能。

  • TIM_BDTRInitTypeDef
typedef struct
{

  uint16_t TIM_OSSRState;        /*!< Specifies the Off-State selection used in Run mode.
                                      This parameter can be a value of @ref OSSR_Off_State_Selection_for_Run_mode_state */

  uint16_t TIM_OSSIState;        /*!< Specifies the Off-State used in Idle state.
                                      This parameter can be a value of @ref OSSI_Off_State_Selection_for_Idle_mode_state */

  uint16_t TIM_LOCKLevel;        /*!< Specifies the LOCK level parameters.
                                      This parameter can be a value of @ref Lock_level */ 

  uint16_t TIM_DeadTime;         /*!< Specifies the delay time between the switching-off and the
                                      switching-on of the outputs.
                                      This parameter can be a number between 0x00 and 0xFF  */

  uint16_t TIM_Break;            /*!< Specifies whether the TIM Break input is enabled or not. 
                                      This parameter can be a value of @ref Break_Input_enable_disable */

  uint16_t TIM_BreakPolarity;    /*!< Specifies the TIM Break Input pin polarity.
                                      This parameter can be a value of @ref Break_Polarity */

  uint16_t TIM_AutomaticOutput;  /*!< Specifies whether the TIM Automatic Output feature is enabled or not. 
                                      This parameter can be a value of @ref TIM_AOE_Bit_Set_Reset */
} TIM_BDTRInitTypeDef;

这个结构体是用来配置刹车和死区功能。

这里讲一下死区的配置,死区是什么意思?打个比方,有两个器件,一个器件工作的时候另外一个器件不能工作,否则会出现问题;所以一个器件关断之后需要延迟一段时间再去打开另一个器件。这个延迟时间就是死区时间。

高级定时器TIM1和TIM8都支持在互补PWM信号输出时插入死区(如下图所示),其他的通用、基本定时器没有这个功能。
1
这个delay就是死区时间,如何配置,看下图:
2

DTS是指数字滤波器,tDTS是指数字滤波器的时钟;上面timebase中的TIM_ClockDivision与这个tDTS有关。
定时器的时钟假设是72MHz,经过TIM_ClockDivision(2)分频之后得到数字滤波器的时钟为36MHz,那么tDTS就是1/36MHz ≈ 27.78 ns。然后根据DTG bit的设置来计算出死区时间,这里不再赘述。

了解了上面三个主要结构体之后我们就可以去做实验了。

实验

硬件环境:目前没有开发板,使用MDK ARM软件仿真。
选取的芯片是stm32f103rc。

这里选择PA8、PB13、PB12这三个引脚,用于产生互补pwm信号和刹车输入。
示例代码如下:

第一步:初始化外设时钟和引脚功能

static void ADVANCE_TIM_GPIO_Config(void) 
{
    GPIO_InitTypeDef GPIO_InitStructure;

    // 输出比较通道 GPIO 初始化
    RCC_APB2PeriphClockCmd(ADVANCE_TIM_CH1_GPIO_CLK, ENABLE);
    GPIO_InitStructure.GPIO_Pin =  ADVANCE_TIM_CH1_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(ADVANCE_TIM_CH1_PORT, &GPIO_InitStructure);

    // 输出比较通道互补通道 GPIO 初始化
    RCC_APB2PeriphClockCmd(ADVANCE_TIM_CH1N_GPIO_CLK, ENABLE);
    GPIO_InitStructure.GPIO_Pin =  ADVANCE_TIM_CH1N_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(ADVANCE_TIM_CH1N_PORT, &GPIO_InitStructure);

    // 输出比较通道刹车通道 GPIO 初始化
    RCC_APB2PeriphClockCmd(ADVANCE_TIM_BKIN_GPIO_CLK, ENABLE);
    GPIO_InitStructure.GPIO_Pin =  ADVANCE_TIM_BKIN_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(ADVANCE_TIM_BKIN_PORT, &GPIO_InitStructure);
	
}

第二步:初始化timebase

/*--------------------时基结构体初始化-------------------------*/
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
	TIM_TimeBaseStructure.TIM_Period=ADVANCE_TIM_PERIOD;	
	// 驱动CNT计数器的时钟 = Fck_int/(psc+1)
	TIM_TimeBaseStructure.TIM_Prescaler= ADVANCE_TIM_PSC;	
	// 时钟分频因子 ,配置死区时间时需要用到
	TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;		
	// 计数器计数模式,设置为向上计数
	TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;		
	// 重复计数器的值,没用到不用管
	TIM_TimeBaseStructure.TIM_RepetitionCounter=0;	
	// 初始化定时器
	TIM_TimeBaseInit(ADVANCE_TIM, &TIM_TimeBaseStructure);

第三步:初始化OC(输出比较通道)

TIM_OCInitTypeDef  TIM_OCInitStructure;
	
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;   // 配置为PWM模式1,即cnt < cc1则输出有效电平;cnt>=cc1则输出无效电平
	
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;// 输出使能
	
	TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; // 互补输出使能
	
	TIM_OCInitStructure.TIM_Pulse = ADVANCE_TIM_PULSE;  // 设置占空比大小
	
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;// 输出通道电平极性配置
	
	TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;// 互补输出通道电平极性配置
	
	TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;// 输出通道空闲电平极性配置
	
	TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Set;// 互补输出通道空闲电平极性配置
	TIM_OC1Init(ADVANCE_TIM, &TIM_OCInitStructure);
	TIM_OC1PreloadConfig(ADVANCE_TIM, TIM_OCPreload_Enable);

第四步:初始化死区和刹车功能

/*-------------------刹车和死区结构体初始化-------------------*/
	// 有关刹车和死区结构体的成员具体可参考BDTR寄存器的描述
	TIM_BDTRInitTypeDef TIM_BDTRInitStructure;
    TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
    TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
    TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1;
	
    // 输出比较信号死区时间配置,具体如何计算可参考 BDTR:UTG[7:0]的描述
	// 这里配置的死区时间为152ns
    TIM_BDTRInitStructure.TIM_DeadTime = 11;
    TIM_BDTRInitStructure.TIM_Break = TIM_Break_Enable;
	
    // 当BKIN引脚检测到高电平的时候,输出比较信号被禁止,就好像是刹车一样
    TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;
    
    //MOE can be set by software or automatically at the next update event 
    TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
    TIM_BDTRConfig(ADVANCE_TIM, &TIM_BDTRInitStructure);

第五步:使能定时器的中断

//使能定时器的中断
TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);

第六步:使能NVIC IRQ通道

NVIC_InitTypeStruct.NVIC_IRQChannel = TIM1_UP_IRQn;//中断通道 
NVIC_InitTypeStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitTypeStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitTypeStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVIC_InitTypeStruct);

第七步:使能定时器

// 使能计数器
TIM_Cmd(ADVANCE_TIM, ENABLE);	

第八步:使能主输出

// 主输出使能,当使用的是通用定时器时,这句不需要
TIM_CtrlPWMOutputs(ADVANCE_TIM, ENABLE);

程序运行效果如下:
1234

想要改变pwm的占空比和周期,可以修改头文件中的宏定义,如下:

// PWM 信号的频率 F = TIM_CLK/{(ARR+1)*(PSC+1)}
#define            ADVANCE_TIM_PERIOD            (80-1)	//计数周期
#define            ADVANCE_TIM_PSC               (9000-1)	//分频值
#define            ADVANCE_TIM_PULSE             20	//比较值,即pwm的脉宽

重复计数器

重复计数器有很多用途。

  • 在pwm信号输出上的应用

下面看STM32参考手册中的一张图:
12

由上图可以看出:

中央对齐模式下:
RCR = 0,更新事件没有延迟;
RCR = 1,更新事件延后了半个PWM周期;
RCR = 2, 更新事件延后了一个PWM周期;
RCR = 3,更新事件延后了3/2个PWM周期;
因此,中央对齐模式下,更新事件延迟的PWM周期数量等于RCR/2。
RCR 寄存器的范围是 0~0xFF;

边沿对齐模式下:
RCR = 0,更新事件没有延迟;
RCR = 1,更新事件延后了一个PWM周期;
RCR = 2, 更新事件延后了两个PWM周期;
RCR = 3,更新事件延后了三个PWM周期;
因此,边沿对齐模式延迟的PWM周期数量等于RCR。

可以简单理解为:每(N+1)个计数器溢出,就产生一个更新事件,N = RCR

在中心对齐模式下,对于奇数的RCR,更新事件要么发生在上溢,要么发生在下溢,这取决于写入RCR寄存器的时间和计数器启动的时间。如果RCR是在启动计数器之前写的,UEV在上溢时发生。如果RCR是在计数器启动后写入的,则UEV发生在下溢。

12345

如上图所示:

1、在中心对齐模式下:

(1) RCR = 2n,n>=0 表示输出(n+1)个pwm信号时在最后一个上溢位置产生更新事件,对应上图中红色箭头

(2) RCR = 2n+1,n>=0表示输出(n+1)个pwm信号时在最后一个下溢位置产生更新事件,对应上图中的黑色箭头;(此情况是在启动计数器后写入RCR)

2、在边沿对齐模式下:

向上计数:
RCR = n,n>=0表示输出(n+1)个pwm信号时在最后一个上溢处产生更新事件,对应上图中的绿色箭头
向下计数同理。

综上,可以通过这种方式在输出N个pwm信号后产生更新中断,从而去执行某个事件。比如步进电机带动受控物体移动到某个位置后产生update事件,然后触发ADC进行采样。

  • 在ADC采样上的应用

对于STM32芯片的ADC的转换启动,一般分为软件启动或外部触发事件启动。其中外部触发事件启动,可以是定时器触发事件或EXTI引脚信号。在很多应用场合,比如电机、电源、变频器等应用中,ADC的采样点可能会有很严格的时间要求,如果采样点选择错误,可能会给整个控制系统造成严重不良后果。

我们可以利用定时器的更新事件或比较输出信号作为ADC的触发使能信号。根据STM32F1参考手册查表得知,可以使用TIM1的TRGO事件或通道CH4的捕捉事件来触发注入通道(或规则通道)的ADC转换。

1、使用TIM1 TRGO来触发ADC

选择Tim1更新事件作为TRGO

TIM_SelectOutputTrigger(TIM1,TIM_TRGOSource_Update);

设定T1_TRGO作为ADC触发源

ADC_InitStructure.ADC_ExternalTrigConv =ADC_ExternalTrigConv_T1_TRGO;

2、利用TIM1 CH4的比较事件来触发ADC

这里就是选择OC4REF信号作为TRGO输出来触发ADC。

选择OC4REF作为TRGO;

TIM_SelectOutputTrigger(TIM1,TIM_TRGOSource_OC4Ref);

设定T1_TRGO作为ADC触发源

ADC_InitStructure.ADC_ExternalTrigConv =ADC_ExternalTrigConv_T1_TRGO;

有时候,我们可能需要多个周期才需进行一次ADC触发采样及相关计算。如果时间是基于更新时刻又是定时周期的倍数,较为方便的办法就是使用定时器里的重复计数器,使用更新事件作为TRGO。
如下设置:

配置重复计数器为2次,即计数器每溢出2次就update一次。

TIM_TimeBaseStructure.TIM_RepetitionCounter = 1;

选择Tim1更新事件作为TRGO

TIM_SelectOutputTrigger(TIM1,TIM_TRGOSource_Update);

设定T1_TRGO作为ADC触发源

ADC_InitStructure.ADC_ExternalTrigConv =ADC_ExternalTrigConv_T1_TRGO;

总结

本文主要讲解了如何使用高级定时器产生互补的pwm信号,介绍了死区如何设置;并进行了仿真实验。最后讲了一下重复计数器的用途。
后续有时间我再补充相关的一些知识点和可能遇到的问题。

附录

1、边沿对齐模式pwm波形
23
2、中心对齐模式pwm波形
2B

posted @ 2022-05-23 17:25  李星云姬如雪  阅读(98)  评论(0编辑  收藏  举报