设计一个智能电压监视仪;电网电压的信号可用变压器从市电上取得,也可用+5V的电位器模拟。
要求:
1)显示内容:电压当前值;最大电压值;最小电压值;电压合格率;超上限率;超下限率;电压上、下限给定值、当前时间
2)利用小键盘实现电压上、下限给定值的输入和显示选择命令
3)电压采样周期为1秒,平均值的计算周期为2分钟,所求得的电压平均值作为统计和计算的依据,统计和计算以一天为单位,超过24小时则从头开始统计计算
4)超上限率=(其中超上限的次数)/(采样计算得到的总的电压平均值次数)*100%;超下限率的计算公式与此类似
5)合格率=1-超上限率-超下限率
6)显示北京时间,可调整
7)当前电压超上限或下限时,利用蜂鸣器报警
?
以上就是该电压监视仪的功能,然后我们根据要求分析需要使用板子上的哪些模块。
?
1)电源模块(不需要编写代码)
2)显示模块(采用LCD1602)
3)键盘模块(采用行列式扫描)
4)蜂鸣器
5)时钟模块(采用DS1307,能掉电后继续计时;若采用定时器,则掉电后重新计时)
6)AD模块(ADuC848有内置AD,没有专门的AD转换芯片)
?
下面就根据各模块逐步讲解代码:
?
显示模块:
?
?
#include <lcd1602.h>
//延时函数
void lcd_delay(uint t)
{
while(--t);
}
//检测忙信号
void Check_Busy(void)
{
P0 = 0xff; //P0口作为输入
lcd_delay(50);
rs = 0;
rw = 1;
e = 1;
lcd_delay(50);
while(P0&0X80);
e = 0;
rw = 0;
P0 = 0X00;
}
//lcd写指令
void lcd_wc(uchar cmd)
{
Check_Busy();
rs=0;
rw=0;
e=1;
P0=cmd;
lcd_delay(3);
e=0;
lcd_delay(65535);
lcd_delay(65535);
}
//lcd写数据
void lcd_wd(uchar dat)
{
Check_Busy();
rs=1;
rw=0;
e=1;
P0=dat;
lcd_delay(3);
e=0;
lcd_delay(250);
}
//显示位置
void lcd_pos(uchar pos )
{
pos+=0x80;
lcd_wc(pos);
}
//lcd初始化
void lcd_init()
{
uchar code table[]={"Voltage Monitor"};
uint i=0;
lcd_delay(60000);
lcd_delay(60000);
lcd_delay(60000);
lcd_delay(60000);
lcd_wc(0x38);
lcd_delay(60000);
lcd_delay(60000);
lcd_wc(0x38);
lcd_delay(60000);
lcd_delay(60000);
lcd_wc(0x38);
lcd_delay(60000);
lcd_delay(60000);
lcd_wc(0x38); //设置16x2显示,5x7点阵,8位数据接口
lcd_wc(0x0c); //开显示,不显示光标
lcd_wc(0x06); //地址指针加1,且光标加1,整屏显示不移动
lcd_wc(0x01); //清屏
// lcd_wc(0x0f); //显示开关
lcd_pos(0);
while(table[i]!=‘\0‘)
{
lcd_wd(table[i]);
i++;
}
}
?
?
?
?
?
?
?
这里需要注意三个函数:lcd_init()、lcd_wc()、lcd_wd()。
?
lcd_init()是lcd1602的初始化函数,就是设置几行显示、是否显示光标、文字是否滚动。在主函数刚开始就需要调用,后面就不再需要。
?
lcd_wc()是写命令函数,可以写清屏等命令,或者要将字符写在显示屏的位置。举例来说,液晶显示屏的第一行初始位置是0x80,第二行初始位置是0xc0,若你想在第一行起始位置输入一个字符,就要调用lcd_wc(0x80);之后的位置逐渐+1既是。
?

?
?
1602液晶模块的读写操作、屏幕和光标的操作都是通过指令编程来实现的。(说明:1为高电平、0为低电平)
?
指令1:清显示,指令码01H,光标复位到地址00H位置。
?
指令2:光标复位,光标返回到地址00H。
?
指令3:光标和显示模式设置 I/D:光标移动方向,高电平右移,低电平左移 S:屏幕上所有文字是否左移或者右移。高电平表示有效,低电平则无效。
?
指令4:显示开关控制。 D:控制整体显示的开与关,高电平表示开显示,低电平表示关显示 C:控制光标的开与关,高电平表示有光标,低电平表示无光标 B:控制光标是否闪烁,高电平闪烁,低电平不闪烁。
?
指令5:光标或显示移位 S/C:高电平时移动显示的文字,低电平时移动光标。
?
指令6:功能设置命令 DL:高电平时为4位总线,低电平时为8位总线 N:低电平时为单行显示,高电平时双行显示 F: 低电平时显示5x7的点阵字符,高电平时显示5x10的点阵字符。
?
指令7:字符发生器RAM地址设置。
?
指令8:DDRAM地址设置。
?
指令9:读忙信号和光标地址 BF:为忙标志位,高电平表示忙,此时模块不能接收命令或者数据,如果为低电平表示不忙。
?
指令10:写数据。
?
?
?
指令11:读数据。
?
?
?
?
?
lcd_wd()是写数据函数,就是你要显示的内容。它是和写命令函数共同使用的。一般一句写命令跟一句写数据,但也有一句写命令跟多句写数据(通过数组实现)。
?
例:代码红色部分,lcd_pos(0)表示显示屏最初始位置。
?
----------------------------------------------------------------------------------------------
DS1307模块(显示北京时间)
?
DS1307模块要用DS1307时钟芯片,想要具体了解可以查看该芯片使用说明书,这里就不详谈了。直接给出所有需要调用的函数。
?
#include<DS1307.h>
//#include<LCD1602.h>
//#define uchar unsigned char
//#define uint unsigned int
void iic_delay_4us()
{
uchar t=25+5; //适当增加延时,保证信号
while(--t);
}
void iic_delay_5us()
{
uchar t=32+5;
while(--t);
}
void iic_init()
{
I2CM=1; //master模式
MDE=1;
MCO=0; //允许SDA改变
MDO=1; //释放SDA
}
void iic_start()
{
MDE=1; //允许输出
MCO=0; //时钟拉低,允许数据线改变
MDO=0; //下面在数据线上产生一个脉冲
MCO=1; //制造起始条件
iic_delay_4us();//等待稳定
MDO=1; //上升
iic_delay_5us();//等待稳定
MDO=0; //拉低数据线,产生起始信号
iic_delay_4us();//等待稳定
MCO=0; //时钟拉低,开始
}
void iic_stop()
{
MDE=1; //允许输出
MCO=0;
MDO=0;
MCO=1;
iic_delay_4us();
MDO=1;
iic_delay_5us();
// MDO=0; //中止后应该释放数据线
}
//1307读入位数据
void iic_write_bit(bit dat)
{
MDE=1;
MCO=0;
MDO=dat;
iic_delay_4us();
MCO=1;
iic_delay_4us();
MCO=0;
iic_delay_4us();
}
//1307写出,输出1307发送出的位数据
bit iic_read_bit()
{
bit dat;
MDE=1;
MCO=0;
MDO=1; //释放数据线
MDE=0;
iic_delay_4us();
MCO=1;
iic_delay_4us();
dat=MDI;
MCO=0;
iic_delay_4us();
return dat;
}
//判断1307写出是否有效,即从机是否接受,输出应答信号。该信号为低,则表明有效
bit iic_write_char(uchar dat)
{
uchar i;
bit res;
for(i=0;i<8;i++)
{
iic_write_bit((dat>>(7-i))&0x01);
}
res=iic_read_bit();
return res;
}
//读入1307,输出写入的字符数据
uchar iic_read_char(bit nack)
{
uchar dat=0,i;
for(i=0;i<8;i++)
{
dat<<=1;
if(iic_read_bit()==1)dat++;
}
iic_write_bit(nack);
return dat;
}
//判断写地址是否有效
bit iic_write_address(uchar id,uchar address)
{
iic_start();
if(iic_write_char(id)!=0)return 1;
if(iic_write_char(address)!=0)return 1;
return 0;
}
?
?
?
----------------------------------------------------------------------------------------------
AD模块
?
ADuC848中包含一个双通道16位ADC模块,该开发板采用内部参考电压,因此使用ADC模块时只需配置相关寄存器,在AD转换完成时即可读取转换值。
?
AD流程图
?
#include <lcd1602.h>
#include <AD.h>
#define v_count 9
#define a_count 3
unsigned char InterruptCounter=0;//定义中断计数变量并赋初值0
unsigned int count=0;//定义秒计数变量并赋初值0
unsigned int count1=0;
unsigned int max,min;//电压最大、最小值
unsigned int CurrentValue;//电压当前值
unsigned int qual_rate,overup_rate,overlow_rate;//电压上、下限值、合格率、超上限率、超下限率
unsigned int upper_limit=20000;
unsigned int lower_limit=0;
bit bz_flag;//设标志位,区分对象是电压值还是百分比,输出“V”还是“%”
unsigned int Fir_bit,Sec_bit,Thi_bit,Fou_bit; //电压位
unsigned int num=0,num1=0; //num为电压采样周期内总计数,num1为平均值计算周期内总计数
unsigned int num_up=0,num_low=0; //num_up为超上限次数,num_low为超下限次数
unsigned int sum=0; //sum为电压采样周期内电压总数和
unsigned int value[v_count]; //取样电压值数组,存放一个平均值计算周期内的每秒电压值
unsigned int average[a_count]; //平均值数组,存放平均值
void Init()
{
ADCMODE = 0X23; //一直转换 /CHOP使能
ADC0CON1 = 0x27; //主ADC选用放大1倍,单端输入,1.28V
ADC0CON2 = 0x0C; //选用内部参考电源,模拟信号从AIN5 AIN6输入
/*
TMOD=0x01;
TH0=(65536-50000)/256;
TL0=(65536-50000)%256;
EA=1;
ET0=1;
TR0=1; */
TMOD=0x01;//配置定时器0为16位模式,时钟是core clock,默认为1.572864MHz
IE=0x82;//使能定时器0中断和全局中断
TH0=0X00;
TL0=0X00;
TR0=1;//开启定时器0
}
void Data_Process(unsigned char ad)
{
unsigned int w;
if(RDY0)
{
CurrentValue=((ADC0H*256)+ADC0M)*0.1953125;
// CurrentValue=1000;
value[num] = ((ADC0H*256)+ADC0M)*0.1953125;
max=value[0];
min=value[0];
//求最大、小值
for(w=1;w<num;w++)
{
if(value[w-1]>value[w])
{
value[w]=value[w-1];
}
else
{
max=value[w];
}
if(value[w-1]<value[w])
{
value[w]=value[w-1];
}
else
{
min=value[w];
}
}
sum+=value[num];//求电压采样周期内电压总和
average[num1]=sum/v_count; //取计算周期内所有采样电压值总和的平均值
//计算上、下限率
overup_rate=(num_up/num1)*100;
overlow_rate=num_low/num1*100;
qual_rate=100*(1-overup_rate/100-overlow_rate/100);
//当超过上下限值时,蜂鸣器鸣响报警
if(CurrentValue>upper_limit||CurrentValue<lower_limit)
{
beep=0;delay_beep(10);
beep=1;delay_beep(10);
}
//选择显示对应AD标号电压值或比率
switch(ad)
{
case 1: AD_display(CurrentValue);
break;
case 2: AD_display(max);
break;
case 3: AD_display(min);
break;
case 4: AD_display(qual_rate);
break;
case 5: AD_display(overup_rate);
break;
case 6: AD_display(overlow_rate);
break;
case 7: AD_display(upper_limit);
break;
case 8: AD_display(lower_limit);
break;
}
RDY0 = 0;
}
}
void delay_beep(unsigned int z)
{
unsigned int x,y;
for(x=z;x>0;x--)
{
for(y=100;y>0;y--);
}
}
//定时器中断
void timer0() interrupt 1
{
unsigned int k;
InterruptCounter++;
if(InterruptCounter>=24*8)
{
InterruptCounter=0;
count++;
num++;
//1秒采样一个电压
if(count>1)
{
count=0;
}
//每一个采样周期获得一个平均值
if(num==v_count)
{
num1++;
num=0;
sum=0;
//一个采样周期后采样电压值清零,重新存储
for(k=0;k<v_count;k++)
{
value[k]=0;
}
}
//判断电压是否超上下限
for(k=0;k<num1;k++)
{
if(average[k]>upper_limit&&num_up<a_count)
{
num_up++;
}
if(average[k]<lower_limit&&num_low<a_count)
{
num_low++;
}
}
//一个计算周期后清零重置
if(num1==a_count)
{
num1=0;
num=0;
num_up=0;
num_low=0;
for(k=0;k<a_count;k++)
{
average[k]=0;
}
}
}
}
//将电压数据或者比率显示在lcd上
void AD_display(unsigned int d)
{
if(RDY0)
{
if(bz_flag==0)
{
Fir_bit = d/10000;
Sec_bit = d/1000%10;
Thi_bit = d/100%10;
Fou_bit = d/10%10;
lcd_pos(lcd_next); //向液晶写指令
lcd_wd(0x30+Fir_bit);
lcd_wd(0x2e); //向液晶写‘.‘
lcd_wd(0x30+Sec_bit);
lcd_wd(0x30+Thi_bit);
lcd_wd(0x30+Fou_bit);
lcd_wd(0x56); //向液晶写‘V‘
}
else
{
d*=100;
Fir_bit = d/1000;
Sec_bit = d/100%10;
Thi_bit = d/10%10;
Fou_bit = d%10;
if(Fir_bit>9)
{
Fir_bit=9;
}
lcd_pos(lcd_next); //向液晶写指令
lcd_wd(0x30+Fir_bit);
lcd_wd(0x30+Sec_bit);
lcd_wd(0x2e); //向液晶写‘.‘
lcd_wd(0x30+Thi_bit);
lcd_wd(0x30+Fou_bit);
lcd_wd(0x25);//向液晶写‘%‘
}
/*
if(bz_flag==1)
{
d*=100;
Fir_bit = d/1000;
Sec_bit = d/100%10;
Thi_bit = d/10%10;
Fou_bit = d%10;
lcd_pos(lcd_next); //向液晶写指令
lcd_wd(0x30+Fir_bit);
lcd_wd(0x30+Sec_bit);
lcd_wd(0x30+Thi_bit);
lcd_wd(0x2e); //向液晶写‘.‘
lcd_wd(0x30+Fou_bit);
lcd_wd(0x25);//向液晶写‘%‘
} */
RDY0=0;
}
}
//加压
void tiaoya_add(unsigned char temp)
{
Fir_bit++;
if(Fir_bit>9)
{
Fir_bit=0;
}
lcd_wc(0xc0);
lcd_wd(0x30+Fir_bit);
lcd_wc(0xc0);
if(temp==1)
{
upper_limit=Fir_bit*10000;
}
if(temp==2)
{
lower_limit=Fir_bit*10000;
}
}
//减压
void tiaoya_sub(unsigned char temp)
{
if(Fir_bit==0)
{
Fir_bit=10;
}
Fir_bit--;
lcd_wc(0xc0);
lcd_wd(0x30+Fir_bit);
lcd_wc(0xc0);
if(temp==1)
{
upper_limit=Fir_bit*10000;
}
if(temp==2)
{
lower_limit=Fir_bit*10000;
}
}
//设定标志位区分电压值或者比率
void setbz_flag(bit nack)
{
bz_flag=nack;
}
?
?
?
红色部分就是修改寄存器的操作。
?
-----------------------------------------------------------------------------------------------
?
键盘模块
?
首先先谈下键盘扫描的原理:
?
矩阵键盘两端都与单片机I/O口相连,因此在检测时需通过单片机一端的I/O口人为产生低电平。检测时,先使第一列为低电平,其余几列全为高电平,然后检测各行是否有低电平,若检测到第一行为低电平,则可确认第一行第一列的按键被按下。用同样的方法轮流送各列一次低电平,再轮流检测一次各行是否变为低电平,这样即可检测完所有的按键,当有键被按下时便可判断出按下的键是哪一个键,然后根据返回的键值取相应的数码管段码。
?
键盘流程图如下:

?
然后我们开始设计下各键盘按钮的作用,在此之前我们再明确下要实现的功能。
?
具体功能如下:
?
功能一:显示当前电压值。
功能二:显示最大电压值。
功能三:显示最小电压值。
功能四:显示电压合格率。
??????? 电压合格率=1-超上线率-超下限率
功能五:显示电压上限率。
电压上限率=超上限率=(其中超上限的次数)/(采样计算得到的总的电压平均值次数)*100%
功能六:显示电压下限率。
电压下限率=超下限率=(其中超下限的次数)/(采样计算得到的总的电压平均值次数)*100%
功能七:显示电压上限值。
功能八:显示电压下限值。
功能九:显示当前北京时间。
功能十:若当前电压超上限或下限时,蜂鸣器会报警。
?
?
然后再分析下操作:
?
?
首先给键盘各按钮标号如下:
?
?0??? 1?? 2?? 3
?4??? 5?? 6?? 7??
8??? 9?? A?? B
C?? ?D? ??E?? F
?
各按钮功能:
?
0:显示当前电压值。
1:显示最大电压值。
2:显示最小电压值。
3:显示电压合格率。
4:显示电压上限率。
5:显示电压下限率。
6:显示电压上限值。
7:显示电压下限值。
8:显示当前北京时间。
9:调整时间,光标闪烁,按一下,光标右移一位;
?? 调整电压值,光标在电压初始位闪烁。
A:光标所在的时间位的数值加1,到了最大限值后则又从最小限值开始。
B:光标所在的时间位的数值减1,到了最小限值后则又从最大限值开始。
C:确认调整时间完毕。
D:调整电压值,光标所在位的数值加1,到了最大限值后则又从最小限值开始。
E:调整电压值,光标所在位的数值减1,到了最小限值后则又从最大限值开始。
F:确认调整电压完毕。
?
分析完各按钮的功能后,我们就可以开始编写键盘模块的程序了,如下:
?
#include<AD.h>
#include<table.h>
#include<lcd1602.h>
#include<jianpan.h>
#include<DS1307.h>
#include<main.h>
idata uchar id; //跳出显示时间界面的标志量,id=0,跳出;否则,继续显示。
idata uchar kcount=0; //按键次数量,亦即光标显示位置的标志量
idata uchar kcount1; //当与kcount相等时,说明没按下光标移动键
uchar ad_flag=0; //设置上下限值的标志量,上限值对应1,下限值对应2,其他值对应3
bit tflag=0; //改动时间标志,tflag=1时能改动时间,tflag=0时锁存时间,禁止改动。
uchar ad=0; //AD显示物理量标志号,各AD物理量对应不同标志号,如当前电压值为1,最大值为2,最小值为3......
void delay() //延时函数
{
uchar i;
for(i = 0;i < 100;i++);
}
//光标移动
void move()
{
if(tflag)
{
if(ad_flag==0) //调节时间时光标移动情况
{
//超过范围则光标复位
if(kcount>16)
{
kcount=1;
}
//遇到非数字符号调到下一位有效位
if(kcount==3||kcount==6||kcount==11||kcount==14)
{
kcount++;
}
lcd_wc(0x0f);//光标显示并闪烁
//光标闪烁位置
if(kcount>8)
{
lcd_wc(0xC0+kcount-9); //当kcount大于8时,光标从第二行开始闪烁
}
else
{
lcd_wc(0x80+kcount-1); //当kcount小于8时,光标从第一行开始闪烁
}
//当不再按键时,光标停留在原位上
kcount1=kcount;
delay();
while(kcount==kcount1)
{
Keyscan();
}
}
if(ad_flag==1||ad_flag==2) //调节电压上、下限值时光标移动情况
{
lcd_wc(0x0f);//光标显示并闪烁
lcd_wc(0xC0);
}
}
tflag=0;
}
//lcd屏幕显示
void lcd_display(uchar key)
{
switch(key)
{
case 1: //当前电压值
Check_Busy();
lcd_wc(lcd_clear);
gettable1();
id=0;
ad_flag=3;
setbz_flag(0);
while(ad)
{
Data_Process(ad);
Keyscan();
}
break;
case 2: //最大电压值
Check_Busy();
lcd_wc(lcd_clear);
gettable2();
id=0;
ad_flag=3;
setbz_flag(0);
while(ad)
{
Data_Process(ad);
Keyscan();
}
break;
case 3: //最小电压值
Check_Busy();
lcd_wc(lcd_clear);
gettable3();
id=0;
ad_flag=3;
setbz_flag(0);
while(ad)
{
Data_Process(ad);
Keyscan();
}
break;
case 4: //电压合格率
Check_Busy();
lcd_wc(lcd_clear);
gettable4();
id=0;
ad_flag=3;
setbz_flag(1);
while(ad)
{
Data_Process(ad);
Keyscan();
}
break;
case 5: //超上限率
Check_Busy();
lcd_wc(lcd_clear);
gettable5();
id=0;
ad_flag=3;
setbz_flag(2);
while(ad)
{
Data_Process(ad);
Keyscan();
}
break;
case 6: //超下限率
Check_Busy();
lcd_wc(lcd_clear);
gettable6();
id=0;
ad_flag=3;
setbz_flag(3);
while(ad)
{
Data_Process(ad);
Keyscan();
}
break;
case 7: //电压上限值
Check_Busy();
lcd_wc(lcd_clear);
gettable7();
id=0;
ad_flag=1;
tflag=1;
setbz_flag(0);
while(ad)
{
Data_Process(ad);
Keyscan();
}
break;
case 8: //电压下限值
Check_Busy();
lcd_wc(lcd_clear);
gettable8();
id=0;
ad_flag=2;
tflag=1;
setbz_flag(0);
while(ad)
{
Data_Process(ad);
Keyscan();
}
break;
case 9: //显示时间
Check_Busy();
lcd_wc(lcd_clear);
id=1;
ad_flag=0;
tflag=1;
display();
break;
case 10: //确认
Check_Busy();
id=1;
makeSure();
break;
}
}
//键盘扫描
void Keyscan()
{
unsigned char temp;
unsigned int j=0;
P1&=0xf0; //P1口低四位作为数字输入时应往相应引脚写0
P2&=0xf0; //置列扫描信号为0
temp=P1&0x0f;
if(temp!=0x0f) //P1口低四位不全为1,则有键被按下
{
delay(); //延时消抖
if(temp==(P1&0x0f)) //消抖后再判断键值
{
P2|=0x07; //P2.3为0
switch(P1&0x0f)
{
case 0x0e://显示电压当前值
ad=1;
lcd_display(1);
break;
case 0x0d: //显示超上限率
ad=5;
lcd_display(5);
break;
case 0x0b: //当前时间
ad=0;
lcd_display(9);
break;
case 0x07: //确认
ad=0;
lcd_display(10);
break;
case 0x0f:break;
default:break;
}
P2&=0xf0;
P2|=0x0b; //P2.2为0
switch(P1&0x0f)
{
case 0x0e: //显示最大电压值
ad=2;
lcd_display(2);
break;
case 0x0d://显示超下限率
ad=6;
lcd_display(6);
break;
case 0x0b: //光标移动
ad=0;
kcount++;
move();
break;
case 0x07://增加电压值
ad=0;
tiaoya_add(ad_flag);
break;
case 0x0f:break;
default:break;
}
P2&=0xf0;
P2|=0x0d; //P2.1为0
switch(P1&0x0f)
{
case 0x0e://显示最小电压值
ad=3;
lcd_display(3);
break;
case 0x0d: //电压上限给定值
ad=7;
lcd_display(7);
break;
case 0x0b: //加光标所在位置的数值
ad=0;
addValue(kcount);
break;
case 0x07:
//减小电压值
ad=0;
tiaoya_sub(ad_flag);
break;
case 0x0f:break;
default:break;
}
P2&=0xf0;
P2|=0x0e; //P2.0为0
switch(P1&0x0f)
{
case 0x0e://显示电压合格率
ad=4;
lcd_display(4);
break;
case 0x0d: //电压下限给定值
ad=8;
lcd_display(8);
break;
case 0x0b: //减光标所在位置的数值
ad=0;
subValue(kcount);
break;
case 0x07: //电压调整完毕
ad=0;
lcd_wc(0x0c);
break;
case 0x0f:break;
default:break;
}
}
}
}
/*
uchar getk()
{
return kcount;
} */
//设置按键次数量,主要负责清零重置
void setk(uchar count)
{
kcount=count;
}
//获得跳出显示时间界面的标志量
uchar getid()
{
return id;
}
//获得设置上下限值的标志量
uchar getad_flag()
{
return ad_flag;
}
//获得AD显示物理量标志号
uchar getad()
{
return ad;
}
?----------------------------------------------------------------------------------------------
?
?
?输出各物理量名的模块
#include<AD.h>
#include<lcd1602.h>
#include<table.h>
//当前电压值
void gettable1()
{
lcd_pos(0);
lcd_wd(0x43);
lcd_pos(0x01);
lcd_wd(0x75);
lcd_pos(0x02);
lcd_wd(0x72);
lcd_pos(0x03);
lcd_wd(0x72);
lcd_pos(0x04);
lcd_wd(0x65);
lcd_pos(0x05);
lcd_wd(0x6E);
lcd_pos(0x06);
lcd_wd(0x74);
lcd_pos(0x07);
lcd_wd(0x20);
lcd_pos(0x08);
lcd_wd(0x56);
lcd_pos(0x09);
lcd_wd(0x6F);
lcd_pos(0x0A);
lcd_wd(0x6C);
lcd_pos(0x0B);
lcd_wd(0x74);
lcd_pos(0x0C);
lcd_wd(0x61);
lcd_pos(0x0D);
lcd_wd(0x67);
lcd_pos(0x0E);
lcd_wd(0x65);
lcd_pos(0x0F);
lcd_wd(0x3A);
lcd_pos(lcd_next);
}
//最大电压值
void gettable2()
{
lcd_pos(0);
lcd_wd(0x4D);
lcd_pos(0x01);
lcd_wd(0x61);
lcd_pos(0x02);
lcd_wd(0x78);
lcd_pos(0x03);
lcd_wd(0x20);
lcd_pos(0x04);
lcd_wd(0x56);
lcd_pos(0x05);
lcd_wd(0x6F);
lcd_pos(0x06);
lcd_wd(0x6C);
lcd_pos(0x07);
lcd_wd(0x74);
lcd_pos(0x08);
lcd_wd(0x61);
lcd_pos(0x09);
lcd_wd(0x67);
lcd_pos(0x0A);
lcd_wd(0x65);
lcd_pos(0x0B);
lcd_wd(0x3A);
lcd_pos(lcd_next);
}
//最小电压值
void gettable3()
{
lcd_pos(0);
lcd_wd(0x4D);
lcd_pos(0x01);
lcd_wd(0x69);
lcd_pos(0x02);
lcd_wd(0x6E);
lcd_pos(0x03);
lcd_wd(0x20);
lcd_pos(0x04);
lcd_wd(0x56);
lcd_pos(0x05);
lcd_wd(0x6F);
lcd_pos(0x06);
lcd_wd(0x6C);
lcd_pos(0x07);
lcd_wd(0x74);
lcd_pos(0x08);
lcd_wd(0x61);
lcd_pos(0x09);
lcd_wd(0x67);
lcd_pos(0x0A);
lcd_wd(0x65);
lcd_pos(0x0B);
lcd_wd(0x3A);
lcd_pos(lcd_next);
}
//电压合格率"Quality Rate"
void gettable4()
{
lcd_pos(0);
lcd_wd(0x51);
lcd_pos(0x01);
lcd_wd(0x75);
lcd_pos(0x02);
lcd_wd(0x61);
lcd_pos(0x03);
lcd_wd(0x6C);
lcd_pos(0x04);
lcd_wd(0x69);
lcd_pos(0x05);
lcd_wd(0x74);
lcd_pos(0x06);
lcd_wd(0x79);
lcd_pos(0x07);
lcd_wd(0x20);
lcd_pos(0x08);
lcd_wd(0x52);
lcd_pos(0x09);
lcd_wd(0x61);
lcd_pos(0x0A);
lcd_wd(0x74);
lcd_pos(0x0B);
lcd_wd(0x65);
lcd_pos(0x0C);
lcd_wd(0x3A);
lcd_pos(lcd_next);
}
//超上限率"Upper Limit Rate "
void gettable5()
{
lcd_pos(0);
lcd_wd(0x55);
lcd_pos(0x01);
lcd_wd(0x70);
lcd_pos(0x02);
lcd_wd(0x70);
lcd_pos(0x03);
lcd_wd(0x65);
lcd_pos(0x04);
lcd_wd(0x72);
lcd_pos(0x05);
lcd_wd(0x20);
lcd_pos(0x06);
lcd_wd(0x4C);
lcd_pos(0x07);
lcd_wd(0x69);
lcd_pos(0x08);
lcd_wd(0x6D);
lcd_pos(0x09);
lcd_wd(0x69);
lcd_pos(0x0A);
lcd_wd(0x74);
lcd_pos(0x0B);
lcd_wd(0x20);
lcd_pos(0x0C);
lcd_wd(0x52);
lcd_pos(0x0D);
lcd_wd(0x61);
lcd_pos(0x0E);
lcd_wd(0x74);
lcd_pos(0x0F);
lcd_wd(0x65);
lcd_pos(lcd_next);
}
//超下限率"Lower Limit Rate"
void gettable6()
{
lcd_pos(0);
lcd_wd(0x4C);
lcd_pos(0x01);
lcd_wd(0x6F);
lcd_pos(0x02);
lcd_wd(0x77);
lcd_pos(0x03);
lcd_wd(0x65);
lcd_pos(0x04);
lcd_wd(0x72);
lcd_pos(0x05);
lcd_wd(0x20);
lcd_pos(0x06);
lcd_wd(0x4C);
lcd_pos(0x07);
lcd_wd(0x69);
lcd_pos(0x08);
lcd_wd(0x6D);
lcd_pos(0x09);
lcd_wd(0x69);
lcd_pos(0x0A);
lcd_wd(0x74);
lcd_pos(0x0B);
lcd_wd(0x20);
lcd_pos(0x0C);
lcd_wd(0x52);
lcd_pos(0x0D);
lcd_wd(0x61);
lcd_pos(0x0E);
lcd_wd(0x74);
lcd_pos(0x0F);
lcd_wd(0x65);
lcd_pos(lcd_next);
}
//"Upper Limit"
void gettable7()
{
lcd_pos(0);
lcd_wd(0x55);
lcd_pos(0x01);
lcd_wd(0x70);
lcd_pos(0x02);
lcd_wd(0x70);
lcd_pos(0x03);
lcd_wd(0x65);
lcd_pos(0x04);
lcd_wd(0x72);
lcd_pos(0x05);
lcd_wd(0x20);
lcd_pos(0x06);
lcd_wd(0x4C);
lcd_pos(0x07);
lcd_wd(0x69);
lcd_pos(0x08);
lcd_wd(0x6D);
lcd_pos(0x09);
lcd_wd(0x69);
lcd_pos(0x0A);
lcd_wd(0x74);
lcd_pos(0x0B);
lcd_wd(0x3A);
lcd_pos(lcd_next);
}
//超下限值"Lower Limit"
void gettable8()
{
lcd_pos(0);
lcd_wd(0x4C);
lcd_pos(0x01);
lcd_wd(0x6F);
lcd_pos(0x02);
lcd_wd(0x77);
lcd_pos(0x03);
lcd_wd(0x65);
lcd_pos(0x04);
lcd_wd(0x72);
lcd_pos(0x05);
lcd_wd(0x20);
lcd_pos(0x06);
lcd_wd(0x4C);
lcd_pos(0x07);
lcd_wd(0x69);
lcd_pos(0x08);
lcd_wd(0x6D);
lcd_pos(0x09);
lcd_wd(0x69);
lcd_pos(0x0A);
lcd_wd(0x74);
lcd_pos(0x0B);
lcd_wd(0x3A);
lcd_pos(lcd_next);
}
?------------------------------------------------------------------------------------------------------------------------------------
?主函数所在的模块:
?
//ADuC847寄存器地址
/*#ifndef _ADUC847_H_
#define _ADUC847_H_
#include <aduc848.h>
#endif */
//LCD1602驱动
#include <lcd1602.h>
#include <AD.h>
#include<DS1307.h>
#include<jianpan.h>
#include<table.h>
#define uchar unsigned char
#define uint unsigned int
bit a,b;
uchar dat[7];
uchar dispnum[]="0123456789ABCDEF";
idata uchar day[7][4]={"SUN","MON","TUE","WED","THU","FRI","SAT"};
idata uchar kcount2;
uchar dat2[6]; //存储时分秒等的改变值
bit zf_flag; //加减标志位
//改变光标所在位置的数值
void changeValue(uint i)
{
//设置时间值
if(zf_flag==0)
{
if(i==0)
{
dat[6]+=0x10;
lcd_wc(0x80);
lcd_wd(dispnum[dat[6]>>4]);
lcd_wc(0x0f);
lcd_wc(0x80);
}
if(i==1)
{
dat[6]+=0x01;
lcd_wc(0x81);
lcd_wd(dispnum[dat[6]&0x0f]);
lcd_wc(0x0f);
lcd_wc(0x81);
}
if(i==2)
{
dat[5]+=0x10;
lcd_wc(0x83);
lcd_wd(dispnum[dat[5]>>4]);
lcd_wc(0x0f);
lcd_wc(0x83);
}
if(i==3)
{
dat[5]+=0x01;
lcd_wc(0x84);
lcd_wd(dispnum[dat[5]&0x0f]);
lcd_wc(0x0f);
lcd_wc(0x84);
}
if(i==4)
{
dat[4]+=0x10;
lcd_wc(0x86);
lcd_wd(dispnum[dat[4]>>4]);
lcd_wc(0x0f);
lcd_wc(0x86);
}
if(i==5)
{
dat[4]+=0x01;
lcd_wc(0x87);
lcd_wd(dispnum[dat[4]&0x0f]);
lcd_wc(0x0f);
lcd_wc(0x87);
}
if(i==6)
{
dat[2]+=0x10;
if(dat[2]>0x20)
{
dat[2]-=0x20;
}
lcd_wc(0xc0);
lcd_wd(dispnum[dat[2]>>4]);
lcd_wc(0x0f);
lcd_wc(0xc0);
}
if(i==7)
{
dat[2]+=0x01;
if(dispnum[dat[2]&0x0f]==‘A‘||dispnum[dat[2]&0x0f]==‘B‘||dispnum[dat[2]&0x0f]==‘C‘||dispnum[dat[2]&0x0f]==‘D‘||dispnum[dat[2]&0x0f]==‘E‘||dispnum[dat[2]&0x0f]==‘F‘)
{
dat[2]-=10;
}
lcd_wc(0xc1);
lcd_wd(dispnum[dat[2]&0x0f]);
lcd_wc(0x0f);
lcd_wc(0xc1);
}
if(i==8)
{
dat[1]+=0x10;
if(dat[1]>0x60)
{
dat[1]-=0x60;
}
lcd_wc(0xc3);
lcd_wd(dispnum[dat[1]>>4]);
lcd_wc(0x0f);
lcd_wc(0xc3);
}
if(i==9)
{
dat[1]+=0x01;
if(dispnum[dat[1]&0x0f]==‘A‘||dispnum[dat[1]&0x0f]==‘B‘||dispnum[dat[1]&0x0f]==‘C‘||dispnum[dat[1]&0x0f]==‘D‘||dispnum[dat[1]&0x0f]==‘E‘||dispnum[dat[1]&0x0f]==‘F‘)
{
dat[1]-=10;
}
lcd_wc(0xc4);
lcd_wd(dispnum[dat[1]&0x0f]);
lcd_wc(0x0f);
lcd_wc(0xc4);
}
if(i==10)
{
dat[0]+=0x10;
if(dat[0]>0x60)
{
dat[0]-=0x60;
}
lcd_wc(0xc6);
lcd_wd(dispnum[dat[0]>>4]);
lcd_wc(0x0f);
lcd_wc(0xc6);
}
if(i==11)
{
dat[0]+=0x01;
if(dispnum[dat[0]&0x0f]==‘A‘||dispnum[dat[0]&0x0f]==‘B‘||dispnum[dat[0]&0x0f]==‘C‘||dispnum[dat[0]&0x0f]==‘D‘||dispnum[dat[0]&0x0f]==‘E‘||dispnum[dat[0]&0x0f]==‘F‘)
{
dat[0]-=10;
}
lcd_wc(0xc7);
lcd_wd(dispnum[dat[0]&0x0f]);
lcd_wc(0x0f);
lcd_wc(0xc7);
}
}
else
{
if(i==0)
{
dat[6]-=0x10;
lcd_wc(0x80);
lcd_wd(dispnum[dat[6]>>4]);
lcd_wc(0x0f);
lcd_wc(0x80);
}
if(i==1)
{
dat[6]-=0x01;
lcd_wc(0x81);
lcd_wd(dispnum[dat[6]&0x0f]);
lcd_wc(0x0f);
lcd_wc(0x81);
}
if(i==2)
{
dat[5]-=0x10;
lcd_wc(0x83);
lcd_wd(dispnum[dat[5]>>4]);
lcd_wc(0x0f);
lcd_wc(0x83);
}
if(i==3)
{
dat[5]-=0x01;
lcd_wc(0x84);
lcd_wd(dispnum[dat[5]&0x0f]);
lcd_wc(0x0f);
lcd_wc(0x84);
}
if(i==4)
{
dat[4]-=0x10;
lcd_wc(0x86);
lcd_wd(dispnum[dat[4]>>4]);
lcd_wc(0x0f);
lcd_wc(0x86);
}
if(i==5)
{
dat[4]-=0x01;
lcd_wc(0x87);
lcd_wd(dispnum[dat[4]&0x0f]);
lcd_wc(0x0f);
lcd_wc(0x87);
}
if(i==6)
{
if(dat[2]>>4==0)
{
dat[2]+=0x20;
}
dat[2]-=0x10;
lcd_wc(0xc0);
lcd_wd(dispnum[dat[2]>>4]);
lcd_wc(0x0f);
lcd_wc(0xc0);
}
if(i==7)
{
if(dispnum[dat[2]&0x0f]==‘A‘||dispnum[dat[2]&0x0f]==‘B‘||dispnum[dat[2]&0x0f]==‘C‘||dispnum[dat[2]&0x0f]==‘D‘||dispnum[dat[2]&0x0f]==‘E‘||dispnum[dat[2]&0x0f]==‘F‘)
{
dat[2]-=0x05;
}
if(dispnum[dat[2]&0x0f]==‘0‘)
{
dat[2]+=0x0a;
}
dat[2]-=0x01;
if(dispnum[dat[2]&0x0f]==‘F‘)
{
dat[2]-=0x06;
}
lcd_wc(0xc1);
lcd_wd(dispnum[dat[2]&0x0f]);
lcd_wc(0x0f);
lcd_wc(0xc1);
}
if(i==8)
{
if(dat[1]>>4==0)
{
dat[1]+=0x60;
}
dat[1]-=0x10;
lcd_wc(0xc3);
lcd_wd(dispnum[dat[1]>>4]);
lcd_wc(0x0f);
lcd_wc(0xc3);
}
if(i==9)
{
if(dispnum[dat[1]&0x0f]==‘A‘||dispnum[dat[1]&0x0f]==‘B‘||dispnum[dat[1]&0x0f]==‘C‘||dispnum[dat[1]&0x0f]==‘D‘||dispnum[dat[1]&0x0f]==‘E‘||dispnum[dat[1]&0x0f]==‘F‘)
{
dat[1]-=0x05;
}
if(dispnum[dat[1]&0x0f]==‘0‘)
{
dat[1]+=0x0a;
}
dat[1]-=0x01;
if(dispnum[dat[1]&0x0f]==‘F‘)
{
dat[1]-=0x06;
}
lcd_wc(0xc4);
lcd_wd(dispnum[dat[1]&0x0f]);
lcd_wc(0x0f);
lcd_wc(0xc4);
}
if(i==10)
{
if(dat[0]>>4==0)
{
dat[0]+=0x60;
}
dat[0]-=0x10;
lcd_wc(0xc6);
lcd_wd(dispnum[dat[0]>>4]);
lcd_wc(0x0f);
lcd_wc(0xc6);
}
if(i==11)
{
if(dispnum[dat[0]&0x0f]==‘A‘||dispnum[dat[0]&0x0f]==‘B‘||dispnum[dat[0]&0x0f]==‘C‘||dispnum[dat[0]&0x0f]==‘D‘||dispnum[dat[0]&0x0f]==‘E‘||dispnum[dat[0]&0x0f]==‘F‘)
{
dat[0]-=0x05;
}
if(dispnum[dat[0]&0x0f]==‘0‘)
{
dat[0]+=0x0a;
}
dat[0]-=0x01;
if(dispnum[dat[0]&0x0f]==‘F‘)
{
dat[0]-=0x06;
}
lcd_wc(0xc7);
lcd_wd(dispnum[dat[0]&0x0f]);
lcd_wc(0x0f);
lcd_wc(0xc7);
}
}
}
//加值
void addValue(uchar k1)
{
zf_flag=0;
switch(k1)
{
case 1:
/*
addv[0]++;
lcd_wc(0x80);
dat1[0]=(dispnum[dat[6]>>4])+addv[0];
lcd_wd(dat1[0]);
lcd_wc(0x0f);
lcd_wc(0x80); */
changeValue(0);
break;
case 2:
changeValue(1);
break;
case 4:
changeValue(2);
break;
case 5:
changeValue(3);
break;
case 7:
changeValue(4);
break;
case 8:
changeValue(5);
break;
case 9:
changeValue(6);
break;
case 10:
changeValue(7);
break;
case 12:
changeValue(8);
break;
case 13:
changeValue(9);
break;
case 15:
changeValue(10);
break;
case 16:
changeValue(11);
break;
}
}
//减值
void subValue(uchar k1)
{
zf_flag=1;
switch(k1)
{
case 1:
changeValue(0);
break;
case 2:
changeValue(1);
break;
case 4:
changeValue(2);
break;
case 5:
changeValue(3);
break;
case 7:
changeValue(4);
break;
case 8:
changeValue(5);
break;
case 9:
changeValue(6);
break;
case 10:
changeValue(7);
break;
case 12:
changeValue(8);
break;
case 13:
changeValue(9);
break;
case 15:
changeValue(10);
break;
case 16:
changeValue(11);
break;
}
}
uchar Decimal_to_BCD(uchar temp)//十进制转换成BCD码
{
uchar a,b,c;
a=temp;
b=0;
if(a>=10)
{
while(a>=10)
{
a=a-10;
b=b+16;
c=a+b;
temp=c;
}
}
return temp;
}
//DS1307传输准备,时间初始化
void display_ready()
{
iic_init();
a=iic_write_address(0xd0,0x00);
b=iic_write_char(0x30);
b=iic_write_char(0x26);
b=iic_write_char(0x16);
b=iic_write_char(0x01); //DS1307只能记录20个小时,每20小时重置。即时最大为19!!
b=iic_write_char(0x01);
b=iic_write_char(0x06);
b=iic_write_char(0x15);
iic_stop();
}
//时间显示
void display()
{
while(1&getid())
{
if(iic_write_address(0xd0,0x00)==0)
{
iic_start();
if(iic_write_char(0xd1)==0)
{
dat[0]=iic_read_char(0);
dat[1]=iic_read_char(0);
dat[2]=iic_read_char(0);
dat[3]=iic_read_char(0);
dat[4]=iic_read_char(0);
dat[5]=iic_read_char(0);
dat[6]=iic_read_char(1);
}
iic_stop();
lcd_pos(0);
lcd_wd(dispnum[dat[6]>>4]);
lcd_wd(dispnum[dat[6]&0x0f]);
lcd_wd(‘-‘);
lcd_wd(dispnum[dat[5]>>4]);
lcd_wd(dispnum[dat[5]&0x0f]);
lcd_wd(‘-‘);
lcd_wd(dispnum[dat[4]>>4]);
lcd_wd(dispnum[dat[4]&0x0f]);
lcd_wd(‘ ‘);
lcd_wd(day[dat[3]][0]);
lcd_wd(day[dat[3]][1]);
lcd_wd(day[dat[3]][2]);
lcd_pos(lcd_next);
lcd_wd(dispnum[(dat[2]>>4)&0x01]);
lcd_wd(dispnum[dat[2]&0x0f]);
lcd_wd(‘:‘);
lcd_wd(dispnum[dat[1]>>4]);
lcd_wd(dispnum[dat[1]&0x0f]);
lcd_wd(‘:‘);
lcd_wd(dispnum[(dat[0]>>4)&0x07]);
lcd_wd(dispnum[dat[0]&0x0f]);
/*
//记录按键按下次数并显示
lcd_pos(lcd_next+0x0C);
if(getk()>9&&getk()<13)
{
lcd_wd(0x30+1);
lcd_pos(lcd_next+0x0D);
lcd_wd(0x30+getk()-10);
}
else if(getk()>12)
{
lcd_wd(0x30+0);
lcd_pos(lcd_next+0x0D);
lcd_wd(0x30+0);
}
else
{
lcd_wd(0x30+0);
lcd_pos(lcd_next+0x0D);
lcd_wd(0x30+getk());
} */
}
iic_delay_5us();
Keyscan(); //继续扫描,使能跳出时间显示
}
}
void makeSure()
{
//按第一位复位;按第二位除它其他复位;
uchar i;
setk(0); //让kcount复零,光标复位
lcd_wc(0x0c);
lcd_wc(0x02);
dat2[0]=Decimal_to_BCD((dispnum[dat[6]>>4]-0x30)*10+(dispnum[dat[6]&0x0f]-0x30));
dat2[1]=Decimal_to_BCD((dispnum[dat[5]>>4]-0x30)*10+(dispnum[dat[5]&0x0f]-0x30));
dat2[2]=Decimal_to_BCD((dispnum[dat[4]>>4]-0x30)*10+(dispnum[dat[4]&0x0f]-0x30));
dat2[3]=Decimal_to_BCD((dispnum[dat[2]>>4]-0x30)*10+(dispnum[dat[2]&0x0f]-0x30));
dat2[4]=Decimal_to_BCD((dispnum[dat[1]>>4]-0x30)*10+(dispnum[dat[1]&0x0f]-0x30));
dat2[5]=Decimal_to_BCD((dispnum[dat[0]>>4]-0x30)*10+(dispnum[dat[0]&0x0f]-0x30));
iic_init();
a=iic_write_address(0xd0,0x00); //0xd0为器件写地址
b=iic_write_char(dat2[5]);
b=iic_write_char(dat2[4]);
b=iic_write_char(dat2[3]);
b=iic_write_char(0x00);
b=iic_write_char(dat2[2]);
b=iic_write_char(dat2[1]);
b=iic_write_char(dat2[0]);
display();
}
void main()
{
PLLCON&=0xf8; //设置频率为12.582912MHz
lcd_init();
// iic_init();
display_ready();
Init();
// display();
while(1)
{
Keyscan();
}
}
?
?这里说下如何调整显示时间。
?
//DS1307传输准备,时间初始化
void display_ready()
{
iic_init();
a=iic_write_address(0xd0,0x00);
b=iic_write_char(0x30);
b=iic_write_char(0x26);
b=iic_write_char(0x16);
b=iic_write_char(0x01);
b=iic_write_char(0x01);
b=iic_write_char(0x06);
b=iic_write_char(0x15);
iic_stop();
}
?
30对应的是秒,26对应的是分,16对应的是时,01对应的是星期即星期一,之后分别代表日月年。
?
如果要显示15年6月5日星期四0时42分00秒,可修改为:
?
//DS1307传输准备,时间初始化
void display_ready()
{
iic_init();
a=iic_write_address(0xd0,0x00);
b=iic_write_char(0x00);
b=iic_write_char(0x42);
b=iic_write_char(0x00);
b=iic_write_char(0x04);
b=iic_write_char(0x05);
b=iic_write_char(0x06);
b=iic_write_char(0x15);
iic_stop();
}
?
若再想实现掉电后能继续计时,则下次将程序写入板子前记得把这个函数注释掉,不需要再定义了。
这样效果就会如:掉电前是0时45分00秒,关开关,过10秒后再开开关,时间显示为0时45分10秒!!
?
综合以上,就能实现出一个智能电压测试仪了。下面再给出调整时间或电压上下限值的操作。
?
操作指南
?
操作一:调整北京时间
?
先按下按钮8,然后按下按钮9,通过按动9使光标移动到想要调整的位置,再通过按钮A或B改动所在位的时间值,最后再按下按钮C则调整完毕。
?
操作二:调整电压上、下限值
?
以调整电压上限值为例,按下按钮6,然后按下按钮9,通过按钮D或E改动电压上限值,最后按下按钮F确定。
?
?
?
?
?
?
?
原文:http://459838660.iteye.com/blog/2217183