步进电机控制系统+LCD状态显示+详细源码注释+proteus仿真(超详细)

功能

实现的功能如下:

1)实现电机正转和反转;

2)实现一定范围内的电机转速变化;

3)通过2x4键盘控制电机的启动、停止、正反转、加减速、时间的修改和转速设置的清零,并配合LCD屏幕显示系统运行状态;

4)显示电机运行当前状态,包括(启动、停止、正转、反转、加速、减速、修改小时、修改分钟等);

5)LCD显示北京时间,可调整小时和分钟(秒钟不可修改),可以加可以减;

6)配有电机运行指示灯,电机运转时,指示灯亮起,电机停止时,指示灯灭掉,用来指示电机是否运行;

7)配有复位按键,运行出错可手动复位;

 

步进电机控制系统+LCD状态显示+详细源码注释+proteus仿真(超详细)_第1张图片

输入输出模块介绍

输入模块

1)按键模块

     按键模块用于接收外界的输入,通过按下按键来控制电机、时间等,用于人机交互,起到主控制台的作用,键盘的查询方式选择的是中断查询,可以减小时钟的利用率;

2)复位按键

     复位按键用于防止系统“跑飞”,工作不正常,或者出现事故要紧急停止,可以通过复位键来复位,使单片机重新从一个已知状态运行;

3)启动模块

     启动按键与其他按键不同被单独列了出来,可以防止误启动,利用外部中断来进行启动,实时性较好,且启动后会以默认转速运行,防止启动之前的设定产生干扰,防止危险发生;

输出模块

1)LCD屏幕显示

     采用液晶显示屏LM016L,用来显示时间和状态,当按键按下后,在液晶显示屏上会显示按键按下后的状态,和执行的操作,更加直观的进行人机交互,可以让操作者了解自己的操作是否被执行,系统此时运行是否正确;

2)电机模块

     电机模块由电机驱动模块ULN2003A和步进电机MOTOR-STEPPER组成,由于单片机引脚的驱动电流很小,所以需要相应的电机驱动模块来放大驱动电流,放大后的电流用来驱动六线制步进电机;

3)电机运行指示灯

     当电机运行时,指示灯亮起,用来提醒用户电机正在运行,也方便用来查错,当指示灯亮起,但是电机未运行时,可考虑步进电机是否损坏;

Proteus绘制硬件电路原理图

 

步进电机控制系统+LCD状态显示+详细源码注释+proteus仿真(超详细)_第2张图片

键盘

此次采用的是2x4的小键盘,共八个按键,实现的功能有:恢复系统默认转速、电机的停止、电机的加速、电机的减速、电机的正反转切换、电子时钟小时/分钟调整的切换、选定时间的加、选定时间的减。

值得一提的是键盘的查询选用的是中断查询,相比于一般查询方式,节省了系统资源,提高了系统的效率。

在正常情况下P2.4~P2.7为低电平,P3.0和P3.1为高电平,经过与门之后,接到中断0引脚的电平为高;当有按键按下时,P3.0和P3.1必有一为低电平,故接到中断引脚的电平为低,触发中断,进入中断程序,在中断程序中是键盘扫描程序,具体去判断是哪一个按键按下,在键盘扫描程序中,有按键功能程序,对相应的值进行设定和修改,进而控制系统运行。设定结束后,P2.4~P2.7重新设置为低电平,P3.0和P3.1设置为高电平。

电机驱动

P0口用于输出电机驱动信号,但是当P0口用作I/O口线,其由数据总线向引脚输出(即输出状态Output)的工作过程:当写锁存器信号CP 有效,数据总线的信号→锁存器的输入端D→锁存器的反向输出Q非端→多路开关→V2管的栅极→V2的漏极到输出端P0.X。前面我们已讲了,当多路开关的控制信号为低电平0时,与门输出为低电平,V1管是截止的,所以作为输出口时,P0是漏极开路输出,类似于OC门,当驱动上接电流负载时,需要外接上拉电阻。

故选择排阻作P0口的上拉电阻,P0口才可正常输出,由于P0口能够提供的驱动电流太小,最大20mA,故采用达林顿管ULN2003A作步进电机的驱动单元,由P0控制。达林顿管是两个三极管复合成的,相当于一个三极管,但比一个三极管的电流放大倍数大了很多,提高了电流驱动能力。输出电流可达数百mA,满足步进电机的驱动要求。

步进电机正转的驱动数组为:0x09,0x08,0x0c,0x04,0x06,0x02,0x03,0x01;

同理,反转为:0x01,0x03,0x02,0x06,0x04,0x0c,0x08,0x09。

在P0口按顺序,不间断的输出控制信号,即可达到电机驱动的作用。

运行示例

电机启动:

步进电机控制系统+LCD状态显示+详细源码注释+proteus仿真(超详细)_第3张图片

源代码及注释:

#include"reg51.h" //头文件
#include  //字符操作

#define uchar unsigned char //宏定义
#define uint unsigned int

uchar time[] = "23:59:56";//时间字符串
uchar state[]="  hello  ";//状态字符串

char code tab[2][4]={{'0','1','2','3'},
		     {'4','5','6','7'}};       //0到7的8个键植
uint num=0;//判断是否到1秒
uchar speed=100;//用来延时控制转速
bit time_flag=0; //修改时间的标志位,修改小时或分钟
bit turn_flag=0;//转速方向标志位
bit turn_stop=0;//启停标志位
uchar key;//键值
sbit E=P2^2;//使能E,以脉冲形式发送信号
sbit RS=P2^1;//0:指令      1:字符
sbit RW=P2^0;//0:写        1:读
sbit led=P2^3;//电机运行状态灯

		     
void keyscan();//用在中断中的键盘扫描
void key_gn();//按键功能
void moto();//电机驱动
				
void delay(uint x)//延时
{
	uint i;
	i=x*100;
	while(i--);	
}

void LCD_1602_Cmd(uchar cmd)//lcd写指令
{
	RS=0;	 //时序为RS、RW、E
	RW=0;
	P1=cmd;
	E=1;		 //一次脉冲、一次使能信号
	delay(2);
	E=0;
}

void LCD_1602_Data(uchar dat)//lcd写字符
{
	RS=1;
	RW=0;
	P1=dat;
	E=1;
	delay(2);
	E=0;
}
void LCD_1602_init()//lcd初始化
{
	LCD_1602_Cmd(0X38);//开显示:8位一行、5x7显示点阵	
	LCD_1602_Cmd(0X06);//每写入一个字符后指针加一,即光标右移一位
	LCD_1602_Cmd(0X0c);//开显示但不显示光标
	LCD_1602_Cmd(0X01);//清屏
}


void display(uint l,uchar *p)//;cd显示字符串
{
	uint i;
	for(i=0;i= 60)//是否到达一分钟
       {
	  fen = 0;//分数清0
	  shi = shi + 1;//小时数+1
       }
       if(shi >= 24)//是否到达24小时
       {
	  shi = 0;//小时数清0
       }
       
       time[0] = shi / 10 + 48;//更新时间字符串中的小时的十位,+48才是对应的阿斯克码
       time[1] = shi % 10 + 48;//更新时间字符串中的小时的个位
       time[3] = fen / 10 + 48;//更新时间字符串中的分钟的十位
       time[4] = fen % 10 + 48;//更新时间字符串中的分钟的个位
       time[6] = miao / 10 + 48;//更新时间字符串中的秒钟的十位
       time[7] = miao % 10 + 48;//更新时间字符串中的秒钟的个位
        
    }
    TR0 = 1;//继续计时

}

void INTERR(void) interrupt 0//利用外部中断进行判断是否有按键按下,不用查询,节省系统时间
{
	keyscan();//判断哪一个键按下
}

void start(void) interrupt 2//利用外部中断进行判断是否有按键按下,不用查询,节省系统时间
{
	turn_stop=1;
	  strcpy(state,"start   ");//strcpy字符串复制函数,把‘start’复制到state,方便显示
	  led = 0;//灯亮起
	speed=100;//防止未启动时的操作,启动时以默认的转速旋转
}

void keyscan()
{
 	unsigned char hang,lie;//行和列
	delay(5);//延时一会
	if((P3&0x03)!=0x03)//判断是否按键按下
		{
		switch(P3&0x03)//判断哪一行的按键按下
				{
				case 0x01:hang=1;break;//第二行按下
				case 0x02:hang=0;break;//第一行按下
				}
		P3=P3&0xfc;//P3的低两位拉低,来判断哪一列
		P2=P2|0xf0;//P2的高四位拉高
		while((P3&0x03)!=0x00);//P3的低两位拉低后继续
		switch(P2&0xf0)//读入P2高四位
				{
				case 0xe0:lie=3;break;//第四列
				case 0xd0:lie=2;break;//第三列
				case 0xb0:lie=1;break;//第二列
				case 0x70:lie=0;break;//第一列
				}
		P2=P2&0x0f;//拉低P2高四位
		P3=P3|0x03;//拉高P3低两位
		while((P2&0xf0)!=0x00);//P2的高四位拉低后继续
		key=tab[hang][lie];//判断哪一个键按下,把相应的值赋给全部变量key,方便进一步操作
		key_gn();//key的功能函数
		}
	else
		key=0;//默认值无按键按下

}


void key_gn()//key的功能函数
{
   uint time_temp;//时间修改得暂存值
   if(key!=0)//如果有按键按下
   {
      state[0]="\0";//lcd上的状态显示信息清零,因为\0转义符代表结束,没有找到更好的方法
      switch(key)//switch分支语句,执行按键的功能
      {
	 case '0'://第一个按下
	    strcpy(state,"reset   ");//strcpy字符串复制函数,把‘reset’复制到state,方便显示
	    speed = 100;//默认转速
	    break;
	 case '1'://第二个键按下,停止
	    strcpy(state,"stop     ");//同理
	    turn_stop = 0;//启动标志位置0
	    led = 1;//灯关闭
	    break;
	 case '2'://第三个键按下,加速
	    strcpy(state,"speed up   ");
	    if((speed >= 50))//设置的最大转速
	       speed = speed - 20;//speed是延时时间和转速成反比
	    break;
	 case '3'://第四个键按下,减速
	    strcpy(state,"slow down    ");
	 if((speed <= 150))//设置的最小速度
	       speed = speed + 20;//speed是延时时间和转速成反比
	    break;
	 case '4'://第五个键按下,反转
	    turn_flag = ~turn_flag;//转向标志位反转
	 if(turn_flag == 1)//标志位为0正转
	    strcpy(state,"left    ");
	 else//标志位为1反转
	    strcpy(state,"right  ");
	    break;
	 case '5'://第六个键按下,时间调整标志位,修改小时或分钟
	    time_flag=~time_flag;//反转
	 if(time_flag==0)//如果标志位为0,调整分钟
	    strcpy(state,"  minute   ");
	 else//如果标志位为0,调整小时
	    strcpy(state," hour   ");
	    break;
	 case '6'://第七个键按下,时间+
	    TR0 = 0;   //停止计时
	    if(time_flag==0)//判断调整小时或分钟
	    {
	       strcpy(state,"minute+  ");//显示状态
	       time_temp = (time[3]-48)*10+time[4]-48;//读取当前分钟数值
	       time_temp = time_temp + 1;//分钟+1
	       if(time_temp >= 60)//如果大于60,置0
		  time_temp = 0;
	       time[3] = time_temp / 10 + 48;//修改字符串时间
	       time[4] = time_temp % 10 + 48;
	    }
	    else
	    {
	       strcpy(state,"hour+  ");//显示状态,小时+
	       time_temp = (time[0]-48)*10+time[1]-48;//读取小时数值
	       time_temp = time_temp + 1;//小时+1
	       if(time_temp >= 24)//小时大于23就置0
		  time_temp = 0;
	       time[0] = time_temp / 10 + 48;//修改字符串时间
	       time[1] = time_temp % 10 + 48;
	    }
	    TR0 = 1;//继续计时
	    break;
	 case '7'://第八个键按下,时间-
	    TR0 = 0;//停止计时
	    if(time_flag==0)//同理判断是小时还是分钟
	    {
	       strcpy(state,"minute-   ");//同理,不在赘述
	       time_temp = (time[3]-48)*10+time[4]-48;
	       time_temp = time_temp - 1;
	       if(time_temp > 100)
		  time_temp = 59;
	       time[3] = time_temp / 10 + 48;
	       time[4] = time_temp % 10 + 48;
	    }
	    else
	    {
	       strcpy(state,"hour-   ");
	       time_temp = (time[0]-48)*10+time[1]-48;
	       time_temp = time_temp - 1;
	       if(time_temp > 100)
		  time_temp = 23;
	       time[0] = time_temp / 10 + 48;
	       time[1] = time_temp % 10 + 48;
	    }
	    TR0 = 1;
	    break;
	    
      }
   }
}

void moto()//步进电机驱动
{
   uchar code fan[] = {0x09,0x08,0x0c,0x04,0x06,0x02,0x03,0x01};//正转
   uchar code zheng[] = {0x01,0x03,0x02,0x06,0x04,0x0c,0x08,0x09};//反转
   uchar i;
   if(turn_stop==1)//如果是启动状态
   {
      if (turn_flag==0)//判断正反转
      {
	 for(i=0;i<8;i++)
	 {
	    P0 = zheng[i];//正转
	    delay(speed);	//延时,调整转速
	 }
      }
      else
      {	
	 for(i=0;i<8;i++)
	 {
	    P0 = fan[i];//反转
	       delay(speed);//延时,调整转速
	 }
      }
   }
}

void main()//主函数部分
{

	TMOD=0x01;//定时器0,模式1
	TL0=0xB0;    //赋初值
	TH0=0x3C;
	
	ET0=1;//允许t0中断
	TR0=1;//开始计时
	IT0=1;  //IT0=1为下边沿触发
	EX0=1;  //打开外部中断0
		IT1=1;  //IT0=1为下边沿触发
	EX1=1;  //打开外部中断0

   EA=1;//打开总中断
   LCD_1602_init();//lcd初始化

   led = 1;//默认灯关闭
   key=0;//键为0
   P2=P2&0x0f;//拉低P2高四位
   while(1)
   {
      moto();//电机驱动
		 
      LCD_1602_Cmd(0X80);	//第一行显示时间 
     display(8,time);//显示时间
    LCD_1602_Cmd(0X80+0X40);	 //第二行显示状态
     display(9,state);//显示状态
   }	
}

 

你可能感兴趣的:(proteus,单片机,嵌入式,步进电机,LCD)