基于单片机实现的四则运算计算器姓名学号:班级:专业名称:测控技术与仪器指导教师:东北大学2016年1月课程设计(论文)任务书目录课程设计(论文)任务书................................................................................................ i i 摘要 (1)第1章绪论 (2)1.1计算器简介 (2)1.2设计主要内容 (2)第2章系统硬件设计 (4)2.1硬件组成 (4)2.2输入模块 (4)2.3输出模块 (5)2.4运算模块 (5)第3章系统软件设计 (7)3.1 主程序 (7)3.1.1主程序框图及初始化 (7)3.1.2LCD程序框图及初始化 (8)3.1.3键盘程序框图及初始化 (9)3.1.4运算程序框图 (10)第4章调试测试与实验分析 (11)4.1 计算器调试 (11)参考文献 (12)心得体会 (13)附录硬件原理图及PCB图 (14)附录程序清单 (15)附录实物照片 (28)摘要单片机的出现是计算机制造技术高速发展的产物,它是嵌入式控制系统的核心,如今,它已广泛的应用到我们生活的各个领域,电子、科技、通信、汽车、工业等。
本设计是基于89C52RC单片机来进行的四则运算计算器系统设计,可以完成计算器的键盘输入,进行加、减、乘、除的基本四则运算,并在LCD1602液晶显示屏上显示相应的结果。
本电路采用89C52RC单片机为主要控制电路,利用4*4矩阵键盘作为计算器以及运算符的输入。
显示采用字符LCD静态显示。
软件用C语言编程,并用开发板进行演示。
关键词:计算器,89C52RC单片机,LCD,矩阵键盘第1章绪论1.1计算器简介计算器是现代人们发明的可以进行数字运算的电子机器。
现代的电子计算器能进行数学运算的手持电子机器,除显示计算结果外,还常有溢出指示、错误指示等,拥有集成电路芯片,但结构比电脑简单得多,可以说是第一代的电子计算机(电脑),且功能也较弱,但较为方便与廉价,可广泛运用于商业交易和学习计算中,是必备的学习和办公用品之一[1]。
1.2设计主要内容1.2.1设计概述本系统使用89C52RC单片机作为主控芯片,通过矩阵键盘输入,进行运算,并在LCD 上显示相应的数字和结果,主要功能如下:(1) LCD第一行显示运算符号之前的数字,第二行显示运算符号和运算符号之后的数字,按下等号键得到结果。
(2)在任何时候按下清零键则清零。
(3)当运算完第一次完整的计算之后,可以在不按任何键的情况下,进行新的一轮计算。
1.2.2设计思路本系统采用MCS-52系列单片机作为主控机,通过拓展必要的外围电路,实现对计算器的设计,具体设计如下:(1)因为想显示运算符号,用数码管不能很好的显示出来,所以采用LCD1602液晶显示器作为显示模块。
(2)计算器一般包含数字键(0~9),符号键(+,—,*,/),等号键,清零键。
故采用4*4矩阵键盘结合键盘扫描技术来达到要求。
(3)开机后1602显示‘0’,等待按键输入,当键入数字,在LCD上显示出来,计算器内部把数据储存起来,并等待下一按键输入,当键入运算符号时,计算器内部步数加1,然后等待再次输入数值,按等于号就在LCD上显示结果。
(4)清零功能:在运算的任何时候键入清零键,则把数据清零,等待下一次运算。
1.2.3电路图设计根据给的开发板的原理图在protel中设计并连接电路图,分析电路图的正确性,输出PCB图。
1.2.4软件的编程利用C语言编程,分模块测试板子的好坏,然后编写程序,在keil中进行调试。
第2章系统硬件设计2.1硬件组成硬件以89C52单片机为核心,外部扩展用LCD实现显示功能,用4*4矩阵键盘实现输入功能。
基本硬件结构图如图2.1所示。
图2.1 四则运算计算器基本硬件结构图2.2输入模块计算器输入数字和其他功能按键要用到很多按键,如果采用独立按键的方式,在这种情况下,编程会简单,但I/0口不够用,所以采用矩阵键盘形式,本次采用4*4矩阵键盘的形式,采用四条I/O 线作为行线,四条I/O 线作为列线组成键盘。
在行线和列线的每个交叉点上设置一个按键,这样键盘上按键的个数就为4×4个。
图2-2输入模块电路图矩阵键盘功能:预设16个键位,分别是0~9数字键,加减乘除,等于和清零功能。
单片机不停扫描键盘当发现有按键按下时,若为数字键则在lcd上显示,功能键则实现对应的功能。
例如,按下5时,lcd上显示数字5,按下加号,即实现加法功能也在lcd 上显示加号。
2.3输出模块采用LCD1602来显示字符和数字,1602能够同事显示16*02即32个字符。
1602液晶模块内部的字符发生储存器已经储存了160个不同的点阵字符图形,这些字符有:阿拉伯数字,英文字母大小写,常用的符号等,每一个字符有一个固定的代码。
在对液晶模块的初始化中要先设置其显示模式,在液晶模块显示字符时光标是自动右移的。
每次输入指令前都要判断液晶模块是否处于忙的状态。
图2-3输出模块电路图lcd1602能够同时显示16*2个字符,用它接收单片机送来的信号,然后在上边显示相应的数字或符号,实现显示功能。
2.4运算模块单片机是靠程序运行的,并且可以修改。
通过不同的程序实现不同的功能,尤其是特殊的独特的一些功能,通过上网查找运算代码作参考,然后对单片机进行程序编写,从而实现运算功能。
第3章系统软件设计3.1 主程序3.1.1主程序框图及初始化主程序框图如图3.1所示图3.1 主程序框图主程序说明:接通电源后,先让lcd显示屏初始化显示0,然后通过键盘扫描看是否有键按下,若有键按下,则分析按下键的性能,若为数字键或清零键则通过lcd显示,若为功能键,实现功能然后lcd显示。
3.1.2LCD程序框图及初始化图3.2 LCD程序框图Lcd初始化显示0,开始后判断是否有键按下,有键按下判断是否是数字键,若为数字键则显示键值,若不是,则为功能键,再判断功能键是否是等号,若为等号,则显示计算结果,若不是实现功能,等待输入。
3.1.3键盘程序框图及初始化图3.3 键盘程序框图开始后,扫描键盘,若没有键按下则继续扫描,有键按下,若为等号键则显示结果然后结束,若不是看是否是计算符,不是计算符则读入内存然后lcd显示,若是计算符读进内存,等待输入。
3.1.4运算程序框图图3.4 运算程序框图运算程序:先判断运算符,若为加或乘则要判断运算结果是否溢出,若溢出,则lcd 显示-1表示运算错误,若为除号,判断除数是否为0,若为0,则lcd显示-1表示运算错误,其他情况则可直接送至lcd显示。
东北大学课程设计报告第4章调试测试与实验分析第4章调试测试与实验分析4.1 计算器调试(1)问题:将已编好的程序用烧录进开发板后,发现开发板上LCD显示屏只发光但是没有任何数字。
(2)分析:这种现象可能存在两个问题:1)LCD显示屏有问题2)程序存在错误(3)解决方案:重新检查开发板元器件,将开发版资料已给的1602液晶显示程序烧录开发板中,发现LCD仍然只发光不显示任何东西,初步判定是LCD未调节好或者故障,经检测是LCD显示字符的亮度未调节合适,拿螺丝刀调节,LCD就能显示字符。
(4)再次检测:再次将已编好的程序烧录进开发板,打开开发板电源,LCD上先显示‘0’字符。
矩阵键盘输入‘3*4’进行计算,发现显示屏上第一行显示‘3’,第二行显示‘4’和‘*’,然后在键盘上输入‘=’,显示屏上得出结果12。
东北大学课程设计报告参考文献参考文献[1]李小坚郝晓丽.protel DXP电路设计与制版实用教程[J].人民邮电出版社, 2015,10.[2]张秋菊刘继超. 单片机应用实训教程[J]. 化学工业出版社, 2015,10.东北大学课程设计报告心得体会心得体会周二早上开始选课题的时候,感觉我们的能力不是很强,所以想选一个简单一点的题目,然后就选了一个四则运算计算器,这个题目硬件上的确不难,但是软件上却令我们很困扰,很头疼。
周二下午写计划的时候,看到被老师打回来这么多份计划,我们就想自己好好的写一写,可是还是改了两遍,也用了一晚上的时间,但是这个计划却对我们之后几天的工作有一个很好的指导。
第二天学习protel,老师没有怎么讲,我们一开始也不会用,只能是哪里不会哪里百度,但是有的元件还是没有找到,周四早上才知道原来资料里有单片机上的元件库,于是把那个元件库导入,画图便容易多了。
我们硬件没有什么东西,于是第三天下午便开始编程了,可是我们对着程序看了一下午也没有什么头绪,反而整得自己要发疯了一样,对课设也失去了信心。
后来老师来检查的时候告诉我们要一步一步的调试,不可能一口吃个大胖子,我们就把程序分开来调试,通过从图书馆借来的资料一步一步的来,程序中不懂的地方便查书或者通过网络解决。
这几天的确是蛮痛苦的,天天早晨起来到教室里,一坐一天,感觉比上课累多了,可也有了自己的收获,没有老师指导,需要的东西基本上要自己来查找,也锻炼了自己的能力。
实物的焊接也锻炼了我们的动手能力,课程设计对于我们的益处是很大的。
也希望自己通过知识与实际的结合不断的提高自己。
附录硬件原理图及PCB图附录程序清单LCD1602#include <reg52.h>#define LCD1602_DB P0sbit LCD1602_RS = P1^0;sbit LCD1602_RW = P1^1;sbit LCD1602_E = P1^2;/* 等待液晶准备好*/void LcdWaitReady(){unsigned char sta;LCD1602_DB = 0xFF;LCD1602_RS = 0;LCD1602_RW = 1;do {LCD1602_E = 1;sta = LCD1602_DB; //读取状态字LCD1602_E = 0;} while (sta & 0x80); //bit7等于1表示液晶正忙,重复检测直到其等于0为止}/* 向LCD1602液晶写入一字节命令,cmd-待写入命令值*/void LcdWriteCmd(unsigned char cmd){LcdWaitReady();LCD1602_RS = 0;LCD1602_RW = 0;LCD1602_DB = cmd;LCD1602_E = 1;LCD1602_E = 0;}/* 向LCD1602液晶写入一字节数据,dat-待写入数据值*/void LcdWriteDat(unsigned char dat){LcdWaitReady();LCD1602_RS = 1;LCD1602_RW = 0;LCD1602_DB = dat;LCD1602_E = 1;LCD1602_E = 0;}/* 设置显示RAM起始地址,亦即光标位置,(x,y)-对应屏幕上的字符坐标*/ void LcdSetCursor(unsigned char x, unsigned char y){unsigned char addr;if (y == 0) //由输入的屏幕坐标计算显示RAM的地址addr = 0x00 + x; //第一行字符地址从0x00起始elseaddr = 0x40 + x; //第二行字符地址从0x40起始LcdWriteCmd(addr | 0x80); //设置RAM地址}/* 在液晶上显示字符串,(x,y)-对应屏幕上的起始坐标,str-字符串指针*/ void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str){LcdSetCursor(x, y); //设置起始地址while (*str != '\0') //连续写入字符串数据,直到检测到结束符{LcdWriteDat(*str++);}}/* 区域清除,清除从(x,y)坐标起始的len个字符位*/void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len) {LcdSetCursor(x, y); //设置起始地址while (len--) //连续写入空格{LcdWriteDat(' ');}}/* 整屏清除*/void LcdFullClear(){LcdWriteCmd(0x01);}/* 初始化1602液晶*/void InitLcd1602(){LcdWriteCmd(0x38); //16*2显示,5*7点阵,8位数据接口LcdWriteCmd(0x0C); //显示器开,光标关闭LcdWriteCmd(0x06); //文字不动,地址自动+1LcdWriteCmd(0x01); //清屏}矩阵键盘#include <reg52.h>sbit KEY_IN_1 = P2^4;sbit KEY_IN_2 = P2^5;sbit KEY_IN_3 = P2^6;sbit KEY_IN_4 = P2^7;sbit KEY_OUT_1 = P2^3;sbit KEY_OUT_2 = P2^2;sbit KEY_OUT_3 = P2^1;sbit KEY_OUT_4 = P2^0;unsigned char code KeyCodeMap[4][4] = { //矩阵按键编号到标准键盘键码的映射表{ '1', '2', '3', 0x26 }, //数字键1、数字键2、数字键3、向上键{ '4', '5', '6', 0x25 }, //数字键4、数字键5、数字键6、向左键{ '7', '8', '9', 0x28 }, //数字键7、数字键8、数字键9、向下键{ '0', 0x1B, 0x0D, 0x27 } //数字键0、ESC键、回车键、向右键};unsigned char pdata KeySta[4][4] = { //全部矩阵按键的当前状态{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}};extern void Reset();extern void GetResult();extern void NumKeyAction(unsigned char n);extern void OprtKeyAction(unsigned char type);extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);void KeyAction(unsigned char keycode){if((keycode >= '0') && (keycode <= '9')){NumKeyAction(keycode - '0');}else if(keycode == 0x26){OprtKeyAction(0);}else if(keycode == 0x28){OprtKeyAction(1);}else if(keycode == 0x25){OprtKeyAction(2);}else if(keycode == 0x27){OprtKeyAction(3);}else if(keycode == 0x0D){GetResult();}else if(keycode == 0x1B){Reset();LcdShowStr(15, 1, "0");}}/* 按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用*/ void KeyDriver(){unsigned char i, j;static unsigned char pdata backup[4][4] = { //按键值备份,保存前一次的值{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1} };for (i=0; i<4; i++) //循环检测4*4的矩阵按键{for (j=0; j<4; j++){if (backup[i][j] != KeySta[i][j]) //检测按键动作{if (backup[i][j] != 0) //按键按下时执行动作{KeyAction(KeyCodeMap[i][j]); //调用按键动作函数}backup[i][j] = KeySta[i][j]; //刷新前一次的备份值}}}}/* 按键扫描函数,需在定时中断中调用,推荐调用间隔1ms */void KeyScan(){unsigned char i;static unsigned char keyout = 0; //矩阵按键扫描输出索引static unsigned char keybuf[4][4] = { //矩阵按键扫描缓冲区{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF},{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF} };//将一行的4个按键值移入缓冲区keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;//消抖后更新按键状态for (i=0; i<4; i++) //每行4个按键,所以循环4次{if ((keybuf[keyout][i] & 0x0F) == 0x00){ //连续4次扫描值为0,即4*4ms内都是按下状态时,可认为按键已稳定的按下KeySta[keyout][i] = 0;}else if ((keybuf[keyout][i] & 0x0F) == 0x0F){ //连续4次扫描值为1,即4*4ms内都是弹起状态时,可认为按键已稳定的弹起KeySta[keyout][i] = 1;}}//执行下一次的扫描输出keyout++; //输出索引递增keyout &= 0x03; //索引值加到4即归零switch (keyout) //根据索引,释放当前输出引脚,拉低下次的输出引脚{case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;default: break;}}主函数#include <reg52.h>unsigned char step = 0;unsigned char oprt = 0;signed long num1 = 0;signed long num2 = 0;signed long result = 0;unsigned char T0RH = 0;unsigned char T0RL = 0;void ConfigTimer0(unsigned int ms);extern void KeyScan();extern void KeyDriver();extern void InitLcd1602();extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str); extern void LcdFullClear();extern void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len);void main(){EA = 1; //开总中断ConfigTimer0(1); //配置T0定时1msInitLcd1602(); //初始化液晶LcdShowStr(15, 1, "0"); //初始显示一个数字0while (1){KeyDriver(); //调用按键驱动}}unsigned char LongToString(unsigned char *str, signed long dat) {signed char i = 0;unsigned char len = 0;unsigned char buf[12];if(dat < 0){dat = -dat;*str++ = '-';len++;}do{buf[i++] = dat % 10;dat /= 10;}while(dat > 0);len += i;while(i-- > 0){*str++ = buf[i] + '0';}*str = '\0';return len;}void ShowOprt(unsigned char y, unsigned char type){switch(type){case 0: LcdShowStr(0, y, "+"); break;case 1: LcdShowStr(0, y, "-"); break;case 2: LcdShowStr(0, y, "*"); break;case 3: LcdShowStr(0, y, "/"); break;default: break;}}void Reset(){num1 = 0;num2 = 0;step = 0;LcdFullClear();}void NumKeyAction(unsigned char n){unsigned char len = 0;unsigned char str[12];if(step > 1){Reset();}if(step == 0){num1 = num1*10 + n;len = LongToString(str, num1);LcdShowStr(16 - len, 1, str);}else{num2 = num2*10 + n;len = LongToString(str, num2);LcdShowStr(16 - len, 1, str);}}void OprtKeyAction(unsigned char type) {unsigned char len;unsigned char str[12];if(step == 0){len = LongToString(str, num1);LcdAreaClear(0, 0, 16-len);LcdShowStr(16-len, 0, str);ShowOprt(1, type);LcdAreaClear(1, 1, 14);LcdShowStr(15, 1, "0");oprt = type;step = 1;}}void GetResult(){unsigned char len;unsigned char str[12];if(step == 1){step = 2;switch(oprt){case 0: result = num1 + num2; break;case 1: result = num1 - num2; break;case 2: result = num1 * num2; break;case 3: result = num1 / num2; break;default: break;}len = LongToString(str, num2);ShowOprt(0, oprt);LcdAreaClear(1, 0, 16-1-len);LcdShowStr(16-len, 0, str);len = LongToString(str, result);LcdShowStr(0, 1, "=");LcdAreaClear(1, 1, 16-1-len);LcdShowStr(16-len, 1, str);}}/* 配置并启动T0,ms-T0定时时间*/void ConfigTimer0(unsigned int ms){unsigned long tmp; //临时变量tmp = 11059200 / 12; //定时器计数频率tmp = (tmp * ms) / 1000; //计算所需的计数值tmp = 65536 - tmp; //计算定时器重载值tmp = tmp + 12; //补偿中断响应延时造成的误差T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节T0RL = (unsigned char)tmp;TMOD &= 0xF0; //清零T0的控制位TMOD |= 0x01; //配置T0为模式1TH0 = T0RH; //加载T0重载值TL0 = T0RL;ET0 = 1; //使能T0中断TR0 = 1; //启动T0}/* T0中断服务函数,执行按键扫描*/ void InterruptTimer0() interrupt 1{TH0 = T0RH; //重新加载重载值TL0 = T0RL;KeyScan(); //按键扫描}附录实物照片加法:减法:乘法:除法:。