实现的功能如下:
1)实现电机正转和反转;
2)实现一定范围内的电机转速变化;
3)通过2x4键盘控制电机的启动、停止、正反转、加减速、时间的修改和转速设置的清零,并配合LCD屏幕显示系统运行状态;
4)显示电机运行当前状态,包括(启动、停止、正转、反转、加速、减速、修改小时、修改分钟等);
5)LCD显示北京时间,可调整小时和分钟(秒钟不可修改),可以加可以减;
6)配有电机运行指示灯,电机运转时,指示灯亮起,电机停止时,指示灯灭掉,用来指示电机是否运行;
7)配有复位按键,运行出错可手动复位;
输入模块
1)按键模块
按键模块用于接收外界的输入,通过按下按键来控制电机、时间等,用于人机交互,起到主控制台的作用,键盘的查询方式选择的是中断查询,可以减小时钟的利用率;
2)复位按键
复位按键用于防止系统“跑飞”,工作不正常,或者出现事故要紧急停止,可以通过复位键来复位,使单片机重新从一个已知状态运行;
3)启动模块
启动按键与其他按键不同被单独列了出来,可以防止误启动,利用外部中断来进行启动,实时性较好,且启动后会以默认转速运行,防止启动之前的设定产生干扰,防止危险发生;
输出模块
1)LCD屏幕显示
采用液晶显示屏LM016L,用来显示时间和状态,当按键按下后,在液晶显示屏上会显示按键按下后的状态,和执行的操作,更加直观的进行人机交互,可以让操作者了解自己的操作是否被执行,系统此时运行是否正确;
2)电机模块
电机模块由电机驱动模块ULN2003A和步进电机MOTOR-STEPPER组成,由于单片机引脚的驱动电流很小,所以需要相应的电机驱动模块来放大驱动电流,放大后的电流用来驱动六线制步进电机;
3)电机运行指示灯
当电机运行时,指示灯亮起,用来提醒用户电机正在运行,也方便用来查错,当指示灯亮起,但是电机未运行时,可考虑步进电机是否损坏;
Proteus绘制硬件电路原理图
此次采用的是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口按顺序,不间断的输出控制信号,即可达到电机驱动的作用。
电机启动:
#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);//显示状态
}
}