STM32——步进电机学习记录
一、步进电机及其驱动器介绍
1.判断二相四线步进电机A,B相的方法:
将任意两个端子短接,转动转轴,判断与没有短接时的转矩是否有变化,若变大,则说明为同一相的两个不同端子,可以视为(A+,A-)。若不变则为不同相的端子(A,B)还需要继续进行判断。
2.步进电机驱动器介绍:
驱动器主要分为控制信号接口,电源接口,电机接口三个部分。其中控制信号接口可以是共阳极接法和共阴极接法两种。
引脚名称 | 功能 |
ENA |
使能信号 |
DIR | 控制方向 |
PUL | 脉冲信号 |
以共阴极为例,即ENA-,DIR-,PUL-接地,ENA+,DIR+,PUL+通过导线连接至IO口,当输出高电平时,信号有效。
3.驱动器的细分技术以及电流设置
通过拨码开关即可改变细分,在本电机中步距角为1.8°,通过拨码开关可以调节成4细分,8细分等。也可以通过拨码开关设置其驱动电流。具体可参照具体的驱动器所对应的表
二、步进电机驱动方式(一):PWM驱动
1.简述:
在共阴极接法下,通过对pul+接口输出PWM波形即可使得步进电机旋转。(PWM波形通过定时器的输出比较通道即可得到)
2.定时器配置:
初始化定时器时钟,OC通道对应IO口的时钟,初始化时基单元,初始化OC通道即可
void Timer_Init(uint32_t ARR,uint32_t PSC,uint32_t CCR)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;//初始化oc通道的io口,设置成复用推挽输出
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
TIM_InternalClockConfig(TIM2);//选择内部时钟
TIM_TimeBaseInitTypeDef TimeBase_InitStructure;
TimeBase_InitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TimeBase_InitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TimeBase_InitStructure.TIM_Period=ARR-1;//ARR
TimeBase_InitStructure.TIM_Prescaler=PSC-1;//PSC
TimeBase_InitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TimeBase_InitStructure);//初始化时基单元
TIM_OCInitTypeDef TIM_OCStructure;
TIM_OCStructure.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OCStructure.TIM_OCPolarity=TIM_OCPolarity_High;
TIM_OCStructure.TIM_OutputState=TIM_OutputState_Enable;
TIM_OCStructure.TIM_Pulse=CCR;//ccr
TIM_OC3Init(TIM2, &TIM_OCStructure);
TIM_Cmd(TIM2, ENABLE);
}
3.通过按键改变ENA+,DIR+的电平控制电机启停以及方向
void Step_Motor_Init(uint32_t ARR,uint32_t PSC,uint32_t CCR)
{
Timer_Init( ARR, PSC,CCR);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_2;//初始化oc通道的io口,设置成复用推挽输出
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_1);
GPIO_ResetBits(GPIOB,GPIO_Pin_2);//开始时ENA+为低电平,即不启动
}
//!: 代表值得取反,对于整形变量,只要不为0,使用 ! 取反都是0,0取反就是1。就像 bool 只有真假一样。
//~: 代表位的取反,对于整形变量,对每一个二进制位进行取反,0变1,1变0。
void Dir_change(void)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_2,(BitAction)!GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_2));
}
void Motor_star_stop(void)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_1,(BitAction)!GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_1));
}
void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3|GPIO_Pin_4;//初始化oc通道的io口,设置成复用推挽输出
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOE,&GPIO_InitStructure);
}
uint8_t Get_KeyNum(void)
{
uint8_t Keynum=0;
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)==0)
{
delay_us(20);
while(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)==0);
delay_us(20);
Keynum= 1;
}
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)==0)
{
delay_us(20);
while(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)==0);
delay_us(20);
Keynum= 2;
}
return Keynum;
}
4.连线后观察现象
三、步进电机驱动方式(二):翻转模式驱动
1.简述:
当TIM的CNT值与CCR值相等时,就会翻转电平。可以设想,如果我们在定时器的比较中断里以一定的方式改变CCR的值,便可以持续的使得输出比较通道使出高低电平。
2.定时器配置
主要就是设置定时器为翻转模式,并配置好定时器的输出比较中断,注意,这里并不是定时器的更新中断。
void Timer_Init(uint32_t ARR,uint32_t PSC,uint32_t CCR)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;//初始化oc通道的io口,设置成复用推挽输出
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
TIM_InternalClockConfig(TIM2);//选择内部时钟
/*===============================初始化时基单元===================================*/
TIM_TimeBaseInitTypeDef TimeBase_InitStructure;
TimeBase_InitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TimeBase_InitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TimeBase_InitStructure.TIM_Period=ARR-1;//ARR
TimeBase_InitStructure.TIM_Prescaler=PSC-1;//PSC
TimeBase_InitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TimeBase_InitStructure);//初始化时基单元
/*===============================初始化输出比较通道===================================*/
TIM_OCInitTypeDef TIM_OCStructure;
TIM_OCStructure.TIM_OCMode=TIM_OCMode_Toggle;//设置为翻转模式
TIM_OCStructure.TIM_OCPolarity=TIM_OCPolarity_High;
TIM_OCStructure.TIM_OutputState=TIM_OutputState_Enable;
TIM_OCStructure.TIM_Pulse=CCR;//ccr
TIM_OC3Init(TIM2, &TIM_OCStructure);
/*=============================一定要将其设为翻转模式=================================*/
TIM_ITConfig(TIM2,TIM_IT_CC3,ENABLE);//开启定时器二输出比较通道的中断通道
TIM_Cmd(TIM2, ENABLE);
NVIC_InitTypeDef Timer_OCIT_Initstrcuture;//配置NVIC
Timer_OCIT_Initstrcuture.NVIC_IRQChannel=TIM2_IRQn;
Timer_OCIT_Initstrcuture.NVIC_IRQChannelCmd=ENABLE;
Timer_OCIT_Initstrcuture.NVIC_IRQChannelPreemptionPriority=2;
Timer_OCIT_Initstrcuture.NVIC_IRQChannelSubPriority=2;
NVIC_Init(&Timer_OCIT_Initstrcuture);
}
3.主函数以及中断函数配置
#include "main.h"
uint8_t KeyNum=0;
u16 speed_factor=200;//这个参数便是决定电机转速的,这在中断函数中会体现
u16 val;
int main(void)
{
delay_init();
Key_Init();
uart_init(115200);
LCD_Init();
Step_Motor_Init(0xffff,36,0);//将ARR设置为最大,72MHz36分频,CCR的值初始为零
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
while(1)
{
/*===============================参数显示===================================*/
LCD_ShowNum(100,150,val,7,24);
LCD_ShowNum(100,100,speed_factor,7,24);
LCD_ShowNum(100,200,TIM2->CNT,7,24);
/*=========================================================================*/
KeyNum=Get_KeyNum();
if(KeyNum==1)
{
/*======================按键控制,为防止溢出,对转速因子取余=======================*/
speed_factor-=10;
speed_factor%=200;
}
if(KeyNum==2)
{
Motor_star_stop();
}
}
}
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_CC3)==SET)
{
val=TIM_GetCapture3(TIM2);//拿到当前CCR的值
TIM_SetCompare3(TIM2,(val+speed_factor)%0xffff);//当CNT的值和CCR相等的时候,边进入中断改变CCR的值,这里便是加上转速因子,可以想想,转速因子越小,CNT再次达到CCR的时间就越小,电平反转的速度也就越快,触发定时器比较中断的时间也就越短。
TIM_ClearITPendingBit(TIM2,TIM_IT_CC3);
}
}
4.调速
在开一个定时器TIM3,在里面调节speed_factor的值即可。
#include "main.h"
uint8_t KeyNum=0;
u16 speed_factor=200;
u16 val;
int8_t flag=1;
int main(void)
{
delay_init();
Key_Init();
uart_init(115200);
LCD_Init();
Step_Motor_Init(0xffff,36,0);
Timer3_Init(720,100);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
while(1)
{
LCD_ShowNum(100,150,val,7,24);
LCD_ShowNum(100,100,speed_factor,7,24);
LCD_ShowNum(100,200,TIM2->CNT,7,24);
KeyNum=Get_KeyNum();
if(KeyNum==1)
{
speed_factor-=10;
speed_factor%=200;
}
if(KeyNum==2)
{
Motor_star_stop();
}
}
}
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_CC3)==SET)
{
val=TIM_GetCapture3(TIM2);
TIM_SetCompare3(TIM2,(val+speed_factor)%0xffff);
TIM_ClearITPendingBit(TIM2,TIM_IT_CC3);
}
}
void TIM3_IRQHandler(void)
{ static u16 times=0;
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)
{
times++;
if(times==1000)
{
times=0;
if(flag==1)
{
speed_factor-=10;
if(speed_factor<=30)
{
flag=-1;
}
}
if(flag==-1)
{
speed_factor+=10;
if(speed_factor>=200)
{
flag=1;
}
}
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}
}
四、步进电机定位实验:翻转模式
1.简述:
即通过控制定时器输出比较通道输出脉冲的数量来控制电机输出的角度,这里依然使用定时器的翻转模式。
2.定义电机结构体
typedef struct
{
int8_t dir;
uint16_t angle;
uint8_t en;
uint16_t pulse;
}Step_Motor;
3.将对应的角度转换成对应的脉冲数
注意,对于定时器翻转中断来说,每产生两次翻转中断才能产生一个完整的脉冲波形。这里采用了16细分,由于本舵机步距角为1.8°,故1.8°/16=0.1125°,即每产生一个脉冲便使得步进电机旋转0.1125°。
void Motor_angle2pulse(void)
{
motor1.pulse=motor1.angle/0.1125;
}
四、中断函数以及主函数编写
#include "main.h"
uint8_t KeyNum=0;
u16 speed_factor=50;
u16 val;
int8_t flag=1;
uint16_t i=0;
int main(void)
{
delay_init();
Matrix_Key_Init();
uart_init(115200);
LCD_Init();
LCD_Display_Dir(0);
Step_Motor_Init(0xffff,72,0);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
while(1)
{
/*===============================电机参数显示===================================*/
LCD_ShowString(0,0,100,1,16,"val:");LCD_ShowNum(120,0,val,7,16);
LCD_ShowString(0,24,100,1,16,"speed_factor:");LCD_ShowNum(120,24,speed_factor,7,16);
LCD_ShowString(0,48,100,1,16,"TIM4->CNT:");LCD_ShowNum(120,48,TIM4->CNT,7,16);
LCD_ShowString(0,72,100,1,16,"i:");LCD_ShowNum(120,72,i,4,16);
LCD_ShowString(0,96,100,1,16,"motor1.pulse:");LCD_ShowNum(120,96,motor1.pulse,4,16);
LCD_ShowString(0,120,100,1,16,"motor1.angle:");LCD_ShowNum(120,120,motor1.angle,4,16);
LCD_ShowString(0,144,100,1,16,"flag:");LCD_ShowNum(120,144,flag,4,16);
LCD_ShowString(0,168,100,1,16,"DIR:");LCD_ShowNum(120,168,motor1.dir,4,16);
/*===============================这里使用了矩阵键盘===================================*/
KeyNum=Matrix_Key_GetNum();
/*===============================使angle转换成pulse===================================*/
if(KeyNum==1)
{
Motor_angle2pulse();
flag=1;
Motor_star_stop(1);
}
/*===============================改变方向===================================*/
if(KeyNum==2)
{
Dir_change();
}
/*===============================旋转角度+===================================*/
if(KeyNum==3)
{
motor1.angle+=45;
Motor_angle2pulse();
}
/*===============================旋转角度-===================================*/
if(KeyNum==4)
{
motor1.angle-=45;
Motor_angle2pulse();
}
}
}
void TIM4_IRQHandler(void)
{
if(TIM_GetITStatus(TIM4,TIM_IT_CC3)==SET)
{
/*====================每进入终端两次产生一次脉冲,脉冲数减一====================*/
i++;
if(i%2==0)
{
/*====================只有当脉冲数为正的时候才减,防止溢出====================*/
if(flag==1)motor1.pulse--;
if(motor1.pulse==0)
{
/*====================当脉冲数为零的时候,失能电机,直至下次失能====================*/
Motor_star_stop(0);
flag=0;
}
}
val=TIM_GetCapture3(TIM4);
TIM_SetCompare3(TIM4,(val+speed_factor)%0xffff);
TIM_ClearITPendingBit(TIM4,TIM_IT_CC3);
}
}
作者:Mistletoe29