#include<reg52.h>
#include<stdlib.h> //包含随机函数rand()的定义文件
#include<intrins.h> //包含_nop_函数定义的头文件
#define OP_READ 0xa1 //器件地址以及读取操作,0xa1即为1010 0001B
#define OP_WRITE 0xa0 //器件地址以及写入操作,0xa1即为1010 0000B
#define uchar unsigned char
#define uint unsigned int
sbit K5=P3^2;
sbit RS=P2^0; //寄存器选择位,将RS位定义为P2.0引脚
sbit RW=P2^1; //读写选择位,将RW位定义为P2.1引脚
sbit E =P2^2; //使能信号位,将E位定义为P2.2引脚
sbit BF=P0^7; //忙碌标志位,将BF位定义为P0.7引脚
sbit SCL=P3^4; //将串行时钟总线SCL位定义在为P3.4引脚
sbit SDA=P3^5;
uint data T0_cnt,D_cnt;
uchar data cnt_3,cnt_distance,cnt_cost,begin;
uchar data lose_power=1;
uchar data state_val,flag=0;
uchar data key_val, key_val_old; //定义新旧键值
uchar code cost_val[3]={1,2,1}; //收费标准 cost_val[0]白天 cost_val[1] 黑夜cost_val[2] 等待
uchar code digit[]={"012456789"};//定义字符数组显示数字
uchar code string1[]={"price:"};//定义字符数组显示提示信息
uchar code string2[]={"/km"};
uchar code string3[]={"total:"};
uint h,d,t;
void delay(uint i)//1ms延时基准函数
{
uchar j;
while(i--)
{
for(j=0;j<115;j++);
}
}
void delaynms(uint n)
{
delay(n);
}
void init_variant()//初始化一些变量的内容
{
cnt_3=0; //3分钟的倒计时
D_cnt=0; //脉冲的个数
cnt_distance=0;//距离的计数
cnt_cost=0;//保存总价格
}
uchar scan_key()
{
uchar i,k;
i=P1;
if(i==0xff)
{
k=255;
} //无建按下
else //有建按下
{
delay(10); //延时去抖动
if(i!=P1)
{
k=255;
}
else
{
switch (i)
{
case 0xef:k=0;
break;//P1.4按下,启动键
case 0xdf:k=1;
break;//P1.5按下,清除键
case 0xbf:k=2;
break;//P1.6按下,切换键
}
}
}
return k;
}
/*************************************
函数功能:开始数据传送
**************************************/
void start()
//开始位
{
SDA=1; //SDA初始化为高电平"1"
SCL=1; //开始数据传送时,要求SCL为高电平"1"
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
SDA=0; //SDA下降沿被认为是开始信号
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
SCL=0; //SCL为低电平时,SDA上数据才允许变化(即允许以后的数据传递)
}
/*************************************
函数功能:结束数据传送
**************************************/
void stop()
//停止位
{
SDA=0; //SDA初始化为低电平"0"
SCL=1; //结束数据传送时,要求SCL为高电平"1"
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
SDA=1; //SDA上升沿沿被认为是结束信号
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
SDA=0;
SCL=0;
}
/*************************************
函数功能:检测应答位
**************************************/
bit Ask() //检测应答
{
bit ack_bit; //储存应答位
SDA=1; //发送设备(主机)应在时线钟脉冲的高电平期间(SCL=1)
//释放SDA线,以让SDA线转由接收设备(AT24Cxx)控制
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
SCL=1; //根据上述规定,SCL应为高电平
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
ack_bit=SDA;//接受设备(AT24Cxx)向SDA送低电平,表示已经接收到一个字节
//若送高电平,表示没有接收到,传送异常 结束发送
SCL=0; //SCL为低电平时,SDA上数据才允许变化(即允许以后的数据传递)
return ack_bit;//返回AT24Cxx应答位
}
/*************************************
函数功能:从AT24Cxx读取数据
出口参数:x
**************************************/
unsigned char ReadData()//从AT24Cxx移入数据到MCU
{
unsigned char i;
unsigned char x; //储存从AT24Cxx中读出的数据
for(i=0;i<8;i++)
{
SCL=1; //SCL置为高电平
x<<=1; //将x中的各二进位向左移一位
x|=(unsigned char)SDA;//将SDA上的数据通过按位“或”运算存入x中
SCL=0; //在SCL的下降沿读出数据
}
return(x); //将读取的数据返回
}
/*************************************
函数功能:向AT24Cxx的当前地址写入数据
入口参数:y(存储待写入的数据)
**************************************/
//在调用此数据写入函数前需首先调用开始函数start(),所以SCL=0
void WriteCurrent(unsigned char y)
{
unsigned char i;
for(i=0;i<8;i++) //循环移入8个位
{
SDA=(bit)(y&0x80); //通过按位”与“运算将最高位数据传送到S
//因为传送时高位在前,低位在后
_nop_(); //等待一个机器周期
SCL=1; //在SCL的上升沿将数据写入AT24Cxx
_nop_(); //等待一个机器周期
_nop_(); //等待一个机器周期
SCL=0; //将SCL重新置为低电平,
//以在SCL线形成传送数据所需的8个脉冲
y<<=1; //将y中的各二进位向左移一位
}
}
/*************************************
函数功能:向AT24Cxx的当前地址写入数据
入口参数:add(存储指定的地址);dat(存储待写入的数据)
**************************************/
void Write(unsigned char add,unsigned char dat)
//在指定的地址addr处写入数据WriteCurrent
{
start(); //开始数据传递
WriteCurrent(OP_WRITE);//选择操作的AT24Cxx芯片,并告知要对其写入数据
Ask();
WriteCurrent(add); //写入指定地址
Ask();
WriteCurrent(dat); //向当前地址(上面指定的地址)写入数据
Ask();
stop(); //停止数据传递
delaynms(4); //1个字节的写入周期为1ms,最好延时1ms以上
}
/*************************************
函数功能:从AT24Cxx的当前地址读取数据
出口参数:x(存储读出的数据)
**************************************/
unsigned char ReadCurrent()
{
unsigned char x;
start(); //开始数据传递
WriteCurrent(OP_READ);//选择要操作的AT24Cxx芯片,并告知要读其数据
Ask();
x=ReadData(); //将读取的数据存入x
stop(); //停止数据传递
return x; //返回读取的数据
}
/*************************************
函数功能:从AT24Cxx中的指定地址读取数据
入口参数:set_addr
出口参数:x
**************************************/
unsigned char ReadSet(unsigned char set_addr)
//在指定地址读取
{
start(); //开始数据传递
WriteCurrent(OP_WRITE);//选择要操作的AT24Cxx芯片,并告知要对其写入数据
Ask();
WriteCurrent(set_addr);//写入指定地址
Ask();
return (ReadCurrent()) ; //从指定地址读取数据并返回
}
/***********************************************
函数功能:判断液晶模块的忙碌状态
返回值:result。result=1,忙碌:result=0,不忙
************************************************/
uchar BusyTest(void)
{
bit result;
RS=0; //根据规定,RS为低电平,RW为高电平时,可以读状态
RW=1;
E=1; //E=1,才允许读写
_nop_();
_nop_();
_nop_();
_nop_(); //空操作四个机器周期,给硬件反应时间
result=BF; //将忙碌标志电平赋给result
E=0; //将E恢复低电平
return result;
}
/**********************************************
函数功能:将模式设置指令或显示地址写入液晶模块
入口参数:dictate
***********************************************/
void WriteInstruction(uchar dictate)
{
while(BusyTest()==1); //如果忙就等待
RS=0; //根据规定,RS和R/W同时为低电平时,可以写入指令
RW=0;
E=0; //E置低电平(根据8-6,写指令时,E为高脉冲
//就是让E从0到1发生跳变,所以应先置"0'
_nop_();
_nop_(); //空操作两个机器周期,给硬件反应时间
P0=dictate; //将数据送入P0口,即写入指令或地址
_nop_();
_nop_();
_nop_();
_nop_(); //空操作四个机器周期,给硬件反应时间
E=1; //E置高电平
_nop_();
_nop_();
_nop_();
_nop_(); //空操作四个机器周期,给硬件反应时间
E=0; //当E由高电平跳变为低电平时,液晶模块开始执行命令
}
/**********************************************
函数功能:指定字符显示的实际地址
入口参数:x
***********************************************/
void WriteAddress(uchar x)
{
WriteInstruction(x|0x80); //显示位置的确定方法规定为”80H+地址码”
}
/**********************************************
函数功能:将数据(字符的标准ASII码)写入液晶模块
入口参数:y(为字符常量)
***********************************************/
void WriteData(uchar y)
{
while(BusyTest()==1);
RS=1; //RS为高电平,RW为低电平时,可以写入数据
RW=0;
E=0; //E置低电平(根据8-6,写指令时,E为高脉冲
//就是让E从0到1发生跳变,所以应先置"0'
P0=y; //将数据送入P0口,即将数据写入液晶模块
_nop_();
_nop_();
_nop_();
_nop_(); //空操作四个机器周期,给硬件反应时间
E=1; //E置高电平
_nop_();
_nop_();
_nop_();
_nop_(); //空操作四个机器周期,给硬件反应时间
E=0; //当E由高电平跳变为低电平时,液晶模块开始执行命令
}
/**********************************************
函数功能:对LCD的显示模式进行初始化设置
***********************************************/
void LcdInitiate(void)
{
delay(15); //延时15ms,首次写指令时应给LCD一段较长的反应时间
WriteInstruction(0x38); //显示模式设置:16x2显示,5x7点阵,8位数据接口
delay(5); //延时5ms,给硬件一点反应时间
WriteInstruction(0x38);
delay(5);
WriteInstruction(0x38); //连续三次,确保初始化成功
delay(5);
WriteInstruction(0x0C); //显示模式设置:显示开,无光标,光标不闪烁
delay(5);
WriteInstruction(0x06); //显示模式设置:光标右移,字符不移
delay(5);
WriteInstruction(0x01); //清屏指令,将以前的显示内容清除
delay(5);
}
void lcd_show()
{
uchar i;
WriteAddress(0x00);
i=0; //从字符数组的第1个元素开始显示
while(string1[i]!='\0') //只要没有显示到字符串的结束标志'0',就继续
{
WriteData(string1[i]); //将第i个字符数组元素写入LCD
i++; //指向下一个数组元素
}
h=cost_val[state_val]/10;
d=cost_val[state_val]%10;
WriteAddress(0x06);
WriteData(digit[h]); //将十位数字的字符常量写入LCD price
WriteData(digit[d]); //将个位数字的字符常量写入LCD
WriteAddress(0x08);
WriteData(0x5c); //币符号
WriteAddress(0x09);
i=0; //从字符数组的第1个元素开始显示
while(string2[i]!='\0') //只要没有显示到字符串的结束标志'0',就继续
{
WriteData(string2[i]); //将第i个字符数组元素写入LCD
i++; //指向下一个数组元素
}
WriteAddress(0x40);
i=0; //从字符数组的第1个元素开始显示
while(string3[i]!='\0') //只要没有显示到字符串的结束标志'0',就继续
{
WriteData(string3[i]); //将第i个字符数组元素写入LCD
i++; //指向下一个数组元素
}
h=cnt_distance/10;
d=cnt_distance%10;
WriteAddress(0x46); //写显示地址,将十位数字显示在第2行第8列
WriteData(digit[h]); //将十位数字的字符常量写入LCD km
WriteData(digit[d]); //将个位数字的字符常量写入LCD
WriteAddress(0x48);
i=1;
while(string2[i]!='\0') //只要没有显示到字符串的结束标志'0',就继续
{
WriteData(string2[i]); //将第i个字符数组元素写入LCD
i++; //指向下一个数组元素
}
h=cnt_cost/10; //取整运算,求得十位数字
d=cnt_cost%10; // 取余运算,求得个位数字
WriteAddress(0x4C);//写显示地址,将十位数字显示在第2行第8列
WriteData(digit[h]); //将十位数字的字符常量写入LCD 金额
WriteData(digit[d]); //将个位数字的字符常量写入LCD
WriteAddress(0x4E);
WriteData(0x5C);
}
void Time0(void) interrupt 1 using 0 //"interrupt"声明函数为中断服务函数
//其后的1为定时器T0的中断编号;0表示使用第0组工作寄存器
{
T0_cnt++;
if(T0_cnt>40) //如果计数>3999,计时1S
{
T0_cnt=0;
if(cnt_3<180)
{
cnt_3++;
}
else //超过180S,途中等待计价
{
cnt_3=0;
cnt_cost=cnt_cost+cost_val[2];
}
}
TH0=0x4C; //定时器T0的高8位重新赋值
TL0=0x00; //定时器T0的高8位重新赋值
}
void init1() interrupt 0 //T1中断
{
if(K5==0)
{
delay(10);
if(TR0==1&&flag==1) //每来一个脉冲,中断一次
{
cnt_3=0; //30S的计时清零
if(D_cnt<543)
{
D_cnt++;
}
else //计数543次,每次1.88米,表示一公里
{
D_cnt=0;
begin++;
if(begin>2)
{
cnt_distance=cnt_distance+1;
cnt_cost=cnt_cost+cost_val[state_val];
}
}
}
while(!K5);
}
}
void main()
{
LcdInitiate();
EX0=1; //允许使用外中断
IT0=1; //选择负跳变来触发外中断
EA=1; //开关总中断
ET0=1; //定时器T0允许中断
TMOD=0X01; //使用定时器T0的模式1
TH0=0x4C; //定时器T0高8位赋初值
TL0=0x00; //定时器T0高8位赋初值
TR0=0; //启动定时器 TR0=1时启动计数 TR0=0时停止计数
SDA=1; //SDA=1,SCL=1;使主从设备处于空闲状态
SCL=1;
cnt_cost=ReadSet(2); //读取保存的数据赋值于sec
cnt_distance=ReadSet(3);
flag=ReadSet(4);
while(1)
{
key_val=scan_key(); //255
if(key_val!=key_val_old)
{
key_val_old=key_val;
if(key_val!=255)
{
switch(key_val)
{
case 0: //启动键
TR0=1;
begin=0;
//cnt_distance=0;
if(flag==0)
{
cnt_cost=(state_val==0)?6:7;
flag=1; //启动计时,TR0=1为启动了的标志
}
else
{
if(lose_power==0)
flag=0;
}
break;
case 1: //清除键
init_variant(); //清除变量
lose_power=0;
TR0=0; //关闭定时器
break;
case 2: //白天/黑夜的切换
if(state_val==0)
{
state_val=1;
}
else
{
state_val=0;
}
break;
}
}
}
WriteSet(4,flag);
WriteSet(2,cnt_cost);
WriteSet(3,cnt_distance);
lcd_show();
}
}
|