基于51单片机的智能数码管百叶窗
设计要求:本设计以MCS-51系列单片机为核心,采用常用电子器件设计,一个电源开关,用一台直流电机控制百叶窗叶片的旋转(正转/反转),用一个光敏电阻传感器测量室内光强度,并用两位数码管显示测量结果,设置三个按键:手动/自动、手动正转和手动反转,用一个发光二极管显示手动/自动状态,自动状态时二极管亮。设置两个极限位置保护行程开关,用于保护百叶窗叶片:当正转到极限位置压下行程开关时,电机停止正转,但还可以反转;当反转到极限位置压下行程开关时,电机停止反转,但还可以正转。按键输入采用中断方式,按键中断请求信号接INT0。单片机根据设定光强S1和S2和实测光强P来控制电机M的动作,分别使电机正转、反转或者停转来达到控制光强弱的功能。
使用的设备:光敏电阻传感器,直流电机,两位数码管
实现功能:自动模式和手动模式
实现方式:采用中断方式
系统框图:
程序流程图:
目录
I2C总线的设定
代码如下:
#include <REGX52.H>
#include "I2C.h"
void I2CStart() //I2C开始
{
I2C_SCL=1;
I2C_SDA=1;
I2C_SDA=0;
I2C_SCL=0;
}
void I2CStop() //I2C结束
{
I2C_SCL=0;
I2C_SDA=0;
I2C_SCL=1;
I2C_SDA=1;
}
bit I2CWrite(unsigned char dat) //I2C写操作,dat-代写数值,ack-返回应答值
{
bit ack; //用来暂存应答值
unsigned char mask; //用来暂存数据
for(mask=0x80;mask!=0;mask>>=1)
{
if((mask&dat))
I2C_SDA=1;
else
I2C_SDA=0;
I2C_SCL=1; //拉高SCL
I2C_SCL=0; //再拉低SCL,完成一个周期
}
I2C_SDA=1; //主机释放SDA
I2C_SCL=1;
ack=I2C_SDA; //读取SDA值,即为应答值
I2C_SCL=0;
return (~ack); //因为原本的I2C是0表示应答,1表示非应答,所以这里取反
}
unsigned char I2CReadNAK() //I2C总线读操作,发送非应答信号并继续读下去,返回值-读到的字节
{
unsigned char mask;
unsigned char dat; //暂存数据
I2C_SDA=1; //确保主机释放SDA
for(mask=0x80;mask!=0;mask>>=1)
{
I2C_SCL=1;
if(I2C_SDA)
dat|=mask;
else
dat&=~mask;
I2C_SCL=0;
}
I2C_SDA=1; //拉高SDA,发送非应答信号
I2C_SCL=1; //拉高SCL
I2C_SCL=0; //再拉低SCL完成非应答
return dat; //返回数据
}
unsigned char I2CReadACK() //I2C总线读操作,发送应答信号并不再读下去,返回值-读到的字节
{
unsigned char mask;
unsigned dat; //暂存数据
I2C_SDA=1; //确保主机释放SDA
for(mask=0x80;mask!=0;mask>>=1)
{
I2C_SCL=1;
if(I2C_SDA)
dat|=mask;
else
dat&=~mask;
I2C_SCL=0;
}
I2C_SDA=0; //拉高SDA,发送应答信号
I2C_SCL=1; //拉高SCL
I2C_SCL=0; //再拉低SCL完成应答
return dat; //返回数据
}
ADC数据转换:通过PCF8591实现
寻址
i2c总线系统中的每个PCF8591设备通过发送一个有效的地址来寻址。
地址由固定部分和可编程部分组成。
可编程部分必须根据地址引脚AO、A1和A2进行设置。
地址总是必须作为I2c总线协议中的开始条件之后的第一个字节被发送。
地址字节的最后一位是读写位,它设定了接下来数据传输的方向。
控制字节
发送到PCF8591设备的第二个字节将存储在其控制寄存器中,并需要控制设备的功能。
1、最高位和第三位默认为0
2、第六位:AD使能位,为1开启,为0关闭
3、第四位和第五位:单端、差分选择位
4、第二位:自动增量位(一般位0)
5、读取选择位:比如读取AIN0,或者读取AIN2
(注意:最低位为第0位,最高位为第七位)
proteus仿真中的接线法
1、PCF8591 是一个单片集成、单独供电、8-bit CMOS数据获取器件。
2、AIN0、AIN1、AIN2、AIN3为模拟输入端
3、AOUT为模拟输出端
4、EXT为低电平时使用内部时钟,为高电平时使用外部时钟
5、A0、A1、A2为地址引脚
6、OCS振荡器、VREF基准电压、AGND接地
#include <REGX52.H>
#include "I2C.h"
//通过I2C读取AD值
unsigned char GetADCValue(unsigned char chn)
{
unsigned char val;
I2CStart();
if(!I2CWrite(0x48<<1)) //判断最后一位是读或写
{
I2CStop();
return 0;
}
I2CWrite(0x40|chn); //AD使能位,为1开启,为0关闭
I2CStart();
I2CWrite(0x48<<1 | 0x01); //读写位置1,即为读出数据
I2CReadACK();
val=I2CReadNAK();
I2CStop();
return val;
}
电机:通过L298N实现
通过将引脚接为高电平或低电平控制接通还是关断,ENA使能左侧电机,ENB使能右侧电机。
IN1和IN2为一组,对应OutA(输出A)
IN3和IN4为一组,对应OutB(输出B)
引脚
proteus仿真中的接线法
两位数码管
SMG1控制第一位数码管,SMG2控制第二位数码管
RP与段码表配合,可以显示想要输出的数字
代码
#include <REGX52.H>
#include "Display.h"
//数码管的动态扫描
unsigned char Number[10]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};//共阳数码管显示当前值0-9
void Display(unsigned char num)
{
static unsigned char t;
SMG1=0; //控制第一位数码管暗灭
SMG2=0; //控制第二位数码管暗灭
if(t==0)
{
t=1;
PR=Number[num/10]; //显示十位
SMG1=1; //点亮第一位数码管
SMG2=0;
}
else
{
t=0;
PR=Number[num%10]; //显示个位
SMG1=0;
SMG2=1; //点亮第二位数码管
}
}
proteus仿真中的接线法
注:由于I0口的输出有效,要加“上拉电阻”拉高电平才能驱动。
proteus仿真中的定时器外部中断
XTAL1:反向振荡放大器的输入。
XTAL2:来自反向振荡器的输出。
RST:接复位按钮。
PSEN:外部程序存储器的选通信号。
EA/VPP:当/EA保持低电平时,则在此期间读取外部程序存储器,不管是否有内部程序存储器。当/EA端保持高电平时,此期间读取内部程序存储器。
ALE/PROG:在编程期间,此引脚用于输入编程脉冲。在平时,ALE端以不变的频率周期输出正脉冲信号,
定时器和外部中断
代码
#include <REGX52.H>
//定时器初始化
void Time1_init()
{
TMOD |= 0x10; //使用模式1,16位定时器
TH1 = (65536-10000)/256; //定时器装入初始值10ms
TL1 = (65536-10000)%256;
EA = 1; //总中断
ET1 = 1;//定时器1初始化
TR1 = 1;
}
//外部中断初始化
void EX_Init()
{
EA=1;
IT0=1;
EX0=1;
}
主函数
#include <REGX52.H>
#include "I2C.h"
#include "Init.h"
#include "ADC.h"
#include "Display.h"
bit flag=0;//模式选择
sbit LED=P1^7; //LED指示灯引脚
sbit IN1=P1^0; //电机
sbit IN2=P1^1;
sbit K2=P3^3; //按键控制正反转
sbit K3=P3^4;
sbit xianwei1=P3^6; //限位开关
sbit xianwei2=P3^7;
unsigned char Light_val; //光照(电阻)强度
void main()
{
Time1_init(); //定时器初始化
EX_Init(); //中断初始化
while(1)
{
Light_val=(char)(GetADCValue(0)/3); //获取ADC数值
if(flag==0) //自动模式
{
LED=0; //灯亮
xianwei1=1;
xianwei2=1;
if((xianwei1==1)&&(Light_val<30)) //如果光照强度小于30并且限位开关1没有关闭
{IN1=1;IN2=0;} //正转
else if((xianwei2==1)&&(Light_val>50)) //如果光照强度大于50并且限位开关2没有关闭
{IN1=0;IN2=1;} //反转
else
{IN1=1;IN2=1;} //刹车
}
if(flag==1) //手动模式
{
K2=1;
K3=1;
xianwei1=1;
xianwei2=1;
if((K2==0)&&(xianwei1==1)) //如果摁下K2并且限位开关1没有关闭
{IN1=1;IN2=0;while(!K2);} //正转,并且摁下K2不松手时一直循环
if((K3==0)&&(xianwei2==1)) //如果摁下K3并且限位开关2没有关闭
{IN1=0;IN2=1;while(!K3);} //反转,并且摁下K3不松手时一直循环
else
{IN1=1;IN2=1;} //刹车
}
}
}
//中断函数
void Int0() interrupt 0
{
flag=~flag; //模式变换
}
//定时器中断服务函数为计时器使用
void time1(void)interrupt 3
{
TH1=(65536-10000)/256;
TL1=(65536-10000)%256;
Display(Light_val); //数码管显示数值
}
仿真
#include <REGX52.H>
//定时器初始化
void Time1_init()
{
TMOD |= 0x10; //使用模式1,16位定时器
TH1 = (65536-10000)/256; //定时器装入初始值10ms
TL1 = (65536-10000)%256;
EA = 1; //总中断
ET1 = 1;//定时器1初始化
TR1 = 1;
}
//外部中断初始化
void EX_Init()
{
EA=1;
IT0=1;
EX0=1;
}
#include <REGX52.H>
#include "I2C.h"
#include "Init.h"
#include "ADC.h"
#include "Display.h"
bit flag=0;//模式选择
sbit LED=P1^7; //LED指示灯引脚
sbit IN1=P1^0; //电机
sbit IN2=P1^1;
sbit K2=P3^3; //按键控制正反转
sbit K3=P3^4;
sbit xianwei1=P3^6; //限位开关
sbit xianwei2=P3^7;
unsigned char Light_val; //光照(电阻)强度
void main()
{
Time1_init(); //定时器初始化
EX_Init(); //中断初始化
while(1)
{
Light_val=(char)(GetADCValue(0)/3); //获取ADC数值
if(flag==0) //自动模式
{
LED=0; //灯亮
xianwei1=1;
xianwei2=1;
if((xianwei1==1)&&(Light_val<30)) //如果光照强度小于30并且限位开关1没有关闭
{IN1=1;IN2=0;} //正转
else if((xianwei2==1)&&(Light_val>50)) //如果光照强度大于50并且限位开关2没有关闭
{IN1=0;IN2=1;} //反转
else
{IN1=1;IN2=1;} //刹车
}
if(flag==1) //手动模式
{
K2=1;
K3=1;
xianwei1=1;
xianwei2=1;
if((K2==0)&&(xianwei1==1)) //如果摁下K2并且限位开关1没有关闭
{IN1=1;IN2=0;while(!K2);} //正转,并且摁下K2不松手时一直循环
if((K3==0)&&(xianwei2==1)) //如果摁下K3并且限位开关2没有关闭
{IN1=0;IN2=1;while(!K3);} //反转,并且摁下K3不松手时一直循环
else
{IN1=1;IN2=1;} //刹车
}
}
}
//中断函数
void Int0() interrupt 0
{
flag=~flag; //模式变换
}
//定时器中断服务函数为计时器使用
void time1(void)interrupt 3
{
TH1=(65536-10000)/256;
TL1=(65536-10000)%256;
Display(Light_val); //数码管显示数值
}