一、设计目标
使用三个51单片机,实现多机通信。
二、实现功能
1号单片机可遥控2、3号单片机、超声波测距等;
1号单片机可集中显示步进电机的转速、转向、启停状态以及超声波测距结果。
三、硬件原理
51单片机,超声波测距模块,四相双极步进电机,导线,静态数码管,动态数码管,独立按键,74HC245芯片,74HC138芯片。
图1
图2
图3
图4
图5
图6
图7
图8
图9
图10
图11
图12
图1为超声波测距模块的内部电路图。
图2为超声波测距的输出和接收声波的时序逻辑图。
图3、图5为动态数码管的控制。使用51单片机的P0_0、P0_1、P0_2端连接74HC138芯片的A、B、C引脚,从而控制哪一个数码管被点亮。
图6为静态数码管的内部电路图;
图7为控制步进电机的部分的内部电路图;
图4为独立按键设置;
图8为四线双极性步进电机内部原理图;
图9为步进电机转动的原理图:
①A加正极,A-加负极,B加负极,B-加负极(电机状态,N极朝上)。
②A加负极,A-加负极,B加正极,B-加负极(电机状态,N极朝右,旋转90度)。
③A加负极,A-加正极,B加负极,B-加负极(电机状态,N极朝下)。
④A加负极,A-加负极,B加负极,B-加正极(电机状态,N极朝左)。
只要依次给相应引脚相应的电平就可以使得电机转动,转动的最小角度为90度。为了实现更加小角度的供电,本次设计中的供电顺序:A、AB、B、BA-、A-、A-B-、B-、B-A,转动的最小角度为45°。
图10、图11、图12为多机通信工作原理。
在多机通信中,主机必须要能对各个从机进行识别,在51系列单片机中可以通过SCON寄存器的SM2位来实现。当串口以方式2或方式3发送数据时,每一帧信息都是11位,第9位是数据可编程位,通过给TB8置1或置0来区别地址帧和数据帧,当该位为1时,发送地址帧;该位为0时,发送数据帧。
四、程序流程图
五、代码说明
主机的代码由主程序、握手子程序、判断按键抬起子程序、延时程序、发送数字程序、同步电机程序、校验速度程序、清零程序、测距程序、显示中断程序组成。
1号从机代码由主程序、延时程序、通信中断程序、调速程序、速度校验程序、显示程序组成。
2号从机代码由主程序、延时程序、通信中断程序、测距程序组成。
1.主函数
①主机
/**********************主函数*************************/
void main()
{
zjcsh();
key();
lian();
return 0;
}
②从机1
/*************************主函数**********************************************************/
void main()
{
csh();
Int0Init();//声明加速的中断
Int1Init();//声明减速的中断
while(1)
{
mode(key());//先按键扫描,后选择模式
}
}
③从机2
/*************************主函数*******************************/
void main()
{
unsigned int time = 0;
csh();
while(1)
{
time = RunOnce();//传感器接收到高电平的时间
distance = GetDistance(time);
xianshi(distance);
}
}
2.子函数1
主机的延时函数。
/**********************延时函数*************************/
void delay(unsigned int t)//调节给电机供电的时间,从而调速
{
while(t--);
3.子函数2
主机的动态数码管显示函数。用于动态数码管显示超声波测距的结果。
}/*********************动态数码管显示*************************/
void shumaguan(unsigned char wei,num)//第几位(wei)显示
{
switch(wei)
{
case 1:P3_5=0;P3_6=0;P3_7=0;break;
case 2:P3_5=0;P3_6=0;P3_7=1;break;
case 3:P3_5=0;P3_6=1;P3_7=0;break;
case 4:P3_5=0;P3_6=1;P3_7=1;break;
case 5:P3_5=1;P3_6=0;P3_7=0;break;
case 6:P3_5=1;P3_6=0;P3_7=1;break;
case 7:P3_5=1;P3_6=1;P3_7=0;break;
case 8:P3_5=1;P3_6=1;P3_7=1;break;
}
P1=shuzi[num];
delay(1);
}
4.子函数3
主机的测距显示函数。用于控制动态数码管显示超声波测距的结果。
/**********************测距显示*************************/
void xianshi(int d)
{
int k=8,m;
while(d!=0)
{
m=(d%10);//在数码管上显示个位,从后往前显示
shumaguan(k,m);
k--;//数码管向前移位
d=d/10;
}
}
5.子函数4
主机的初始化函数。定义工作方式3,定义SM2的初态。定义波特率9600。
/**********************主机初始化*************************/
void zjcsh()
{
TMOD=0x20;
SCON=0xd0;
TH1=TL1=0xfd;
PCON=0X00;;
TR1=1;
ES=1;
EA=1;
}
6.子函数5
主机与从机建立联系的函数,并且发地址用于呼叫。
/**********************建立联系——发地址*************************/
void lian(date)
{
TB8=1;//发送的地址
if(date==1&&2&&3&&4&&5&&6)
{
SBUF=0xA1;//从机1 步进电机
}
else
{
SBUF=0xA2;//从机2 超声波测距
}
while(!TI);
TI=0;
}
7.子函数6
主机的中断接受及发数据函数。判断收到的从机反馈信息,判断是哪个从机发来的,并给两个从机分别发送不同的指令信息用于控制。若收到的是从机反馈的数据,则分别通过不同方式进行显示。动态数码管显示超声波测距结果,静态数码管显示步进电机的转速。
/**********************中断接收——发数据*************************/
void zd()interrupt 4
{
RI=0;
addr=SBUF;
if(RB8==1)//判断是否为地址
{
if(addr==0xA1)
{
TB8=0;//发送的是数据
SBUF=date;//给从机1发数据
while(!TI);
TI=0;
}
if(addr==0xA2)
{
TB8=0;//发送的是数据
SBUF=date;//给从机2发数据
while(!TI);
TI=0;
}
}
else
{
if(RB8==0)//判断是否为数据
{
dat=SBUF;
if(addr==0xA1)
{
P0=smg[dat];//显示电机速度
}
else
{
xianshi(dat);//显示测距结果
}
}
}
}
8.子函数7
主机的按键判断函数。用于生成对步进电机的控制指令。
/**********************按键判断*************************/
void key()
{
if(P2_0==0) date=1;//顺时针
if(P2_1==0) date=2;//逆时针
if(P2_2==0) date=3;//启动
if(P2_3==0) date=4;//停机
if(P2_4==0) date=5;//加速
if(P2_5==0) date=6;//减速
if(P2_6==0) date=7;//超声波测距
if(P2_7==0) date=8;
}
9.子函数8
从机1的延时函数。
/***************************延时函数*******************************/
void delay(unsigned int t)//调节给电机供电的时间,从而调速
{
while(t--);
}
10.子函数9
从机1的按键扫描函数。用于在从机端控制步进电机。
/***************************按键扫描***************************************/
int key()
{
if(P0_0==0) i=1;//顺时针
if(P0_1==0) i=2;//逆时针
if(P0_2==0) i=3;//启动/回到初速度
if(P0_3==0) i=4;//停机
return i;
}
11.子函数10
从机1的模式判断函数。用于控制步进电机。
/****************************模式判断**************************************/
void mode(int i)//由按键扫描返回值判断
{
switch(i)
{
/****************模式1 正转****************/
case 1:
while(1)
{
for(n=0;n<8;n++)//循环供电
{
P2=SSZ[n];//供电
delay(V);//延时
P1=~smg[D];//显示速度档
}
break;
}
break;
/****************模式2 反转****************/
case 2:
while(1)
{
for(n=0;n<8;n++)//循环供电
{
P2=NSZ[n];//供电
delay(V);//延时
P1=~smg[D];//显示速度档
}
break;
}
break;
/****************启动/重启****************/
case 3:
V=500;//启动的速度/初速度
D=1;//速度1档,自动进入模式1
/****************停机****************/
case 4:
P2=0X00;//不给电机供电
P1=~smg[0];//数码管显示0 共阳极 取反供电
break;
}
}
12.子函数11
从机1的中断初始化函数。
/*************************中断初始化*****************************/
void Int0Init()//加速
{
IT0=1;//下降沿有效
EX0=1;//IT0中断允许
EA=1;//总开关打开
}
void Int1Init()//减速
{
IT1=1;
EX1=1;
EA=1;
}
13.子函数12
从机1的调速函数。
/*************************调速函数****************************/
void Int0() interrupt 0 //加速
{
if(P3_2==0)//判断加速键是否被按下
{
delay(2000);//消抖
if(P3_2==0)//消抖后再次判断
{
V=V-50;//延时时间减少,所以速度增加
D=D+1;//速度档加1
}
}
}
void Int1() interrupt 2 //减速
{
if(P3_3==0)//判断减速键是否被按下
{
delay(2000);//消抖
if(P3_3==0)//消抖后再次判断
{
V=V+50;//延时时间增加,所以速度减少
D=D-1;//速度档减1
}
}
}
14.子函数13
从机1 的通信初始化函数。
/*************************初始化*******************************/
void csh()
{
TMOD=0x20; //定时器工作方式2
REN=1;
SM0=1;
SM1=1;
SM2=1;//主机置0 从机置1
TH1=TL1=0xfd; //9600波特率
SCON=0X00;
TR1=1;//开定时
TI=0;
RI=0;
ES=1;//开串口中断
EA=1;//串口总开关
}
15.子函数14
从机1的中断收函数。
/*************************中断收*******************************/
void zd() interrupt 4
{
RI=0;
if(RB8==1)//再判断接受数据=地址
{
if(addr==SBUF)
{
SM2=0;
P2_0=0;
TB8=1;
SBUF=0XA1;
while(!TI);
TI=0;
}
}
else
{
date=SBUF;
P2_1=0;
mode(date);
TB8=1;
SBUF=D;
while(!TI);
TI=0;
SM2=1;
}
}
16.子函数15
从机2的延时函数。
/******************************************延时函数***************************************/
void delay(unsigned int xms)//延时x毫秒
{
unsigned int i,j;
for(i=xms;i>0;i--)
for(j=112;j>0;j--);
return;
}
17.子函数16
从机2的数码管显示函数。
/******************************************数码管控制函数***************************************/
unsigned char shuzi[]={0x3f/*0*/,0x06/*1*/,0x5b/*2*/,0x4f/*3*/,0x66/*4*/,0x6d/*5*/,0x7d/*6*/,0x07/*7*/,0x7f/*8*/,0x6f/*9*/};//显示的数字(num)的数组
void shumaguan(unsigned char wei,num)//第几位(wei)显示
{
switch(wei)
{
case 1:P0_2=0;P0_1=0;P0_0=0;break;
case 2:P0_2=0;P0_1=0;P0_0=1;break;
case 3:P0_2=0;P0_1=1;P0_0=0;break;
case 4:P0_2=0;P0_1=1;P0_0=1;break;
case 5:P0_2=1;P0_1=0;P0_0=0;break;
case 6:P0_2=1;P0_1=0;P0_0=1;break;
case 7:P0_2=1;P0_1=1;P0_0=0;break;
case 8:P0_2=1;P0_1=1;P0_0=1;break;
}
P3=shuzi[num];
delay(1);
}
18.子函数17
从机2的定时器函数。用于产生方波。
/**********************************定时器函数 延时10us*************************************/
void Delay10us()
{
TMOD |= 0x01;//16位定时器/计数器
TH0 = 0xFF;//赋初值
TL0 = 0xF6;//赋初值
TR0 = 1;//启动
while(!TF0);//溢出
TF0 = 0;//清溢出
}
19.子函数18
从机2的算距离函数。
/******************************************算距离函数***************************************/
float GetDistance(unsigned int time)
{
float distance;
distance = (float)time * 0.017;//cm 距离=高电平时间×声速/2 0.017cm/us
return distance;//将距离返回主函数
}
20.子函数19
从机2的测时间函数。
/******************************************测时间函数***************************************/
unsigned int RunOnce()
{
unsigned int time;
/******************发送10us高电平信号*************/
Trig = 0;
Trig = 1;
Delay10us();
Trig = 0;
/**************等待高电平信号接收*****************/
while(!Echo);
/*********T0清0重新计数(高电平持续时间)*********/
TH0 = 0;
TL0 = 0;
TR0 = 1;
/*********等待高电平信号接收结束******************/
while(Echo);
/*******************关闭T0计数********************/
TR0 = 0;
/**********高电平时间赋值,单位us*****************/
time = TH0*256 + TL0;
TH0 = 0;
TL0 = 0;
return time;
}
21.子函数20
从机2的动态数码管显示结果函数。用于处理数据后交给数码管函数显示结果。
/**************************************动态数码管显示结果***********************************/
void xianshi(int d)
{
int k=8,m;
while(d!=0)
{
m=(d%10);//在数码管上显示个位,从后往前显示
shumaguan(k,m);
k--;//数码管向前移位
d=d/10;
}
}
22.子函数21
从机2的通信初始化函数。
/*************************初始化*******************************/
void csh()
{
TMOD=0x20; //定时器工作方式2
REN=1;
SM0=1;
SM1=1;
SM2=1;//主机置0 从机置1
TH1=TL1=0xfd; //9600波特率
SCON=0X00;
TR1=1;//开定时
TI=0;
RI=0;
ES=1;//开串口中断
EA=1;//串口总开关
}
23.子函数22
从机2的中断接收函数。
/*************************中断收*******************************/
void zd() interrupt 4
{
RI=0;
if(RB8==1)//再判断接受数据=地址
{
if(addr==SBUF)
{
SM2=0;
P2_0=0;
TB8=1;
SBUF=0XA2;
while(!TI);
TI=0;
}
}
else
{
date=SBUF;
P2_1=0;
TB8=0;
SBUF=distance;
while(!TI);
TI=0;
SM2=1;
}
}
六、源代码
1.主机
#include <REGX51.H>
int addr;
int date,dat;
int num;
int D=1;//V在延时函数中,用于调速;D用来控制数码管显示的数字
smg[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07, 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//用于显示档速 0-f
unsigned char shuzi[]={0x3f/*0*/,0x06/*1*/,0x5b/*2*/,0x4f/*3*/,0x66/*4*/,0x6d/*5*/,0x7d/*6*/,0x07/*7*/,0x7f/*8*/,0x6f/*9*/};//显示的数字(num)的数组
/**********************延时函数*************************/
void delay(unsigned int t)//调节给电机供电的时间,从而调速
{
while(t--);
}/*********************动态数码管显示*************************/
void shumaguan(unsigned char wei,num)//第几位(wei)显示
{
switch(wei)
{
case 1:P3_5=0;P3_6=0;P3_7=0;break;
case 2:P3_5=0;P3_6=0;P3_7=1;break;
case 3:P3_5=0;P3_6=1;P3_7=0;break;
case 4:P3_5=0;P3_6=1;P3_7=1;break;
case 5:P3_5=1;P3_6=0;P3_7=0;break;
case 6:P3_5=1;P3_6=0;P3_7=1;break;
case 7:P3_5=1;P3_6=1;P3_7=0;break;
case 8:P3_5=1;P3_6=1;P3_7=1;break;
}
P1=shuzi[num];
delay(1);
}
/**********************测距显示*************************/
void xianshi(int d)
{
int k=8,m;
while(d!=0)
{
m=(d%10);//在数码管上显示个位,从后往前显示
shumaguan(k,m);
k--;//数码管向前移位
d=d/10;
}
}
/**********************主机初始化*************************/
void zjcsh()
{
TMOD=0x20;
SCON=0xd0;
TH1=TL1=0xfd;
PCON=0X00;;
TR1=1;
ES=1;
EA=1;
}
/**********************建立联系——发地址*************************/
void lian(date)
{
TB8=1;//发送的地址
if(date==1&&2&&3&&4&&5&&6)
{
SBUF=0xA1;//从机1 步进电机
}
else
{
SBUF=0xA2;//从机2 超声波测距
}
while(!TI);
TI=0;
}
/**********************中断接收——发数据*************************/
void zd()interrupt 4
{
RI=0;
addr=SBUF;
if(RB8==1)//判断是否为地址
{
if(addr==0xA1)
{
TB8=0;//发送的是数据
SBUF=date;//给从机1发数据
while(!TI);
TI=0;
}
if(addr==0xA2)
{
TB8=0;//发送的是数据
SBUF=date;//给从机2发数据
while(!TI);
TI=0;
}
}
else
{
if(RB8==0)//判断是否为数据
{
dat=SBUF;
if(addr==0xA1)
{
P0=smg[dat];//显示电机速度
}
else
{
xianshi(dat);//显示测距结果
}
}
}
}
/**********************按键判断*************************/
void key()
{
if(P2_0==0) date=1;//顺时针
if(P2_1==0) date=2;//逆时针
if(P2_2==0) date=3;//启动
if(P2_3==0) date=4;//停机
if(P2_4==0) date=5;//加速
if(P2_5==0) date=6;//减速
if(P2_6==0) date=7;//超声波测距
if(P2_7==0) date=8;
}
/**********************主函数*************************/
void main()
{
zjcsh();
key();
lian();
return 0;
}
2.从机一 (步进电机)
#include <REGX51.H>
int addr=0xA1;
int date;
unsigned char SSZ[] = {0x08,0x0a,0x02,0x06,0x04,0x05,0x01,0x09};//顺时针数组
unsigned char NSZ[] = {0x09,0x01,0x05,0x04,0x06,0x02,0x0a,0x08};//逆时针数组
smg[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07, 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//用于显示档速 0-f
int V=500,D=1;//V在延时函数中,用于调速;D用来控制数码管显示的数字
int i=4;//先使电机处于停机状态
int n=0;//用于循环给电机通电
/*************************步进电机******************************************************************************/
/***************************延时函数*******************************/
void delay(unsigned int t)//调节给电机供电的时间,从而调速
{
while(t--);
}
/***************************按键扫描***************************************/
int key()
{
if(P0_0==0) i=1;//顺时针
if(P0_1==0) i=2;//逆时针
if(P0_2==0) i=3;//启动/回到初速度
if(P0_3==0) i=4;//停机
return i;
}
/****************************模式判断**************************************/
void mode(int i)//由按键扫描返回值判断
{
switch(i)
{
/****************模式1 正转****************/
case 1:
while(1)
{
for(n=0;n<8;n++)//循环供电
{
P2=SSZ[n];//供电
delay(V);//延时
P1=~smg[D];//显示速度档
}
break;
}
break;
/****************模式2 反转****************/
case 2:
while(1)
{
for(n=0;n<8;n++)//循环供电
{
P2=NSZ[n];//供电
delay(V);//延时
P1=~smg[D];//显示速度档
}
break;
}
break;
/****************启动/重启****************/
case 3:
V=500;//启动的速度/初速度
D=1;//速度1档,自动进入模式1
/****************停机****************/
case 4:
P2=0X00;//不给电机供电
P1=~smg[0];//数码管显示0 共阳极 取反供电
break;
}
}
/*************************中断初始化*****************************/
void Int0Init()//加速
{
IT0=1;//下降沿有效
EX0=1;//IT0中断允许
EA=1;//总开关打开
}
void Int1Init()//减速
{
IT1=1;
EX1=1;
EA=1;
}
/*************************调速函数****************************/
void Int0() interrupt 0 //加速
{
if(P3_2==0)//判断加速键是否被按下
{
delay(2000);//消抖
if(P3_2==0)//消抖后再次判断
{
V=V-50;//延时时间减少,所以速度增加
D=D+1;//速度档加1
}
}
}
void Int1() interrupt 2 //减速
{
if(P3_3==0)//判断减速键是否被按下
{
delay(2000);//消抖
if(P3_3==0)//消抖后再次判断
{
V=V+50;//延时时间增加,所以速度减少
D=D-1;//速度档减1
}
}
}
/*************************初始化*******************************/
void csh()
{
TMOD=0x20; //定时器工作方式2
REN=1;
SM0=1;
SM1=1;
SM2=1;//主机置0 从机置1
TH1=TL1=0xfd; //9600波特率
SCON=0X00;
TR1=1;//开定时
TI=0;
RI=0;
ES=1;//开串口中断
EA=1;//串口总开关
}
/*************************中断收*******************************/
void zd() interrupt 4
{
RI=0;
if(RB8==1)//再判断接受数据=地址
{
if(addr==SBUF)
{
SM2=0;
P2_0=0;
TB8=1;
SBUF=0XA1;
while(!TI);
TI=0;
}
}
else
{
date=SBUF;
P2_1=0;
mode(date);
TB8=1;
SBUF=D;
while(!TI);
TI=0;
SM2=1;
}
}
/*************************主函数**********************************************************/
void main()
{
csh();
Int0Init();//声明加速的中断
Int1Init();//声明减速的中断
while(1)
{
mode(key());//先按键扫描,后选择模式
}
}
3.从机二 (超声波测距)
#include <REGX51.H>
int addr=0xA2;
int date;
float distance;
sbit Trig = P1^0;
sbit Echo = P1^1;
/******************************************延时函数***************************************/
void delay(unsigned int xms)//延时x毫秒
{
unsigned int i,j;
for(i=xms;i>0;i--)
for(j=112;j>0;j--);
return;
}
/******************************************数码管控制函数***************************************/
unsigned char shuzi[]={0x3f/*0*/,0x06/*1*/,0x5b/*2*/,0x4f/*3*/,0x66/*4*/,0x6d/*5*/,0x7d/*6*/,0x07/*7*/,0x7f/*8*/,0x6f/*9*/};//显示的数字(num)的数组
void shumaguan(unsigned char wei,num)//第几位(wei)显示
{
switch(wei)
{
case 1:P0_2=0;P0_1=0;P0_0=0;break;
case 2:P0_2=0;P0_1=0;P0_0=1;break;
case 3:P0_2=0;P0_1=1;P0_0=0;break;
case 4:P0_2=0;P0_1=1;P0_0=1;break;
case 5:P0_2=1;P0_1=0;P0_0=0;break;
case 6:P0_2=1;P0_1=0;P0_0=1;break;
case 7:P0_2=1;P0_1=1;P0_0=0;break;
case 8:P0_2=1;P0_1=1;P0_0=1;break;
}
P3=shuzi[num];
delay(1);
}
/**********************************定时器函数 延时10us*************************************/
void Delay10us()
{
TMOD |= 0x01;//16位定时器/计数器
TH0 = 0xFF;//赋初值
TL0 = 0xF6;//赋初值
TR0 = 1;//启动
while(!TF0);//溢出
TF0 = 0;//清溢出
}
/******************************************算距离函数***************************************/
float GetDistance(unsigned int time)
{
float distance;
distance = (float)time * 0.017;//cm 距离=高电平时间×声速/2 0.017cm/us
return distance;//将距离返回主函数
}
/******************************************测时间函数***************************************/
unsigned int RunOnce()
{
unsigned int time;
/******************发送10us高电平信号*************/
Trig = 0;
Trig = 1;
Delay10us();
Trig = 0;
/**************等待高电平信号接收*****************/
while(!Echo);
/*********T0清0重新计数(高电平持续时间)*********/
TH0 = 0;
TL0 = 0;
TR0 = 1;
/*********等待高电平信号接收结束******************/
while(Echo);
/*******************关闭T0计数********************/
TR0 = 0;
/**********高电平时间赋值,单位us*****************/
time = TH0*256 + TL0;
TH0 = 0;
TL0 = 0;
return time;
}
/**************************************动态数码管显示结果***********************************/
void xianshi(int d)
{
int k=8,m;
while(d!=0)
{
m=(d%10);//在数码管上显示个位,从后往前显示
shumaguan(k,m);
k--;//数码管向前移位
d=d/10;
}
}
/*************************初始化*******************************/
void csh()
{
TMOD=0x20; //定时器工作方式2
REN=1;
SM0=1;
SM1=1;
SM2=1;//主机置0 从机置1
TH1=TL1=0xfd; //9600波特率
SCON=0X00;
TR1=1;//开定时
TI=0;
RI=0;
ES=1;//开串口中断
EA=1;//串口总开关
}
/*************************中断收*******************************/
void zd() interrupt 4
{
RI=0;
if(RB8==1)//再判断接受数据=地址
{
if(addr==SBUF)
{
SM2=0;
P2_0=0;
TB8=1;
SBUF=0XA2;
while(!TI);
TI=0;
}
}
else
{
date=SBUF;
P2_1=0;
TB8=0;
SBUF=distance;
while(!TI);
TI=0;
SM2=1;
}
}
/*************************主函数*******************************/
void main()
{
unsigned int time = 0;
csh();
while(1)
{
time = RunOnce();//传感器接收到高电平的时间
distance = GetDistance(time);
xianshi(distance);
}
}