可以测量电压的计算器

项目简介

        预计设计出一款家庭使用计算器同时具有测量电压功能,考虑到家庭使用对精度要求并不需要使用到高精度。所以我们预计使用两块AT89C51完成该项目。达到计算设计出精度为10char类型的计算器,小数精度可达到为6位,并且具有测量电压的功能,并采用较为简单的ADC0809对电池电压进行采样,精度可达到为0.0192V。并使用LCD1602对结果进行显示。使用了所有基本内容知识。

总体设计(要包括项目的总体功能描述,包括总体设计框图)

        此项目主要分为两个设计功能,分别是基于51单片机的简易计算器设计以及采集模拟电压值并转化为数字电压值,最后都能在液晶显示屏上显示输出相关数据。计算器键盘模块采用 4*4 矩阵式键盘,实现一个能够进行基本四则运算(加、减、乘、除)的计算器功能。电压检测采用AD0809进行采样,AT89C51单片机组及显示模块完成0~5V的直流电压的检测,并通过LCD1602实时显示。项目总体设计框图如图1:

图1:项目总体设计框图

硬件设计(包括原理图以及各功能模块说明)

        该项目的原理图如图二:

图2:原理图

        模拟电压输入:使用ADC0809对电压源进行采集模拟电压信号。

        模数转换器(ADC):通过ADC0809将采集到的模拟电压信号转换为数字信号并传入单片机中。

        核心控制器:选用AT89C51系列单片机作为主控芯片。第一块单片机(Assister)接收ADC传来的数字信号,通过串口通信传输给另一块单片机(Leader)。

        键盘输入模块:通过4*4矩阵键盘输入数字和运算符。通过独立按键进行定时保证单片机的平稳运行。

        显示模块:使用LCD1602显示采集到的数字电压值以及计算过程和结果。

        各功能模块图如图3:

图3:各功能模块图

软件设计(包括流程图、算法说明)

计算器模块

键盘扫描算法

        目的:检测键盘状态,识别用户按下的键。

        算法:

                i.循环检测:使用定时器中断键盘矩阵。

                ii.行列扫描:对于矩阵键盘,先置低一行,读取列;再依次置低其他行,读取列,直到所有行扫描完成。记录下有效按键的行列坐标。

                iii.去抖动:为防止按键按下瞬间产生的机械抖动导致误判,对检测到的按键状态进行一段时间的延时后再确认是否仍被按下。

运算处理

        目的:根据用户输入的运算符执行相应的数学运算,并显示计算结果。

算法:

                i.数组操作:使用数组对储操作数与存储运算符进行储存。

                ii.计算:当遇到等号时,对储存的数据进行运算,当检测到运算符时,会将前面的数值存入另外一个数组中,在最后进行运算。

                iii.类型转换:确保在进行除法时,结果精确可以达到小数点6为后。

 结果输出显示

        目的:将计算结果显示在LCD屏上。

        算法:

                i.格式化输出:将计算结果转换为字符串格式,根据LCD屏的特性进行适当的格式化(如限制小数点后位数)。

                ii.屏幕更新:使用LCD控制函数,将结果显示在屏幕的指定位置,可能需要清除之前的显示内容。

电压检测器

ADC读取与转换

        目的:读取ADC转换结果,并将模拟电压值其转换为数字电压值。

        算法:

                i.等待转换完成:通过中断方式,等待ADC完成转换并准备好数据。

                ii.读取数据:从ADC的数据寄存器中读取转换后的数字值。

                iii.转换计算:根据ADC的分辨率,将数字值转换为实际电压值。公式为 电压值 = (数字值 * Vref) / (2^n - 1),其中 Vref 是ADC的参考电压,n 是ADC的位数。

显示处理

        目的:将转换后的数字电压值显示在LCD屏或其他显示设备上。

        算法:

                i.数据格式化:将数字电压值转换为适合显示的字符串格式。

                ii.屏幕初始化:初始化LCD屏幕,设置其工作模式、显示方向等。

                iii.数据显示:将格式化后的电压值字符串发送到LCD,显示在指定位置。

                iiii.屏幕刷新:根据需要定期更新显示,以反映最新的电压值。

流程图如图4

图4:流程图

调试及仿真结果(包括设计成果展示以及仿真设计结果等)

        使用protues进行仿真

        功能一简易计算器仿真结果如图5:

 图5:功能一简易计算器仿真结果

        在Proteus仿真中,可以看到模拟的按键操作。用户通过点击虚拟按键,如数字键(0-9)、运算符键(+、-、*、/)以及等于号(=)、清除键(C)等,这些操作会被单片机捕获并处理。在仿真中,输入的数字和计算结果会正确地显示在LCD屏幕上,随着用户的操作动态更新。输入一系列数字和运算符后,按下等于号,屏幕应显示出正确的计算结果,并通过LCD(1602液晶显示器)来展示。设计中还考虑了运算优先级,仿真能正确处理括号内的运算先于其他运算,以及乘除优于加减的原则。

        功能二电压检测器仿真结果如图6:

图6:功能二电压检测器仿真结果

        在Proteus仿真中,可以看到ADC0809芯片正确连接到单片机的相应I/O端口,并且输入的模拟电压值能够被ADC芯片识别。设置一个可调的电压源(通过滑动变阻器)来模拟不同幅度的模拟电压输入,分压电路用于将电压调整至ADC可以处理的范围内(如0-5V

        转换后的数字电压值可以通过连接的显示设备(LCD1602液晶屏)显示出来。仿真中,能看到数值随模拟输入电压的变化而实时更新。进行多次仿真,改变模拟输入电压,观察系统能够稳定、准确地完成转换和显示任务。

代码

//************************************//
//          author:BoronLi            //
//          data:2024.5.10            //
//         brief: 计算器模块           //
//************************************//

#include "reg51.h"
#include <stdio.h>
#include "intrins.h"
#define u8  unsigned char
#define u16  unsigned char
sbit LCDEN=P3^4;
sbit RS=P3^5;
sbit RW=P3^6;
sbit BF=P0^7; 
sbit mode_sel=P3^7;
bit flag = 0;

unsigned int volts;
unsigned char code table[]="0123456789"; 

u8 key;	  
u8 y = 16;	

u8 code keyval[]="789/456*123-c0=+"; //按键对应的符号 
u8 data1[10];
u8 k=0;
char  m[10]={0};
double sum=0;

void delay(u16 x){  //1ms
  u16 i,j;
  for(i=0;i<x;i++)
    for(j=0;j<115;j++);
}

void Uart_init(){
  TMOD &= 0x0F;
  TMOD |= 0x20; //定时器0方式2
  
  TL1=0xf3;
  TH1=0xf3;
  TR1=1;	
  SM0=0;
  SM1=1;
  REN=1;
  ES=1;
  EA=1; 
} 
void Timer0Init(){
  TMOD &= 0xF0;
  TMOD |= 0x01;

  TL0=(65535 - 5)%256+1;
  TH0=(65535 - 5)/256;

  //TCON配置
  TF0 = 0;		//清除TF0标志
  TR0 = 1;		//定时器0开始计时

  //中断配置
  ET0=1; //enable time1 interrupt
  EA=1; //enable global interrupt switch
  PT0=0;//低优先级
} 
void Int0Init(){
    IT0=1;   
    EX0=1;  
    EA=1;
}

u8 keypad4_4(){
  u8 i,row,temp;
  u8 key=16;
  row=0x10;    
  for(i=0;i<4;i++)
    {
      P1=0x00;  
      P1=row;	
      row=_crol_(row,1);
      delay(1);
      temp=P1;
      P2=temp;
      temp=temp&0x0f;
      if(temp!=0x00)
      {  
        delay(10);//消抖
        temp=P1;  
        temp=temp&0x0f;  
        if(temp!=0x00)   
          {  
            switch(temp)  
              {  
                case 0x01:key=0+i;break;
                case 0x02:key=4+i;break;  
                case 0x04:key=8+i;break;
                case 0x08:key=12+i;break;
              }
          do
          {
            temp=P1;
            temp=temp&0x0f;  
            }while(temp!=0x00);
          }  
        }
    }  
  return(key);
}
unsigned char DectectBusyBit(void){  
  bit result;
  P0 = 0xff;
  RS = 0;
  delay(5);

  RW = 1;
  LCDEN = 1;
  delay(5);
  
  result=BF; 
  LCDEN = 0;
  return result;
}
void WrComLCD(unsigned char ComVal){//写命令函数
  while(DectectBusyBit()==1);         //先检测LCM是否空闲
  RS = 0;
  delay(1);
  RW = 0;
  LCDEN = 1;
  P0 = ComVal;
  delay(1);
  LCDEN = 0;
}
void WrDatLCD(unsigned char DatVal){//写数据函数
  while(DectectBusyBit()==1); 
  RS = 1;
  delay(1);
  RW = 0;
  LCDEN = 1;
  P0 = DatVal;
  delay(1);
  LCDEN = 0;	
}
void LCD_Init(void){//1602初始化函数 
  WrComLCD(0x38);     // 功能设定:16*2行、5*7点阵、8位数据接口
  WrComLCD(0x38);
  WrComLCD(0x38);    
//多次重复设定功能指令,因为LCD启动后并不知道使用的是4位数据接口还是8位的,所以开始时总是默认为4位
  WrComLCD(0x01);    // 清屏 
  WrComLCD(0x06);    // 光标自增、屏幕不动  
  delay(1);	      // 延时,等待上面的指令生效,下面再显示,防止出现乱码
  WrComLCD(0x0c);    // 开显示
}          
void compute(){
  u8 i,j=0,k,n=0;
  char data3[10]={0};
  int sum1,data2[10]={0};
  sum=0;
    
  for(i=0;data1[i]!='\0';i++){
      if(data1[i]!='+' && data1[i]!='-' && data1[i]!='*' && data1[i]!='/'){
        data2[j] =data2[j]*10+(data1[i]-'0');
      }
      else{
        data3[n++] = data1[i];
        j++;
      } 
  }
  for(i=0;i<n;i++){
    if(i==0){
      if(data3[0]=='+')  sum = data2[0] + data2[1];
      if(data3[0]=='-')  sum = data2[0] - data2[1];
      if(data3[0]=='*')  sum = data2[0] * data2[1];
      if(data3[0]=='/')  sum = data2[0] / (double)data2[1]; 
    }
    if(i==1){
      if(data3[1]=='+')  sum = sum+data2[2];
      if(data3[1]=='-')  sum = sum-data2[2];
      if(data3[1]=='*')  sum = sum*data2[2];
      if(data3[1]=='/')  sum = sum/((float)data2[2]); 
    }
    if(i==2){
      if(data3[2]=='+')  sum = sum+data2[3];
      if(data3[2]=='-')  sum = sum-data2[3];
      if(data3[2]=='*')  sum = sum*data2[3];
      if(data3[2]=='/')  sum = sum/((float)data2[3]); 
    }
  }
  //判断是小数输出还是整数输出
  sum1 = sum;
  if(sum1 == sum){
    sprintf(m,"%d",sum1);
  }
  else{
    sprintf(m,"%f",sum);
  }
  //把结果输出出来
  for(k=0;m[k]!='\0';k++){
    WrDatLCD(m[k]);
  }
}  

void main(){
  LCD_Init();
  Int0Init();
  Uart_init();
  Timer0Init();
  delay(5);   //延时,等待初始化完成
  WrDatLCD('0');
  WrComLCD(0x80);	   //设置显示地址第一行第一位:0X00(0x80+0x00)
  while(1){
    P1 = 0xF0;
    if(flag == 1){
      
      y = keypad4_4();
      flag = 0;
    } 
  }
}

void Timer0() interrupt 3{
  TL0=(65535 - 5000)%256+1;
  TH0=(65535 - 5000)/256;

  if(mode_sel == 1){
    if(y==12){
      k=0;
      WrComLCD(0x01);
      WrDatLCD('0');
      WrComLCD(0x80);
      y=16;
    }   //清屏
    
    if(y==14){
      WrComLCD(0xC0);
      WrDatLCD(keyval[y]);
      WrDatLCD(' ');
      data1[k]='\0';
      compute();//调用出结果函数
      y=16;
    }                  

    if(y<16 && y!=12 && y!=14){
      WrDatLCD(keyval[y]);
      data1[k++]= keyval[y];
      y=16;
    }
  }
  else{
      WrComLCD(0x01);  
      WrComLCD(0x80); 
      WrDatLCD('V');
      WrDatLCD('o');
      WrDatLCD('l');
      WrDatLCD('t');
      WrDatLCD(':');
      WrDatLCD(table[volts/1000]);
      WrDatLCD(table[volts/100%10]);
      WrDatLCD('.');
      WrDatLCD(table[volts/10%10]);
      WrDatLCD(table[volts%10]);
      WrDatLCD('V');
      delay(20);
      y = 12;
  }
}
void KeypadInt0() interrupt 0{
  flag = 1;
}	


void uart() interrupt 4{
	u8 receiveData;
	receiveData=SBUF;        //出去接收到的数据
  volts = receiveData*1.96;          //转化为十进制
  // P2=receiveData;
	RI=0;                    //清除接收中断标志位
}
//************************************//
//          author:BoronLi            //
//          data:2024.5.10            //
//         brief: 电压采集模块         //
//************************************//

#include<reg51.h> 
#define uchar unsigned char
#define uint unsigned int 
sbit OE=P2^0; //AD转换结果输出允许端
sbit START=P2^1;//AD启动信号输入端与接受C、B、A编码时的锁存控制信号
sbit EOC=P3^2;//转换结束输出信号,AD转换开始时为低电平,转换结束时为高电平
sbit A_data=P2^3;
sbit B_data=P2^4;
sbit C_data=P2^5; 

uchar i;

uint information=0; //转换出的数据

void Int0Init(){
	EA=1;            //开总中断	
	IT0=1;           //跳变沿方式感知外部中断(触发方式)	
	EX0=1;           //开外部中断0	
	IE0=1;
}

//串口中断初始化
void Usart_Init(void)
{
    TMOD=0X20;//定时器1方式2
    TH1=0xF3;         //计数器初始值设置,注意波特率是4800
    TL1=0xF3;
	ET1=0;
    TR1=1;//打开定时器
    SM0=0;//设置串口工作方式
    SM1=1;
    EA=1;
    ES=1;
}

//发送数据函数
void Send_Data(uchar Key_val)
{
    SBUF=Key_val;      //将要发送的数据存入发送缓冲器中
    while(!TI);        //若发送中断标志位没有置1(正在发送数据),就等待
    TI=0;              //若发送完成,TI自动置1,这里把它清零
}

void delay1ms(uint digit){	
	uint i,j;	
	for(i=0;i<digit;i++)		
	for(j=0;j<120;j++);
}


void main(){	
	Int0Init();
	Usart_Init();
	while(1){	
		START=0;	
		A_data=0;	
		B_data=0;	
		C_data=0;	
		START=1; 	
		START=0;	
		Send_Data(0x88);		
		delay1ms(5);
	}
}

void InT0(void) interrupt 0{	
	if(OE==1){
		Send_Data(0x88);	
		OE=0;
	}	
		
	//information=information*1.96; //将数据转换为十进制(5V/256约等于1.96)	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值