STM32电机控制基础知识
1. 前言
电机控制是嵌入式领域中一个前景广阔的专业方向。然而,面对繁杂的理论与实践,许多初学者常常感到无从下手。因此本篇文章将以STM32的标准库驱动直流有刷电机为例,分享一些电机控制的基础知识,希望可以给大家做一个参考。
2. 电机控制外设--高级定时器
一般而言,电机控制都是通过定时器输出PWM来进行控制的。而在STM32中,有高级定时器和通用定时器之分,高级定时器相较于普通定时器,多了互补输出和带死区刹车控制等功能,因此电机控制通常用高级定时器来进行控制。接下来先对高级定时器的互补输出、死区、刹车输入等基础知识进行介绍。
2.1 互补输出
在之前学习的STM32基础定时器知识中,可以通过定时器的某个通道CHX输出PWM波形。而在高级定时器中,每个CHX通道(x取1,2,3,4),存在着对应的CHXN通道,该通道称为互补输出通道,如下图所示:
红色的波形代表CHX通道,绿色的波形代表CHXN通道。当CHX通道输出高电平时,CHXN输出低电平,这就称为互补输出。
2.2 死区
带死区控制的互补输出指的是在CHX和CHXN输出的高低电平转换的等待时间,这段时间称为死区时间,只有死区时间过后,才能实现互补输出,如下图:
那为什么需要这段死区时间呢?答案还需要借助电机H桥来进行说明,带死区控制的互补输出通常用于电机控制的H桥中,H桥的简化示意图如下所示:
如上图H桥的原理表述为:Q1-Q4都是NPN型三极管。当采用互补输出时,OC1输出高电平期间,OCIN输出低电平。那么OC1输出高电平让Q1和Q4导通(原理看模电知识,此处不过多解释),那么此时电流经VCC流经Q1-->电机-->Q4-->GND,此时电机转动(假设正转);而OC1N输出高电平时,此时OC1输出低电平,此时Q2、Q3导通,Q1和Q4截止,电流方向相反,电机反转。
但是当没有死区时间时,在OC1和OC1N的高电平--低电平(或者是低电平--高电平)转换期间,三极管由于关断不及时(关断是需要时间的),这就会造成H桥的同一侧三极管同时导通,此时会让VCC直接与GND连接,造成短路!因此,需要在两个通道进行高低电平转换的时候插入死区时间,等待三极管到达稳定状态,再进行互补输出。
那又出现了新的问题,如何去计算这段死区时间呢?答案就在STM32高级定时器的寄存器中,一般使用CR1的CKD位和BDTR寄存器的UTG位来进行死区时间设置的,如下图所示:
举个例子:当UTG位的值为250时,250 的二进制数为11111010,即UTG[7:5]为111,所以选择第四条公式:DT=(32+ DTG[4:0]) * Tdtg,其中Tdtg = 16 * TDTS。而TDTS的值为CKD位的选择,如CKD=10时,此时为4分频。
2.3 刹车输入
刹车的概念即是字面意思,当配置一个IO口为刹车输入时,如果刹车有效,此时CHX和CHXN通道就不会输出PWM信号去驱动电机转动,此时电机就会慢慢停止。这就称为刹车,它也是由BDTR寄存器来进行设置的,如下图:
可见,当刹车输入有效时,此时MOE位会被硬件清零,此时会禁止OC和OCN输出或强制为空闲状态。
3. 编写互补输出驱动
在了解上述原理后,接下来使用STM32F103标准库编写驱动函数,代码如下所示:
/*IO初始化函数*/
void io_set(gpioled port,u16 pin,GPIOMode_TypeDef mode)
{GPIO_InitTypeDef GPIO_InitStructure;if(port==GPIOA) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); else if(port==GPIOB) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);else if(port==GPIOC) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);else if(port==GPIOD) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);else if(port==GPIOE) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);else if(port==GPIOF) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF, ENABLE);else if(port==GPIOG) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG, ENABLE);GPIO_InitStructure.GPIO_Pin = pin; GPIO_InitStructure.GPIO_Mode = mode; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(port, &GPIO_InitStructure); //初始化GPIO
}/*
*@breif:带刹车输入的死区互补输出
*@para:u16 arr
*@para:u16 psc
*@para:u16 ccr
*@para:u16 dtg
*@return:none
*/
void TIM1_dead_pwm_init(u16 arr,u16 psc,u16 ccr,u16 dtg)
{TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_OCInitTypeDef TIM_OCInitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //使能定时器1时钟io_set(GPIOA,GPIO_Pin_8,GPIO_Mode_AF_PP); //C1io_set(GPIOB,GPIO_Pin_13,GPIO_Mode_AF_PP); //C1Nio_set(GPIOB,GPIO_Pin_12,GPIO_Mode_IPU); //刹车输入io初始化,上拉输入//初始化TIM1TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV4; //CKD[1:0] = 10, tDTS = 4 * tCK_INTTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High; //互补输出极性:TIM输出比较极性高TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; //互补输出使能TIM_OCInitStructure.TIM_Pulse = ccr; //设置占空比TIM_OC1Init(TIM1, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC1TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); //使能TIM1在CCR上的预装载寄存器/*设置刹车*/TIM_BDTRStructInit(&TIM_BDTRInitStructure); //填充BDTR初始默认值,初始化TIM_BDTRInitStructure.TIM_DeadTime=dtg; //设置死区时间TIM_BDTRInitStructure.TIM_Break = TIM_Break_Enable; //使能刹车输入TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_Low; // BKIN 低电平触发刹车TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable; //使能AOE位,刹车结束后恢复输出TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure); //BDTR初始化TIM_CtrlPWMOutputs(TIM1, ENABLE); //使能MOE位TIM_Cmd(TIM1, ENABLE); //使能TIM1
}
驱动编写分为以下几个步骤。首先对定时器时钟使能(此处使用的是高级定时器TIM1);其次对CH1、CH1N以及刹车输入引脚进行初始化;设置定时器自动重装值及psc进行定时器初始化;接下来设置PWM模式,并且设置CH1、CH1N的输出极性及输出使能。
Note:此处的CH1和CH1N的输出极性都设置成高,不能一个设置成高,另一个设置成低,否则会造成CH1和CH1N同时输出高/低电平。原因是:当CH1设置为高有效,CH1N设置成低有效时,假如在PWM1模式,此时CNT<CCR的时候CH1输出有效电平为1,此时其互补输出CH1N应该为无效电平,但CH1的无效电平还是1,即同时输出高电平(但测试的时候发现都设置成低电平有效时也会出现该问题)。
最后,设置死区刹车。先设置刹车时间、使能刹车输入,将刹车输入设置成低电平有效;使能AOE位,即当刹车结束后可以恢复互补输出功能;TIM_CtrlPWMOutputs函数设置MOE位为1,此时才能实现互补输出,最后使能TIM1即可。
4. 直流有刷电机
在了解高级定时器的互补输出使用基础知识之后,接下来看一下如何通过互补输出去驱动直流有刷电机转动。
直流有刷电机BDC是一种内含电刷装置,可以将直流电能转换成机械能的电动机。 其结构由定子、转子、电刷和换向器组成。其中,定子用于产生固定的磁场,通常由永磁体或电磁绕组制成;转子由一个或多个铜线绕组构成,通电后可以在磁场中受力运动;电刷指的是将外部电流输入到转子绕组上;换向器用于改变转子绕组中电流的流向。了解结构之后来看一下直流有刷电机的工作原理。
4.1 直流有刷电机工作原理
其原理本质上是通电导线在磁场中受力运动,因此需要通过左手定则来进行判断(是否感觉到死去的高中物理知识在攻击你)。左手定则是判断通电导线处于磁场中时,所受安培力 F (或运动)的方向、磁感应强度B的方向以及通电导体棒的电流I三者方向之间的关系的定律。 左手定则的具体内容:将左手四指并拢伸直,使拇指与其他四指在平面内垂直,手掌方向 代表磁场的方向(从N级到S级),四指代表电流的方向(从正极到负极),那拇指所指的方向就是受力的方向,如下图所示:
知道左手定则后,来看直流有刷电机的简化示意图,如下所示:
由图可见,当电流从C端流入,D端流出时;此时根据左手定则可知a1a2线圈往上运动,b2b1线圈往下运动;当整个线圈转动90°后,此时换向器C和D的位置交换,那么此时电流从D端流入,C端流出,导致线圈继续沿着原来的方向受力转动,这就是直流有刷电机基本工作原理。
那么从上可知,电机想要调节速度时,即调节线圈的受力大小,也就是调节电流大小(即调节电源电压大小);电机想要换向时即调节电流方向。了解直流有刷电机的基本工作原理之后,接下来依然使用H桥电路(见2.2章节)来进行电机驱动。
4.2 直流有刷电机方向及速度控制原理
H桥电路可方便对电机的正反转方向及速度进行控制,具体原理如下:电机方向控制原理不再过多叙述(见2.2章节);电机调速原理是固定一路输入信号的电平,另一路信号利用PWM波作为输入,这样即可调节驱动板输出到电机的电压,从而实现电机的速度控制。波形如下所示:
具体来说,比如CH1固定为高电平时,CH1N输出PWM波,当CH1N为低电平周期时,此时电机会转动;这时CH1N转换为高电平周期时,由于CH1和CH1N都输出高电平,那么此时电机就会减速,但由于惯性电机不会立即停止。因此,可通过调节CH1N的PWM占空比来实现速度控制,即占空比越大,高电平周期越长,电机转动的就慢。
综上,可通过TIM1_CH1(主通道)输出PWM波,TIM1_CH1N(互补通道)固定输出高电平(后续讲如何实现),此时只要调节主通道输出的 PWM 占空比即可调整电机上的电压,进而控制电机的速度。当电机需要换向的时候,我们就让主通道固定输出高电平,互补通道输出PWM即可。
5. 编写直流有刷电机驱动程序
了解上述直流有刷电机的调速、换向原理之后,接下来先介绍电机调速时如何让某通道固定输出高/低电平。根据直流有刷电机调速原理,需要将一路输出固定电平,另外一路输出PWM波。为了方便同时对方向和速度进行控制,采用高级定时器的互补输出方式。但互补输出是主通道输出PWM方波时,互补通道输出相反的PWM方波。那如何做到在主通道输出PWM方波时,互补通道固定输出一个高电平或者低电平呢?
答案还是藏在BDTR的寄存器的OSSR位和OSSI位里,如下图:
上图中OSSR位代表的是当OSSR=1,定时器在运行模式下(计数使能的状态)如果突然关闭了互补输出中的某个通道(CCXE或CCXNE=0),那么此时被关闭通道的CH1或者CH1N将输出无效电平;同理,OSSI位代表的是定时器在空闲模式下(定时器不工作),如果设置为1,关闭对应通道时,对应通道输出空闲电平(具体看CR2寄存器,此处不过多展开)。更加具体来说,手册中表75对OSSR和OSSI的设置状态做了总结,如下图:
知道如何做到让互补输出的通道输出固定电平之后,接下来介绍驱动程序的编写,代码如下:
*直流有刷电机驱动*/
void TIM1_dead_pwm_init(u16 arr,u16 psc,u16 ccr,u16 dtg)
{TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_OCInitTypeDef TIM_OCInitStructure;TIM_BDTRInitTypeDef TIM_BDTRInitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //使能定时器1时钟io_set(GPIOA,GPIO_Pin_8,GPIO_Mode_AF_PP); //C1io_set(GPIOB,GPIO_Pin_13,GPIO_Mode_AF_PP); //C1Nio_set(GPIOB,GPIO_Pin_12,GPIO_Mode_IPU); //刹车输入io初始化,上拉输入//初始化TIM1TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV4; //CKD[1:0] = 10, tDTS = 4 * tCK_INTTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High; //互补输出极性:TIM输出比较极性高TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; //互补输出使能TIM_OCInitStructure.TIM_Pulse = ccr; //设置占空比TIM_OC1Init(TIM1, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC1TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); //使能TIM1在CCR上的预装载寄存器/*设置BDTR*/TIM_BDTRStructInit(&TIM_BDTRInitStructure); //填充BDTR初始默认值,初始化TIM_BDTRInitStructure.TIM_DeadTime=dtg; //设置死区时间TIM_BDTRInitStructure.TIM_OSSRState=TIM_OSSRState_Enable; //OSSR位设置为1TIM_BDTRInitStructure.TIM_OSSIState=TIM_OSSIState_Disable; //OSSI位设置为0TIM_BDTRInitStructure.TIM_Break = TIM_Break_Disable; //注意在本功能中没有用到刹车TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_Low; // BKIN 低电平触发刹车TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable; //使能AOE位,刹车结束后恢复输出TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure); //BDTR初始化TIM_CtrlPWMOutputs(TIM1, ENABLE); //使能MOE位TIM_Cmd(TIM1, ENABLE); //使能TIM1
}/*电机停止*/
void motor_stop(void)
{TIM_CCxCmd(TIM1,TIM_Channel_1,TIM_CCx_Disable); //关闭CH1通道PWM输出TIM_CCxNCmd(TIM1,TIM_Channel_1,TIM_CCx_Disable); //关闭CH1N通道PWM输出
}/*电机方向设置*/
void motor_dir(u8 para)
{TIM_CCxCmd(TIM1,TIM_Channel_1,TIM_CCx_Disable); //关闭CH1通道PWM输出TIM_CCxNCmd(TIM1,TIM_Channel_1,TIM_CCx_Disable); //关闭CH1N通道PWM输出if (para == 0) /* 正转 */{TIM_CCxCmd(TIM1,TIM_Channel_1,TIM_CCx_Enable); //开启主通道输出 } else if (para == 1) /* 反转 */{TIM_CCxCmd(TIM1,TIM_Channel_1,TIM_CCx_Enable); //开启互补通道输出}
}/*电机速度调节*/
void motor_speed(u16 ccr)
{if(ccr<1000) /*限速*/{TIM_SetCompare1(TIM1,ccr); //调节占空比}
}
由上可见,初始化函数与定时器的互补输出驱动函数差不多,主要是增加了OSSR、OSSI的设置。此处将OSSR设置为1,OSSI设置为0。
接下来看电机停止函数,电机停止函数主要是将CH1和CH1N两个通道进行关闭(需要注意本功能没有使用刹车输入实现电机停止),当两个通道关闭后,根据表75可知,OSSR为1,MOE=1(初始化函数中设置了),CCXE=CCXEN=0,此时禁止输出。
电机方向设置函数:先将两个通道都进行关闭,每次只打开一个通道,关闭一个通道;根据表75可知,此时会让一方输出固定电平,一方输出PWM信号。如此轮换,即可让电机换向。
最后是电机速度调节函数,调节PWM的占空比即可。
6. 参考文献
[1] STM32中文参考手册V10;
[2] DMF407电机控制_V1.0;
[3] STM32F103开发指南;
[4] STM32F103C8T6数据手册;