28BYJ-48步进电机驱动程序
这两天开始学调 28BYJ-48步进电机,但是淘宝卖家给的资料和网上搜到的都是51的程序,用的驱动板都是ULN2003。
以下是我自己写的STM32的程序。我用的板子是F103ZET6,驱动板是L298N。
电机驱动原理简述:电机内有四组线圈,每给一个电机供电,转子就会转动一个角度,按顺序轮流循环转动给四个线圈供电,就能让转子一直转起来。跟详细的说明网上有很多,这里就不重复了。
接线:红线接5V电源,其余四根按顺序接驱动板上,其余四根的颜色是橙黄蓝粉,不同厂家做的顺序可能不一样,但都是按顺序排的,比如我的顺序就是橙黄蓝粉,或者说粉蓝黄橙也对,反正就是按这个颜色的先后顺序接就对了。
时序说明:电机内部四个线圈的一端都接到5V上,所以我们需要先给线圈的另一端供5V的电,然后按一定的顺序拉低电压,使线圈通电,电机就能动了。我们是通过驱动板控制的,也就是说,IO口默认输出高电平,然后按时序给低电平。
↓↓↓四相八拍时序图↓↓↓
↓↓↓四相单四拍时序图↓↓↓
↓↓↓四相双四拍时序图↓↓↓↓
以下是源码:
首先是IO口的初始化,这里我设PD1为1号,PD4为2号,PD6为3号,PG9为4号。
#include "motor.h"
#include "sys.h"
#include "delay.h"
void my_motor_init(void)
{
GPIO_InitTypeDef GPIO_Init_config_d1;
GPIO_InitTypeDef GPIO_Init_config_d4;
GPIO_InitTypeDef GPIO_Init_config_d6;
GPIO_InitTypeDef GPIO_Init_config_g9;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOG,ENABLE);
GPIO_Init_config_d1.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init_config_d1.GPIO_Pin=GPIO_Pin_1;
GPIO_Init_config_d1.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOD,&GPIO_Init_config_d1);
GPIO_Init_config_d4.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init_config_d4.GPIO_Pin=GPIO_Pin_4;
GPIO_Init_config_d4.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init_config_d6.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init_config_d6.GPIO_Pin=GPIO_Pin_6;
GPIO_Init_config_d6.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOD,&GPIO_Init_config_d6);
GPIO_Init_config_g9.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init_config_g9.GPIO_Pin=GPIO_Pin_9;
GPIO_Init_config_g9.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOG,&GPIO_Init_config_g9);
PD1=1;
PD4=1;
PD6=1;
PG9=1;
}
然后是头文件
这里用了正点原子的 sys.h头文件进行了一些宏定义
里面声明了四个函数,第一个是上面的初始化,后面三个是三种不同的驱动写法
#include "sys.h"
#define PD1 PDout(1)
#define PD4 PDout(4)
#define PD6 PDout(6)
#define PG9 PGout(9)
#define IN1 PDout(1)
#define IN2 PDout(4)
#define IN3 PDout(6)
#define IN4 PGout(9)
#ifndef my_motor_H
#define my_motor_H
void my_motor_init(void);
void my_motor_working_0(int time_us);//low B写法
void my_motor_working_1(u16 arr[],int M_us);
void my_motor_working_2(u16 arr[],int M_us);
#endif
第一种
这是最直观的控制方法,直接一步一种情况的控制,每一步中间要有一个延迟,控制我们输出的低电平宽度,这里我用微秒us控制。据电机说明书,脉冲频率不能太高,我这里试了下,似乎至少要大于一点多个毫米ms,就是1000多微妙us,因此我在主函数里设脉冲频率为1100us。这段是四相八拍的程序,这样写的话不够灵活,换成其他方式就很麻烦。
void my_motor_working_0(int time_us)//low B写法
{
delay_init();
PD1=0;
PD4=1;
PD6=1;
PG9=1;
delay_us(time_us);//1
PD1=0;
PD4=0;
PD6=1;
PG9=1;
delay_us(time_us);//2
PD1=1;
PD4=0;
PD6=1;
PG9=1;
delay_us(time_us);//3
PD1=1;
PD4=0;
PD6=0;
PG9=1;
delay_us(time_us);//4
PD1=1;
PD4=1;
PD6=0;
PG9=1;
delay_us(time_us);//5
PD1=1;
PD4=1;
PD6=0;
PG9=0;
delay_us(time_us);//6
PD1=1;
PD4=1;
PD6=1;
PG9=0;
delay_us(time_us);//7
PD1=0;
PD4=1;
PD6=1;
PG9=0;
delay_us(time_us);//8
}
第二种
将每一步的io口状态都变成一个二进制数某一位,如上面的第一步,按PD1,PD4,PD6,PG9的顺序,就可以得到一个二进制数0111,第二步就是0011,不知道是不是MD5的原因还是什么原因,程序无法识别二进制前缀0b,所以我这里写成16进制,并按顺序写到一个数组中,那么我们就可以得到一个这样的数组(我在主函数里定义)
time_48_0[]={0x7,0x3,0xb,0x9,0xd,0xc,0xe,0x6};
在这段函数里,for进行0~7的循环,四个if分别判断数组元素的一二三四位,符合条件就进行相应的操作。要注意的是,符合条件和不符合条件的操作都要有,保持四根信号线默认是高电平的状态,或者说for每次运算完,四个io口的结果都是要符合上面我给出的时序表。拿这段程序举例,如果我不写else,io口就是上一步的状态,显然这样是有问题的。
void my_motor_working_1(u16 arr[],int M_us)
{
u8 i;
u16 c=0;
for(i=0;i<8;i++)
{
if(arr[i]&0x8) IN1=1; else IN1=0;
if(arr[i]&0x4) IN2=1; else IN2=0;
if(arr[i]&0x2) IN3=1; else IN3=0;
if(arr[i]&0x1) IN4=1; else IN4=0;
delay_us(M_us);
}
}
第三种
用数组里的数进行位运算。要注意的是,通过宏定义对io状态进行控制的数值,似乎只能是0和1,如5、7、8之类的数输入,虽然也有电,电机也会转,但转起来很奇怪,这里我统一只用0和1控制。
这段函数拿IN2距离,与0x4(即0100)进行与运算,这样就得到了第二位的结果(位3,位2,位1,位0),不管是0还是1,都右移最低位(位0),这样得出的结果就是前面三位都是0 ,只有最低位的数是有效数据,然后赋给宏定义符。
void my_motor_working_2(u16 arr[],int M_us)
{
u8 i;
u16 c=0;
for(i=0;i<8;i++)
{
IN1=(c=(arr[i]&0x8)>>3);
IN2=(c=(arr[i]&0x4)>>2);
IN3=(c=(arr[i]&0x2)>>1);
IN4=(c=(arr[i]&0x1)>>0);
delay_us(M_us);
}
}
然后就没了。最后再贴出我主函数。
#include "sys.h"
#include "motor.h"
#include "delay.h"
int main(void)
{
u16 time_48_0[]={0x7,0x3,0xb,0x9,0xd,0xc,0xe,0x6}; //四相八拍 顺转
u16 time_48_1[]={0x6,0xe,0xc,0xd,0x9,0xb,0x3,0x7}; //四相八拍 逆转
u16 time_414_0[]={0x7,0xb,0xd,0xe}; //四相单四拍 顺转
u16 time_414_1[]={0xe,0xd,0xb,0x7}; //四相单四拍 逆转
u16 time_424_0[]={0x6,0x3,0x9,0xc}; //四相双拍 顺转
u16 time_424_1[]={0xc,0x9,0x3,0x6}; //四相双拍 逆转
int us=0;
my_motor_init();
delay_init();
us=1200;
while(1)
{
my_motor_working_1(time_424_0,us);
// my_motor_working_2(time_48_0,us);
// my_motor_working_0(1200);
}
}
这里的顺转和逆转,跟线序有关,等你开始调的时候你就懂了。