首先聊聊 PWM 输出。脉宽调制(PWM,Pulse Width Modulation)是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。尤其是电机控制。
PWM 的主要参数包括频率和占空比。
死区的概念是在 PWM 控制电机引入的。由于 H 桥的存在,每个桥的上半桥和下半桥是绝对不能同时导通的,但高速的PWM驱动信号在达到功率元件的控制端时,往往会由于各种各样的原因产生延迟的效果,造成某个半桥元件在应该关断时没有关断,造成功率元件烧毁。
死区就是在互补的输出中,人为的插入 Delay,以致 H 桥不至于出现同时导通的情况。类似于 DTPHx 和 DTPHx_ 信号 :
STM32 的定时器功能十分强大,不仅仅实现了定时器的基本功能,对于高级定时器 TIM1/TIM8,更是能够输出 PWM,波形,同时还带插入死区的互补输出。天生就是拿来做电机控制的一把好手。
TIM1 能够输出 4 路 PWM 信号,所有逻辑全部做在了硬件里面,也就是说, TIM1/TIM8 的硬件逻辑中,绑定了几个引脚,不单纯的只是 Timer:
如图所示,TIM1 的四个 Channel 1 ~ Channel 4 分别对应着 PA8 ~ PA11,互补输出的信号 Channel 1 N ~ Channel 3 N 分别对应到了 PB13 ~ PB15 管脚。
对应到单板上:
好了,然后连接逻辑分析仪上去:
环境准备 Ready!!!
TIM1 的功能能强大,不仅仅支持计数器,同时可以支持 4 个独立通道的:
这里关注 PWM 相关的逻辑。
TIM1 的时钟来源于 APB2 的时钟输入:
需要开启 TIM 的时钟输入。这里配置的 APB2 时钟为 72MHz。
TIMxCLK 输入到 TIM1 模块后,会有一个预分频器(TIMx_PSC),对其进行分频,分出来的频率就供给 TIM1 的频率 (上限 65536)。同时存在一个自动装载寄存器 (TIMx_ARR) ,这个寄存器的作用是当计数器以预分频后的时钟进行计数后,达到这个装载寄存器的值,就完成一次 Timer 事件。
TIM1 还包含一个重复计数器,灵活的配置这个寄存器,能够使得多次达到 TIMx_ARR 的值后,才产生对应的事件。
TIM1 有一个预装载的功能,如果打开这个功能,代表更新 TIMx_ARR 寄存器的值,将在下一次完成事件后,才对 TIMx_ARR 进行更新,否则,立即更新 TIMx_ARR。
计数器的方式有三种:向上计数,向下计数,以及中央对齐模式(向上/向下计数) ,非常灵活。具体的详见 DataSheet。
TIM1 的支持的配置相当繁多,这里仅仅以向上计数的方式介绍。
在 PWM 模式中,主要是控制波形的周期和占空比:
周期的控制方式由,TIMx_ARR寄存器确定
由TIMx_CCRx寄存器确定占空比的信号。
这里引入了一个 TIMx_CCRx 寄存器,他是一个比较输出的寄存器:
下面是一个PWM模式1的例子。当 TIMx_CNT < TIMx_CCRx 时,PWM 输出的参考信号 OCxREF 为高,否则为低。以此来达到控制占空比的要求。
针对这个, STM32 有一个有效电平的概念,即先定义有效电平是 1 还是 0,然后根据和比较寄存器的比较情况,来输出有效电平。(够繁琐)。
1. 当然,还是需要首先开启管脚的时钟和配置管脚的 mode
2. 配置 NVIC (如果有中断需要)
3. 开启 TIM1/TIM8 的时钟
4. 配置 TIMx_ARR 寄存器,来确定周期
5. 配置分频寄存器,确定分频参数
6. 配置计数方式(向上,向下,中央)
7. 配置 PWM1 模式
8. 开启输出和互补输出
9. 配置 TIMx_CCRx ,比较寄存器的值(后面可以更改)
10. 配置输出信号的极性
11. 开启每个输出通道的预装入功能
12. 配置死区时间
13. 开启 TIM1 的整个模块的预装入功能
14. 打开 PWM 输出
15. 开启 TIM1 的使能位
#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_dma.h"
#include "stm32f10x_tim.h"
#include "stm32f10x_pwr.h"
#include "sk_pwm.h"
#define TIMx_DEFAULT_PERIOD (1000)
#define CCRx_DEFAULT_VALUE (TIMx_DEFAULT_PERIOD / 2)
#define TIMx_DEFAULT_PRE (72)
static void SK_PWM_PortInit(void)
{
GPIO_InitTypeDef stGpioInit;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
stGpioInit.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11;
stGpioInit.GPIO_Mode = GPIO_Mode_AF_PP;
stGpioInit.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &stGpioInit);
stGpioInit.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_Init(GPIOB, &stGpioInit);
}
static void SK_PWM_NvicInit(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
static void SK_PWM_ClockInit(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 | RCC_APB2Periph_TIM8, ENABLE);
}
static void SK_PWM_ModeInit(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_BDTRInitTypeDef TIM1_BDTRInitStruct;
// Configure the period and prescaler
TIM_TimeBaseStructure.TIM_Period = (TIMx_DEFAULT_PERIOD - 1);
TIM_TimeBaseStructure.TIM_Prescaler = (TIMx_DEFAULT_PRE - 1);
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
// Configure the Output mode
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 = CCRx_DEFAULT_VALUE;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCNPolarity= TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCRx_DEFAULT_VALUE / 2;
TIM_OC2Init(TIM1, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCRx_DEFAULT_VALUE / 4;
TIM_OC3Init(TIM1, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCRx_DEFAULT_VALUE / 10;
TIM_OC4Init(TIM1, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM1_BDTRInitStruct.TIM_OSSRState = TIM_OSSRState_Disable;
TIM1_BDTRInitStruct.TIM_OSSIState = TIM_OSSIState_Disable;
TIM1_BDTRInitStruct.TIM_LOCKLevel = TIM_LOCKLevel_OFF;
TIM1_BDTRInitStruct.TIM_DeadTime = 205;
TIM_BDTRConfig(TIM1, &TIM1_BDTRInitStruct);
TIM_ARRPreloadConfig(TIM1, ENABLE);
TIM_CtrlPWMOutputs(TIM1, ENABLE);
TIM_ClearFlag(TIM1, TIM_FLAG_Update);
TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
}
static void SK_PWM_Enable(void)
{
TIM_Cmd(TIM1, ENABLE);
}
void SK_PWM_Init(void)
{
SK_PWM_PortInit();
SK_PWM_ClockInit();
SK_PWM_NvicInit();
SK_PWM_ModeInit();
SK_PWM_Enable();
}
void TIM1_UP_IRQHandler(void)
{
static uint32_t cnt = 0;
if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
}
}
使用逻辑分析仪抓到的结果如下:
互补输出时间 delta 测量为 0,即几乎是同时输出翻转:
互补输出时间 delta 测量为 5us,实现死区控制: