基于89C51的计算器设计

电子系统设计实验课–计算器

这篇文章是我电子系统设计课的第一次大作业报告。老师要求从六个题目当中选一个做项目,我选择的是看起来最复杂的一个。因为其他的项目貌似只要修改我之前的万能电子钟的代码就好,简易计算器这个项目算是从零开始啦。
### 设计思路 单片机读键值和LCD显示的方法我们再熟悉不过了。这些都有已经构建好的底层文件,直接调用接口函数就好了。读键值的函数作用是有键按下,则返回一个unsigned char型数。所以我们首先需要做一次转换,目的是我定义一个键,按下它就为1或者2或者3等等,便于下一步的计算。第二个问题是,当我们依次按下了键1、键2,单片机会读入的值是1,接着又读入2,我们实际需要计算的值为12。我进行了第二次转换,即将上次读回的值乘10加这次读回的值。我们把完整的一次计算过程大致分为四步:输入第一个数,输入运算符号,输入第二个数,按等于号显示结果。
### 目前实现的功能和问题 功能:

  1. -32767至+32768之间的加减乘除整数运算。
  2. -32767至+32768之间的加减小数运算,仅能算一位小数,两位就bug,原因等下说。
  3. 可以清屏。

这样看起来功能确实挺弱智的,但是基本上满足了这次作业的要求,说实话做的时间不大够最近又很烦,就留了很多的bug没解决。
问题:

  1. 小数功能不完善是因为小数部分计算的算法不合理。我在这个计算器里开的所有变量几乎都是整型,51的话全用float会造成数据溢出。所以小数的计算其实是当成整数算的,输入等式为12.3+12.3的时候,程序的执行过程是12+12,3+3,得到24,6两个数,中间显示个小数点。这种太脑残的方法不适用于乘除,我又没空给乘除写单独的算法就把这bug暂且扔在那儿了。
  2. 小数位数限制还是因为这算法过于简单,12.3+12.31的算式在单片机中就算成了12+12,3+31,输出的结果就不正确了。
  3. 显示有一些问题。那个鬼畜的LCD1602有的时候会自动移位显示,有的时候会把我原来显示的东西覆盖掉,目前来看显示方面的bug基本被我改完了,基本不会出现显示算式不正确的情况。

硬件电路和实现效果图

电路图 实现效果

代码部分

第一部分:底层/硬件-软件接口

这里是使用hc165芯片来读键值程序,使用hc_165()返回的就是键值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<reg52.h>
#include<hc165.h>
sbit hc165_sdo=P2^5;
sbit hc165_sclk=P2^6;
sbit hc165_pl=P2^7;
void hc165_init(){
hc165_sdo=1;
hc165_sclk=0;
hc165_pl=1;
}
unsigned char hc165_re(void){
unsigned i,x=0;
hc165_pl=0;
x=x+0;
hc165_pl=1;
if( hc165_sdo == 1 )
x = x + 1;
for( i = 0;i < 7;i ++){
hc165_sclk=1;
x = x << 1;
hc165_sclk=0;
if( hc165_sdo == 1 )
x = x + 1;
}
return x;
}

这里是LCD显示的函数,主要实现的是送字符给LCD显示,还有确定LCD显示位置的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include<reg52.h>
sbit LCD_E = P2^2;
sbit LCD_RW = P2^1;
sbit LCD_RS = P2^0;
sbit HC164_SDAT = P2^3;
sbit HC164_SCLK = P2^4;
void send_data(unsigned char dat) {
unsigned char i;
for(i = 0; i < 8; i++){
HC164_SCLK = 0;
if(dat & 0x80){
HC164_SDAT = 1;
}
else{
HC164_SDAT = 0;
}
HC164_SCLK = 1;
dat = dat << 1;
}
}
void Delay1ms(unsigned int c) {
unsigned char a,b;
for(; c>0; c--) {
for(b = 199; b > 0; b--) {
for(a = 1; a > 0; a--);
}
}
}
void LcdWrCmd(unsigned char com) {
unsigned char x;
LCD_E = 0;
LCD_RS = 0;
LCD_RW = 0;
send_data(com);
x++;
LCD_E = 1;
x++;
LCD_E = 0;
Delay1ms(1);
}
void LcdWrData(unsigned char dat) {
unsigned char x;
LCD_E = 0;
LCD_RS = 1;
LCD_RW = 0;
send_data(dat);
x++;
LCD_E = 1;
x++;
LCD_E = 0;
Delay1ms(1);
}
void disp_line(unsigned char line) {
if(line == 0){
LcdWrCmd(0x80 + 0x00);
}
else{
LcdWrCmd(0x80 + 0x40);
}
}
void LcdInit(){
LcdWrCmd(0x38);
LcdWrCmd(0x0c);
LcdWrCmd(0x06);
LcdWrCmd(0x01);
}

老师给我们的实验板上有一块ZLG7920芯片,我们用它来做4*4矩阵键盘的读值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include "i2c.h"
#include "ZLG7290.h"
#define EEPROM24C02 0xA0
#define ZLG7290 0x70
#define SubKey 0x01
#define SubCmdBuf 0x07
#define SubDpRam 0x10
unsigned char key_code=0x4d;
unsigned char key_press=0;
void ZLG7290_ReadData(unsigned char address,unsigned char num)
{
IRcvStr(ZLG7290,address,I2C_temp,num);
}
void Read_ZLG7290Key(void)
{
char usTmp;
ZLG7290_ReadData(1,1);
usTmp=I2C_temp[0];
if(usTmp !=0)
{
if(usTmp<0x41)
{
key_press=1;
key_code=usTmp;
}
}
}
void delayMS(unsigned int i)
{
unsigned int j,k;
for(k=0;k<i;k++)
for(j=0;j<60;j++);
}

这个芯片需要使用I2C总线,所以我们需要添加I2C总线的底层文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include "I2C.H"
#include <REG52.H>
#include <intrins.h>
#define NOP() _nop_()
#define _Nop() _nop_()
sbit SCL=P1^6;
sbit SDA=P1^7;
bit ack;
unsigned char I2C_temp[8]={"01234567"};
void Start_I2c()
{
SDA=1;
_Nop();
SCL=1;
I2C_delay(10);
SDA=0;
I2C_delay(10);
SCL=0;
I2C_delay(10);
}
void Stop_I2c()
{
SDA=0;
_Nop();
SCL=1;
I2C_delay(10);
SDA=1;
I2C_delay(10);
}
void SendByte(unsigned char c)
{
unsigned char BitCnt;
for(BitCnt=0;BitCnt<8;BitCnt++)
{
if((c<<BitCnt)&0x80)SDA=1;
else SDA=0;
_Nop();
SCL=1;
I2C_delay(10);
SCL=0;
I2C_delay(10);
}
I2C_delay(10);
SDA=1;
I2C_delay(10);
SCL=1;
I2C_delay(10);
if(SDA==1)ack=0;
else ack=1;
SCL=0;
I2C_delay(10);
}
unsigned char RcvByte()
{
unsigned char retc;
unsigned char BitCnt;
retc=0;
SDA=1;
for(BitCnt=0;BitCnt<8;BitCnt++)
{
I2C_delay(10);
SCL=0;
I2C_delay(10);
SCL=1;
I2C_delay(10);
retc=retc<<1;
if(SDA==1)retc=retc+1;
I2C_delay(10);
}
SCL=0;
I2C_delay(10);
return(retc);
}
void Ack_I2c(bit a)
{
if(a==0)SDA=0;
else SDA=1;
I2C_delay(10);
SCL=1;
I2C_delay(10);
SCL=0;
I2C_delay(10);
}
bit IRcvStr(unsigned char sla,unsigned char suba,unsigned char *s,unsigned char no)
{
unsigned char i;
Start_I2c();
SendByte(sla);
if(ack==0)return(0);
SendByte(suba);
if(ack==0)return(0);
Start_I2c();
SendByte(sla+1);
if(ack==0)return(0);
for(i=0;i<no-1;i++)
{
*s=RcvByte();
Ack_I2c(0);
s++;
}
*s=RcvByte();
Ack_I2c(1);
Stop_I2c();
return(1);
}
void I2C_Init(void)
{
SCL=1;
SDA=1;
}
void I2C_delay(unsigned char dly)
{
while(dly--);
}

用户层

键值扫描的程序,使用hc165和zlg7290,读回键值并做一次转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include<reg52.h>
#include<lcd.h>
#include<hc165.h>
#include<keyscan_basic.h>
extern unsigned char step;
extern unsigned char state;
int keyscan_transfer(unsigned char key) {
int keyscan_transfer = 0;
switch(key) {
case 0x01:keyscan_transfer = 7; break;
case 0x02:keyscan_transfer = 8; break;
case 0x03:keyscan_transfer = 9; break;
case 0x04:keyscan_transfer = 10; step = 1; break;
case 0x05:keyscan_transfer = 4; break;
case 0x06:keyscan_transfer = 5; break;
case 0x07:keyscan_transfer = 6; break;
case 0x08:keyscan_transfer = 11; step = 1; break;
case 0x09:keyscan_transfer = 1; break;
case 0x0A:keyscan_transfer = 2; break;
case 0x0B:keyscan_transfer = 3; break;
case 0x0C:keyscan_transfer = 12; step = 1; break;
case 0x0D:keyscan_transfer = 0; break;
case 0x0E:keyscan_transfer = 15; state = 1; break;
case 0x0F:step = 3;keyscan_transfer = 14; break;
case 0x10:keyscan_transfer = 13; step = 1; break;
}
return keyscan_transfer;
}
#include<reg52.h>
#include<lcd.h>
#include<hc165.h>
#include<keyscan_advance.h>
#include<zlg7290.h>
extern int tempNumtransfer_1;
extern int tempNumtransfer_2;
extern int tempKeytransfer_1;
extern int tempKeytransfer_2;
extern unsigned char step;
extern unsigned char result;
extern int num_temp;
void key_function(void) {
unsigned char key_function;
key_function = hc165_re();
Delay1ms(100);
key_function = hc165_re();
switch(key_function){
case 0xfd: clear_step(); break;
}
}
void clear_step(void) {
tempNumtransfer_1 = 0;
tempNumtransfer_2 = 0;
tempKeytransfer_1 = 0;
tempKeytransfer_2 = 0;
step = 0;
disp_line(0);
LcdWrCmd(0x01);
LcdWrCmd(0x80);
key_press = 0;
num_temp = 0;
}

计算器核心计算功能实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include<reg52.h>
#include<keyscan_basic.h>
#include<hc165.h>
int num_temp = 0;
int dec_temp = 0;
unsigned char buf[12];
extern unsigned char step;
unsigned char LongToString(int dat){
signed char i = 0;
unsigned char len = 0;
if (dat < 0){
dat = -dat;
buf[0] = '-';
i++;
len++;
}
do {
buf[i] = dat % 10;
i++;
dat /= 10;
} while (dat > 0);
len += i;
return len;
}
int number_transfer(int key_transfer) {
num_temp = num_temp*10 + key_transfer;
return num_temp;
}
int number_calculate(int num_1, int num_2,unsigned char key) {
int result;
switch(key){
case 0x04: result = num_1 + num_2; break;
case 0x08: result = num_1 - num_2; break;
case 0x0C: result = num_1 * num_2; break;
case 0x10: result = num_1 / num_2; break;
}
return result;
}
int decimal_transfer(int key_transfer) {
dec_temp = dec_temp * 10 + key_transfer;
return dec_temp;
}

显示功能设计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<hc165.h>
#include<calc.h>
#include<lcd.h>
#include<display.h>
unsigned char state = 0;
extern unsigned char buf[12];
unsigned char code ascii[]={"0123456789"};
extern int temp;
extern unsigned char step;
int lcd_numdisplay(int num){
unsigned char arrayLength;
unsigned char i;
arrayLength = LongToString(num);
for(i = 0; i < arrayLength; i++) {
if(step == 0 || step == 2 || step == 3)
LcdWrData(ascii[buf[arrayLength-i-1]]);
}
return arrayLength;
}
void lcd_flagdisplay(int num) {
switch(num) {
case 10: LcdWrData('+'); break;
case 11: LcdWrData('-'); break;
case 12: LcdWrData('*'); break;
case 13: LcdWrData('/'); break;
}
step = 2;
}
void decimal_display(void) {
LcdWrData('.');
}

主函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include<reg52.h>
#include<lcd.h>
#include<hc165.h>
#include<display.h>
#include<calc.h>
#include<I2C.h>
#include<zlg7290.h>
#include<keyscan_basic.h>
#include<keyscan_advance.h>
int tempKeytransfer_1 = 0;
int tempNumtransfer_1 = 0;
int tempKeytransfer_2 = 0;
int tempNumtransfer_2 = 0;
int tempKeytransfer_3 = 0;
int tempNumtransfer_3 = 0;
int tempKeytransfer_4 = 0;
int tempNumtransfer_4 = 0;
int resultTransfer;
int result = 0;
int decimal_result = 0;
unsigned char decimalFlag_1 = 0;
unsigned char decimalFlag_2 = 0;
unsigned char step = 0;
int temp_useless;
int temp_decimal_useless;
int temp;
int temp_decimal;
extern int num_temp;
extern int dec_temp;
extern unsigned char state;
void main(){
unsigned char tempKeycode;
hc165_init();
I2C_Init();
LcdInit();
while(1){
Read_ZLG7290Key();
delayMS(200);
key_function();
if(key_press == 1 && step == 0 && state == 0) {
key_press = 0;
tempKeytransfer_1 = keyscan_transfer(key_code);
if(key_code != 0x04 && key_code != 0x08 && key_code != 0x0C && key_code != 0x10 && key_code != 0x0E) {
tempNumtransfer_1 = number_transfer(tempKeytransfer_1);
disp_line(0);
temp = lcd_numdisplay(tempNumtransfer_1);
}
LcdWrCmd(0x80+temp);
}
if(key_press == 1 && step == 0 && state == 1){
key_press = 0;
LcdWrCmd(0x80+temp);
decimal_display();
tempKeytransfer_3 = keyscan_transfer(key_code);
if(key_code != 0x04 && key_code != 0x08 && key_code != 0x0C && key_code != 0x10 && key_code != 0x0E) {
tempNumtransfer_3 = decimal_transfer(tempKeytransfer_3);
temp_decimal = lcd_numdisplay(tempNumtransfer_3) + 1;
}
LcdWrCmd(0x80+temp+temp_decimal);
decimalFlag_1 = 1;
}
if(key_press == 1 && step == 1) {
key_press = 0;
tempKeycode = key_code;
lcd_flagdisplay(keyscan_transfer(key_code));
num_temp = 0;
dec_temp = 0;
state = 0;
}
if(key_press == 1 && step == 2 && state == 0) {
key_press = 0;
LcdWrCmd(0x80+temp+temp_decimal+1);
tempKeytransfer_2 = keyscan_transfer(key_code);
if(key_code != 0x0F && key_code != 0x0E){
tempNumtransfer_2 = number_transfer(tempKeytransfer_2);
temp_useless = lcd_numdisplay(tempNumtransfer_2);
}
LcdWrCmd(0x80+temp+temp_decimal+temp_useless+1);
decimalFlag_2 = 1;
}
if(key_press == 1 && step == 2 && state == 1){
key_press = 0;
LcdWrCmd(0x80+temp+temp_decimal+temp_useless+1);
decimal_display();
tempKeytransfer_4 = keyscan_transfer(key_code);
if(key_code != 0x0F && key_code != 0x0E) {
tempNumtransfer_4 = decimal_transfer(tempKeytransfer_4);
temp_decimal_useless = lcd_numdisplay(tempNumtransfer_4);
}
}
if(step == 3){
if(decimalFlag_1 * decimalFlag_2 == 0) {
result = number_calculate(tempNumtransfer_1,tempNumtransfer_2,tempKeycode);
disp_line(1);
temp_useless = lcd_numdisplay(result);
}
if(decimalFlag_1 * decimalFlag_2 == 1) {
result = number_calculate(tempNumtransfer_1,tempNumtransfer_2,tempKeycode);
decimal_result = number_calculate(tempNumtransfer_3,tempNumtransfer_4,tempKeycode);
disp_line(1);
if(decimal_result > 9) {
temp = lcd_numdisplay(result+1);
LcdWrCmd(0x80+0x40+temp);
decimal_display();
LcdWrCmd(0x80+0x40+temp+1);
temp_useless = lcd_numdisplay(decimal_result%10);
}
else {
temp = lcd_numdisplay(result);
LcdWrCmd(0x80+0x40+temp);
decimal_display();
LcdWrCmd(0x80+0x40+temp+1);
temp_useless = lcd_numdisplay(decimal_result);
}
}
}
}
}

小结

具体实现的思路大概就是前文讲的那样,可是程序结构的设计相当欠考虑,有很多做的不到位的地方,看起来很傻,耗费的时间太多。就这样。