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信号输出时插入死区(如下图所示),其他的通用、基本定时器没有这个功能。
这个delay就是死区时间,如何配置,看下图:
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);
程序运行效果如下:
想要改变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参考手册中的一张图:
由上图可以看出:
中央对齐模式下:
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发生在下溢。
如上图所示:
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波形
2、中心对齐模式pwm波形
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了