前言
这一次要分享的项目是最近接单做的一个小玩意儿,基于51单片机的步进电机驱动。最近积压了两个月的小项目会在后面陆续发出,好了回归正题。本次步进电机驱动的话主要实现的功能就是实现:
步进电机的加速和减速,正转反转和开启或者停止工作。用LCD1602显示当前工作转速及正反转状态,可通过按键控制电机的开启和关闭。
一、我们该如何实现电机驱动?
目前步进电机的驱动系统已经非常完善了,总结下来你只需要准备这几个信号即可:
脉冲信号:步进电机是脉冲驱动型,拿最常见的42步进电机来说,普通状态下它的步距角为1.8°。这个步距角的含义指的是,你给电机一个脉冲信号,步进电机就转动1.8°,而很明显我们转动一圈需要360°,也就是说我们给步进电机200个脉冲信号,就能够让步进电机转一圈。因此只需要单片机内部发出脉冲信号,根据你自己的需求,比如你想使步进电机转动速度变快,相应的增加脉冲的频率即可,在相同的时间内发出的脉冲数越多,你就可以转动的越快。
方向信号:顾名思义,你给高电平或者低电平,对应的步进电机转动的方向就为正方向或者反方向。你只需要设置好IO口输出状态,51单片机不用管,32单片机推挽输出即可。
脱机信号:这玩意儿我的评价是,暂时用不到,悬空就行。
其他的就是供电电源之类的准备,其实搞懂这些,就可以实现对步进电机的驱动了
二、驱动实现
1.硬件准备
- 主要的硬件清单
1、AT89C51最小系统套件,兼容即可
2、LCD1602液晶显示屏
3、LM2596S-5.0稳压芯片
4、步进电机驱动器+42步进电机(我买的是一体式的)
5、供电电源(我用的是XH_2接口的12V电池接入
- 接线说明
P1口的0-4号引脚均接入按键中,实现不同的功能
P3^7为正反转的控制位
P3^4为脉冲的输出位
- 电路图
- PCB
2.软件编写
- 关键点
在代码中总共用到了两个定时器:
其中定时器一主要是为了刷新界面,每隔2s对界面进行刷新,注意此处是在步进电机不细分的情况下,对应的pulse_speed参量是每秒钟的脉冲总数
定时器二主要是对脉冲输出进行控制,work_degree为步进电机的档位,根据不同的档位选择不同的定时时间,也就是脉冲的频率,档位越高,定时器的时间就越短,相对应的频率也就越高,在此处还定义了一个flag变量,定义此变量的意义在于每执行一次定时器,输出电平就会反转。相当于输出脉冲的频率是此定时器的1/2,快乐加倍。
至于其他地方代码都是比较简洁明了的,也有相对应的注释。
- 代码
main.c
#include<reg51.h>
#include"lcd.h"
#define uchar unsigned char
#define uint unsigned int
typedef unsigned int u16; //对数据类型进行声明定义
typedef unsigned char u8;
void DelayMs(unsigned int );
/*******************************************************************************
* 函数名 : 变量定义
* 函数功能 : 新的变量
* 输入 : 无
* 输出 : 无
*******************************************************************************/
unsigned char num[4]; //显示每分钟多少转
unsigned char dangwei[1]; //档位的显示
unsigned int pulse_speed=100; //每分钟多少转
unsigned int pulse_speed1=0; //每分钟多少转
unsigned char code wenzi1[16]={"now_V: r/min"};
unsigned char code wenzi2[16]={"status: "};
unsigned char code zheng1[5]={"zheng"};
unsigned char code fan1[5]={"fan "};
//占空比输出
uint flag=1; //计时增加值
u16 duty=1; //占空比
u16 work_degree=1; //步进电机工作等级
u16 work_status=0; //工作状态,为0的时候不工作,为1的时候工作
//电机控制
sbit status=P3^7; //正反转控制位
sbit out=P3^4; //脉冲输出位
//按键控制
sbit add=P1^0; //加速
sbit reduce=P1^1; //减速
sbit zheng=P1^2; //正转
sbit fan=P1^3; //反转
sbit start=P1^4; //开始或者停止工作
/*******************************************************************************
* 函 数 名 : delay
* 函数功能 : 延时函数,i=1时,大约延时10us
*******************************************************************************/
void delay(u16 i)
{
while(i--);
}
/*******************************************************************************
* 函 数 名 : keypros
* 函数功能 : 按键处理函数,判断按键K1是否按下
*******************************************************************************/
void keypros()
{
if(add==0) //检测按键K1是否按下
{
delay(1000); //消除抖动 一般大约10ms
if(add==0) //再次判断按键是否按下
{
work_degree+=1;
if(work_degree==8)work_degree=7;
}
while(!add); //检测按键是否松开
}
if(reduce==0) //检测按键K1是否按下
{
delay(1000); //消除抖动 一般大约10ms
if(reduce==0) //再次判断按键是否按下
{
work_degree-=1;
if(work_degree==0)work_degree=1;
}
while(!reduce); //检测按键是否松开
}
if(zheng==0) //检测按键K1是否按下
{
delay(1000); //消除抖动 一般大约10ms
if(zheng==0) //再次判断按键是否按下
{
status=1;
}
while(!zheng); //检测按键是否松开
}
if(fan==0) //检测按键K1是否按下
{
delay(1000); //消除抖动 一般大约10ms
if(fan==0) //再次判断按键是否按下
{
status=0;
}
while(!fan); //检测按键是否松开
}
if(start==0) //检测按键K1是否按下
{
delay(1000); //消除抖动 一般大约10ms
if(start==0) //再次判断按键是否按下
{
work_status+=1;
if(work_status>=2)work_status=0;
}
while(!start); //检测按键是否松开
}
}
/*******************************************************************************
* 函 数 名 : Timer0Init
* 函数功能 : 定时器0初始化
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Timer0Init()
{
TMOD|=0X11;//选择为定时器0模式,工作方式1,仅用TR0打开启动。
TH0=0XD8; //给定时器赋初值,定时1ms
TL0=0XF1;
ET0=1;//打开定时器0中断允许
EA=1;//打开总中断
TR0=1;//打开定时器
}
/*******************************************************************************
* 函 数 名 : Timer1Init
* 函数功能 : 定时器1初始化
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Timer1Init()
{
TMOD|=0X11;//选择为定时器1模式,工作方式1,仅用TR1打开启动。
TH1=0X3C; //给定时器赋初值,定时50ms
TL1=0XB1;
ET1=1;//打开定时器1中断允许
EA=1;//打开总中断
TR1=1;//打开定时器
}
/*******************************************************************************
* 函数名 : main
* 函数功能 : 主函数
* 输入 : 无
* 输出 : 无
*******************************************************************************/
void main()
{
unsigned char i;
Timer0Init(); //定时器0初始化
Timer1Init(); //定时器1初始化
LcdInit();
LcdWriteCom(0x80);
for(i=0;i<16;i++)
{
LcdWriteData(wenzi1[i]);
}
LcdWriteCom(0x80+0x40);
for(i=0;i<16;i++)
{
LcdWriteData(wenzi2[i]);
}
while(1)
{
//按键处理
keypros();
//显示转速
// pulse_speed=;
num[0]=pulse_speed/1000;
num[1]=pulse_speed%1000/100;
num[2]=pulse_speed%100/10;
num[3]=pulse_speed%10;
dangwei[0]=work_degree;
if(num[0]>9)
{
LcdWriteCom(0x80+0x06); //设置显示位置
LcdWriteData(0x37+num[0]); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0x80+0x06);
LcdWriteData(num[0]+0x30); //将数值转换为该显示的ASCII码
}
if(num[1]>9)
{
LcdWriteCom(0x80+0x07);
LcdWriteData(num[1]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0x80+0x07);
LcdWriteData(num[1]+0x30); //将数值转换为该显示的ASCII码
}
if(num[2]>9)
{
LcdWriteCom(0x80+0x08);
LcdWriteData(num[2]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0x80+0x08);
LcdWriteData(num[2]+0x30); //将数值转换为该显示的ASCII码
}
if(num[3]>9)
{
LcdWriteCom(0x80+0x09);
LcdWriteData(num[3]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0x80+0x09);
LcdWriteData(num[3]+0x30); //将数值转换为该显示的ASCII码
}
//显示正反转
if(status==1)
{
LcdWriteCom(0x80+0x47);
for(i=0;i<5;i++)
{
LcdWriteData(zheng1[i]);
}
}
else
{
LcdWriteCom(0x80+0x47);
for(i=0;i<5;i++)
{
LcdWriteData(fan1[i]);
}
}
//显示当前档位
if(dangwei[0]>9)
{
LcdWriteCom(0x80+0x4F);
LcdWriteData(dangwei[0]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0x80+0x4F);
LcdWriteData(dangwei[0]+0x30); //将数值转换为该显示的ASCII码
}
}
}
/*******************************************************************************
* 函 数 名 : void Timer0() interrupt 1
* 函数功能 : 定时器0中断函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Timer0() interrupt 1
{
static u16 i;
TH0=0Xdc; //给定时器赋初值,定时10ms
TL0=0X00;
i++;
if(i==200)
{
if(work_degree==1)pulse_speed=50;
else if(work_degree==2)pulse_speed=100;
else if(work_degree==3)pulse_speed=200;
else if(work_degree==4)pulse_speed=500;
else if(work_degree==5)pulse_speed=1000;
else if(work_degree==6)pulse_speed=2000;
else if(work_degree==7)pulse_speed=4000;
i=0;
}
}
/*******************************************************************************
* 函 数 名 : void Timer1() interrupt 3
* 函数功能 : 定时器0中断函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Timer1() interrupt 3
{
if(work_degree==1)TH1=0xD8,TL1=0xF1;//定时10ms
else if(work_degree==2)TH1=0xEC,TL1=0x79;//定时5ms
else if(work_degree==3)TH1=0xF6,TL1=0x3D;//定时2.5ms
else if(work_degree==4)TH1=0xFC,TL1=0x19;//定时1ms
else if(work_degree==5)TH1=0xFE,TL1=0x0D;//定时500us
else if(work_degree==6)TH1=0xFF,TL1=0x07;//定时250us
else if(work_degree==7)TH1=0xFF,TL1=0x84;//定时125us
flag++;
if(flag>1)
{
flag=0;
}
if(work_status==1)
{
if(flag<duty)
{
out=1;
}
else
{
out=0;
}
}
else
{
out=0;
}
}
Lcd.c
#include"lcd.h"
/*******************************************************************************
* 函 数 名 : Lcd1602_Delay1ms
* 函数功能 : 延时函数,延时1ms
* 输 入 : c
* 输 出 : 无
* 说 名 : 该函数是在12MHZ晶振下,12分频单片机的延时。
*******************************************************************************/
void Lcd1602_Delay1ms(uint c) //误差 0us
{
uchar a,b;
for (; c>0; c--)
{
for (b=199;b>0;b--)
{
for(a=1;a>0;a--);
}
}
}
/*******************************************************************************
* 函 数 名 : LcdWriteCom
* 函数功能 : 向LCD写入一个字节的命令
* 输 入 : com
* 输 出 : 无
*******************************************************************************/
#ifndef LCD1602_4PINS //当没有定义这个LCD1602_4PINS时
void LcdWriteCom(uchar com) //写入命令
{
LCD1602_E = 0; //使能
LCD1602_RS = 0; //选择发送命令
LCD1602_RW = 0; //选择写入
LCD1602_DATAPINS = com; //放入命令
Lcd1602_Delay1ms(1); //等待数据稳定
LCD1602_E = 1; //写入时序
Lcd1602_Delay1ms(5); //保持时间
LCD1602_E = 0;
}
#else
void LcdWriteCom(uchar com) //写入命令
{
LCD1602_E = 0; //使能清零
LCD1602_RS = 0; //选择写入命令
LCD1602_RW = 0; //选择写入
LCD1602_DATAPINS = com; //由于4位的接线是接到P0口的高四位,所以传送高四位不用改
Lcd1602_Delay1ms(1);
LCD1602_E = 1; //写入时序
Lcd1602_Delay1ms(5);
LCD1602_E = 0;
// Lcd1602_Delay1ms(1);
LCD1602_DATAPINS = com << 4; //发送低四位
Lcd1602_Delay1ms(1);
LCD1602_E = 1; //写入时序
Lcd1602_Delay1ms(5);
LCD1602_E = 0;
}
#endif
/*******************************************************************************
* 函 数 名 : LcdWriteData
* 函数功能 : 向LCD写入一个字节的数据
* 输 入 : dat
* 输 出 : 无
*******************************************************************************/
#ifndef LCD1602_4PINS
void LcdWriteData(uchar dat) //写入数据
{
LCD1602_E = 0; //使能清零
LCD1602_RS = 1; //选择输入数据
LCD1602_RW = 0; //选择写入
LCD1602_DATAPINS = dat; //写入数据
Lcd1602_Delay1ms(1);
LCD1602_E = 1; //写入时序
Lcd1602_Delay1ms(5); //保持时间
LCD1602_E = 0;
}
#else
void LcdWriteData(uchar dat) //写入数据
{
LCD1602_E = 0; //使能清零
LCD1602_RS = 1; //选择写入数据
LCD1602_RW = 0; //选择写入
LCD1602_DATAPINS = dat; //由于4位的接线是接到P0口的高四位,所以传送高四位不用改
Lcd1602_Delay1ms(1);
LCD1602_E = 1; //写入时序
Lcd1602_Delay1ms(5);
LCD1602_E = 0;
LCD1602_DATAPINS = dat << 4; //写入低四位
Lcd1602_Delay1ms(1);
LCD1602_E = 1; //写入时序
Lcd1602_Delay1ms(5);
LCD1602_E = 0;
}
#endif
/*******************************************************************************
* 函 数 名 : LcdInit()
* 函数功能 : 初始化LCD屏
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
#ifndef LCD1602_4PINS
void LcdInit() //LCD初始化子程序
{
LcdWriteCom(0x38); //开显示
LcdWriteCom(0x0c); //开显示不显示光标
LcdWriteCom(0x06); //写一个指针加1
LcdWriteCom(0x01); //清屏
LcdWriteCom(0x80); //设置数据指针起点
}
#else
void LcdInit() //LCD初始化子程序
{
LcdWriteCom(0x32); //将8位总线转为4位总线
LcdWriteCom(0x28); //在四位线下的初始化
LcdWriteCom(0x0c); //开显示不显示光标
LcdWriteCom(0x06); //写一个指针加1
LcdWriteCom(0x01); //清屏
LcdWriteCom(0x80); //设置数据指针起点
}
#endif
lcd.h
#ifndef __LCD_H_
#define __LCD_H_
/**********************************
当使用的是4位数据传输的时候定义,
使用8位取消这个定义
**********************************/
//#define LCD1602_4PINS
/**********************************
包含头文件
**********************************/
#include<reg51.h>
//---重定义关键词---//
#ifndef uchar
#define uchar unsigned char
#endif
#ifndef uint
#define uint unsigned int
#endif
/**********************************
PIN口定义
**********************************/
#define LCD1602_DATAPINS P0
sbit LCD1602_E=P2^5;
sbit LCD1602_RW=P2^6;
sbit LCD1602_RS=P2^7;
/**********************************
函数声明
**********************************/
/*在51单片机12MHZ时钟下的延时函数*/
void Lcd1602_Delay1ms(uint c); //误差 0us
/*LCD1602写入8位命令子函数*/
void LcdWriteCom(uchar com);
/*LCD1602写入8位数据子函数*/
void LcdWriteData(uchar dat) ;
/*LCD1602初始化子程序*/
void LcdInit();
#endif
需要注意的是,虽然lcd的代码是基于普中科技的实验例程修正过来的,但是对于RS\RW\EN三个接口的引脚有所更改,建议复制粘贴我的lcd代码
3.实物
实物视频请移步我的个人主页视频去看
总结
冲冲冲,步进电机驱动很简单,大家可以在此基础上结合其他模块玩一些好玩的东西,稍后代码和资料会发布到我的资源中