课程设计报告
题 目∶ 院 系∶ 专业班级∶ 姓 名∶ 学 号∶ 指 导 老 师
2015年 1 月13日
简易计算器设计 机械与电子工程学院
- 1 -
概 要
随着时代的进步和发展,单片机技术已经普及到我们生活、工作、科研等各个领域,已经成为一种比较成熟的技术,本文将介绍一种用单片机设计的简单计算器。
这个计算器除了会加减乘除的运算功能以外,还具有连续运算的功能。
本文首先简单描述了硬件系统的工作原理,且附以硬件系统的设计框图,论述了本次课程设计所应用的硬件接口技术和各个接口模块的功能及工作过程, 并具体描述了外接电路接口的软、硬件调试。
其次阐述了程序的流程和实现过程。本次课程设计就是以C51来进行软件设计,软件的设计采用模块化结构,使程序设计的逻辑关系更加简洁明了。使硬件在软件的控制下协调运作。
本文撰写的主导思想是软、硬件相结合,以硬件为基础,来进行各功能模块的编写。
关键词: 计算器,单片机,AT89C51,汇编语言,模块化
- 1 -
目 录
概 要-----------------------------------------------------------------1 第一章 课程设计选题及设计要求-------------------------------------------3 1.1课程设计选题--------------------------------------------------------3 1.2课题设计要求---------------------------------------------------------3 第二章 方案选择----------------------------------------------------------4 2.1单片机芯片的选择方案-------------------------------------------------4 2.2 显示模块选择方案----------------------------------------------------4 2.3 电路设计最终方案----------------------------------------------------4
第三章 硬件电路设计------------------------------------------------------ 5 - 3.1硬件电路设计图-------------------------------------------------------- 5 -
3.2 主要单元电路介绍----------------------------------------------------6 3.2.1 单片机最小系统----------------------------------------------------6
3.2.2 键盘控制电路----------------------------------------------------7 3.2.3 LCD显示电路-------------------------------------------------------错误!未定义书签。
第四章 软件编程设计----------------------------------------------------13 4.1流程图--------------------------------------------------------------- 13 -
4.2 设计思路分析--------------------------------------------------------- 13 -
4.3 源程序--------------------------------------------------------------15 第五章 调试程序-----------------------------------------------------------------27 5.1 调试结果分析-------------------------------------------------------27 5.2存在的问题及解决方法------------------------------------------------28 设计总结及体会-----------------------------------------------------------30 参考文献-----------------------------------------------------------------31
- 2 -
第一章 课程设计选题与功能要求
1.1 课程设计选题:
此次课程设计包含九个课题,存在几个比较难得课题但大部分还是算比较简单的,像
简易数字示波器就比较有难度,因为涉及的模块比较多,像AD采集转换、LCD12864,而且自己对LCD12864不是很熟悉,在一个礼拜的课程设计的时间里很难熟练地掌握和应用,故没有选择;课题中相对来说也存在几个简单的课题,像灯光控制器,交通灯,因为自己先前做过类似的实验,所以没有选择。
在考虑课题难度及时间的情况下,再综合自己的实践能力,我就选择难度不是很大的“简易计算器设计”这一个课题。一是因为自己对这个课题感兴趣,而且经常在学习中使用;二是自己对这个课题有一定的理论基础和完成这个课题的构思,所以就定下了这课题。
1.2 课题设计要求
1.能进行多字节的整数的单独运算,结果用十进制的数字显示; 2.尝试编写连加、连减的运算程序并进行演示; 3.尝试编写混合的运算程序并进行演示; 4.尝试编写实数的连加、连减运算程序。
- 3 -
第二章 方案选择
2.1单片机芯片的选择:
方案一:
采用89C51芯片作为硬件核心,采用Flash ROM,内部具有4KB ROM 存储空间,能于3V的超低压工作。AT89C51是一种带4K字节闪烁可编程可擦除只读存储器(FPEROM—Falsh
Programmable and Erasable Read Only Memory)的低电压,高性能、CMOS、8位单片机。该器件采用ATMEL高密度非易失存储器制造技术制造,而且与MCS-51系列单片机完全兼容。
2.2 显示模块的选择:
方案一:
采用点阵式数码管显示,点阵式数码管是由八行八列的发光二极管组成,对于显示文字比较适合,如采用在显示数字显得太浪费,且价格也相对较高,所以不用此种作为显示.
方案二:
采用LED数码管动态扫描,虽然LED数码管价格适中,但要显示多个数字所需要的个数偏多,功耗较大,所以也不用此种作为显示。 方案三:
采用LCD液晶显示屏,液晶显示屏的显示功能强大,可显示大量文字,图形,显示多样,清晰可见,与普通数码管相比功耗较小,硬件连接简单。所以显示部分采用1602液晶。
2.3 电路设计最终方案:
综上各方案所述,对本次设计的方案选定为:
1.采用AT89C51作为主控制系统;
2.4X4矩阵键盘和独立键盘作为输入模块; 3.1602液晶作为显示模块。
- 4 -
第三章 硬件电路设计
3.1总体框图设计
1.本设计里用到的单片机芯片是AT89C51芯片,除此之外还包括:晶振电路和复位电路构成单片机最小应用系统;
2.4x4矩阵键盘; 3.LCD显示电路。
总体设计框图
- 5 -
3.2 主要单元电路
3.2.1 TC89C52RC 单片机介绍
STC89C52RC 单片机是宏晶科技推出的新一代高速/低功耗/超强抗干扰的单 片机,指令代码完全兼容传统 8051 单片机,12 时钟/机器周期和 6 时钟/机器周 期可以任意选择。 主要特性如下:
1. 增强型 8051 单片机,6 时钟/机器周期和 12 时钟/机器周期可以任 意选择,指令代码完全兼容传统 8051.
2. 工作电压:5.5V~3.3V(5V 单片机)/3.8V~2.0V(3V 单片机)
3. 工作频率范围:0~40MHz,相当于普通 8051 的 0~80MHz,实际工 作频率可达 48MHz 4. 用户应用程序空间为 8K 字节 5. 片上集成 512 字节 RAM
6. 通用 I/O 口 (32 个) 复位后为: , P1/P2/P3/P4 是准双向口/弱上拉, P0 口是漏极开路输出,作为总线扩展用时,不用加上拉电阻,作为 I/O 口用时,需加上拉电阻。 7. ISP(在系统可编程)/IAP(在应用可编程) ,无需专用编程器,无 需专用仿真器,可通过串口(RxD/P3.0,TxD/P3.1)直接下载用户程 序,数秒即可完成一片 8. 具有 EEPROM 功能 9. 具有看门狗功能
10. 共 3 个 16 位定时器/计数器。即定时器 T0、T1、T2
11. 外部中断 4 路,下降沿中断或低电平触发电路,Power Down 模式可 由外部中断低电平触发中断方式唤醒
12. 通用异步串行口(UART) ,还可用定时器软件实现多个 UART 13. 工作温度范围:-40~+85℃(工业级)/0~75℃(商业级) 14. PDIP 封装
STC89C52RC 单片机的工作模式
掉电模式:典型功耗<0.1μA,可由外部中断唤醒,中断返回后,继续执行原 程序 空闲模式:典型功耗 2mA 典型功耗
正常工作模式:典型功耗 4Ma~7mA 典型功耗
掉电模式可由外部中断唤醒,适用于水表、气表等电池供电系统及便携设备 STC89C52RC 引脚功能说明
VCC(40 引脚):电源电压 VS S(20 引脚):接地
P0 端口(P0.0~P0.7 P0.7,39~32 引脚) :P0 口是一个漏极开路的 8 位双向 I/O 口。作为输出端口,每个引脚能驱动 8 个 TTL 负载,对端口 P0 写入 每个引脚能驱动 写入“1”时,可 以作为高阻抗输入。在访问外部程序和数据存储器时 在访问外部程序和数据存储器时,P0 口也可以提供低 8 位 地址和 8 位数据的复用总线 位数据的复用总线。此时,P0 口内部上拉电阻有效。在 Flash ROM 编 在 程时,P0 端口接收指令字节 端口接收指令字节;而在校验程序时,则输出指令字节 则输出指令字节。验证时,要求外接上拉电阻。
P1 端口(P1.0~P1.7,1~8 引脚) :P1 口是一个带内部上拉电阻的 8 位双向 I/O 口。P1 的输出缓冲器可驱动(吸收或者输出电流方式)4 个 TTL 输入。对端 口写入 1 时,通过内部的上拉电阻把端口拉到高电位,这是可用作输入口。P1 口作输入口使用时,因为有内部上拉电阻,那些被外部拉低的引脚会输出一个电 流( ) 。
此外,P1.0 和 P1.1 还可以作为定时器/计数器 2 的外部技术输入(P1.0/T2) 和定时器/计数器 2 的触发输入(P1.1/T2EX) ,具体参见下表:
- 6 -
在对 Flash ROM 编程和程序校验时,P1 接收低 8 位地址。 表 XX P1.0 和 P1.1 引脚复用功能
P2 端口(P2.0~P2.7,21~28 引脚) :P2 口是一个带内部上拉电阻的 8 位双 向 I/O 端口。P2 的输出缓冲器可以驱动(吸收或输出电流方式)4 个 TTL 输入。 对端口写入 1 时,通过内部的上拉电阻把端口拉到高电平,这时可用作输入口。 P2 作为输入口使用时,因为有内部的上拉电阻,那些被外部信号拉低的引脚会 输出一个电流(I) 。
在访问外部程序存储器和 16 位地址的外部数据存储器(如执行“MOVX @DPTR”指令)时,P2 送出高 8 位地址。在访问 8 位地址的外部数据存储器(如 执行“MOVX @R1”指令)时,P2 口引脚上的内容(就是专用寄存器(SFR)区 中的 P2 寄存器的内容) ,在整个访问期间不会改变。 在对 Flash ROM 编程和程序校验期间, P2也接收高位地址和一些控制信号。 P3 端口(P3.0~P3.7,10~17 引脚) :P3 是一个带内部上拉电阻的 8 位双向 I/O 端口。P3 的输出缓冲器可驱动(吸收或输出电流方式)4 个 TTL 输入。对端 口写入 1 时,通过内部的上拉电阻把端口拉到高电位,这时可用作输入口。P3 做输入口使用时,因为有内部的上拉电阻,那些被外部信号拉低的引脚会输入一 个电流( ) 。
在对 Flash ROM 编程或程序校验时,P3 还接收一些控制信号。
P3 口除作为一般 I/O 口外,还有其他一些复用功能,如下表所示: 表 XX P3 口引脚复用功能 复用功能
RST(9 引脚) :复位输入。当输入连续两个机器周期以上高电平时为有效, 用来完成单片机单片机的复位初始化操作。看门狗计时完成后,RST 引脚输出 96 个晶振周期的高电平。特殊寄存器 AUXR(地址 8EH)上的 DISRTO 位可以使此功 能无效。DISRTO 默认状态下,复位高电平有效。 ALE/ ROG (30 引脚) 地址锁存控制信号 : (ALE) 是访问外部程序存储器时, 锁存低 8 位地址的输出脉冲。在 Flash 编程时,此引脚( ROG)也用作编程输入 脉冲。 在一般情况下,ALE 以晶振六分之一的固定频率输出脉冲,可用来作为外部 定时器或时钟使用。然而,特别强调,在每次访问外部数据存储器时,ALE 脉冲 将会跳过。
如果需要,通过将地址位 8EH 的 SFR 的第 0 位置“1” ,ALE 操作将无效。这 一位置“1” ,ALE 仅在执行 MOVX 或 MOV 指令时有效。否则,ALE 将被微弱拉 高。这个 ALE 使能标志位(地址位 8EH 的 SFR 的第 0 位)的设置对微控制器处于 外部执行模式下无效。 :外部程序存储器选通信号( SEN)是外部程序存储器选 SEN(29 引脚)
通信号。当 AT89C51RC 从外部程序存储器执行外部代码时, SEN在每个机器周 期被激活两次,而访问外部数据存储器时, SEN将不被激活。 A/VPP (31 引脚) 访问外部程序存储器控制信号。 : 为使能从 0000H 到 FFFFH 的外部程序存储器读取指令, A必须接 GND。注意加密方式 1 时, A将内部锁 定位 RESET。为了执行内部程序指令, A应该接 VCC。在 Flash 编程期间, A也 接收 12 伏 VPP 电压。 XTAL1(19 引脚) :振荡器反相放大器和内部时钟发生电路的输入端。 XTAL2(18 引脚) :振荡器反相放大器的输入端。
3.2.2 4X4键盘控制电路
矩阵式键盘的结构与工作原理:
在键盘中的按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式,如图2.5所示。在矩阵式键盘中,每条水平线和垂直线在交叉处不直接连通,而是通过一个按键加以连接。这样,只需要单片机的一个端口(如P1口)就可以构成4*4=16个按键,比直接将端口线用于键盘多出了一倍,而且
- 7 -
线数越多,区别越明显,比如再多加一条线就可以构成20键的键盘,而直接用端口线则只能多出一键(9键)。由此可见,在需要的键数比较多时,采用矩阵法来做键盘比较是合理的。
由于本系统按键较多,在这里采用矩阵式4*4键盘,这样可以合理应用硬件资源,用一个8位I/O口控制, 如图2.5所示:
图2.5 按键电路
矩阵式结构的键盘显然比直接法要复杂一些,识别也要复杂一些,图2.5中,列线通过电阻接正电源,并将行线所接的单片机的I/O口作为输出端,而列线所接的I/O口则作为输入。这样,当按键没有按下时,所有的输出端都是高电平,代表无键按下。一旦有键按下,则输入线就会被拉低,行线输出是低电平。这样,通过读入输入线的状态就可得知是否有键按下了。具体的识别及编程方法如2.3.2所述。
矩阵式键盘的工作方法:
为了确定矩阵式键盘上何键被按下,我们采用一种“扫描法”。
扫描法 :扫描法又称为逐行(或列)扫描查询法,是一种最常用的按键识别方法。以图2.5所示键盘电路为例,介绍过程如下:
1.判断键盘中有无键按下 将全部行线X0-X3置低电平,然后检测列线的状态。只要有一列的电平为低,则表示键盘中有键被按下,而且闭合的键位于低电平线与4根行线相交叉的4个按键之中。若所有列线均为高电平,则键盘中无键按下。
2.判断闭合键所在的位置 在确认有键按下后,即可进入确定具体闭合键的过程。其方法是:依次将行线置为低电平,即在置某根行线为低电平时,其它行线为高电平。在确定某根行线为低电平后,再逐行检测各列线的电平状态。若某列为低,则该列线与置为低电平的行线交叉处的按键就是闭合的按键。
如图2.5所示。AT89C51单片机的P1口用作键盘I/O口,键盘的列线接到P1口的高4位,键盘的行线接到P1口的低4位。列线P14-P17分别接有4个上拉电阻到正电源+5V,并把行线P10-P13设置为输出线,列线P14-P17设置为输入线。4根行线和4根列线形成16个相交点。
1.检测当前是否有键被按下。检测的方法是P10-P13输出全“0”,读取P14-P17的状态,若P14-P17
- 8 -
为全“1”,则无键闭合,否则有键闭合。
2.去除键抖动。当检测到有键按下后,延时一段时间再做下一步的检测判断。
3.若有键被按下,应识别出是哪一个键闭合。方法是对键盘的行线进行扫描。P10-P13按下述4种组合依次输出:
P13 1 1 1 0 P12 1 1 0 1 P11 1 0 1 1 P10 0 1 1 1
在每组行输出时读取P14-P17,若全为“1”,则表示为“0”这一行没有键闭合,否则有键闭合。由此得到闭合键的行值和列值,然后可采用计算法或查表法将闭合键的行值和列值转换成所定义的键值。
4.为了保证键每闭合一次CPU仅作一次处理,必须消除键释放时的抖动。
3.2.3 LCD显示电路
1. 1602介绍
字符型液晶显示模块是一种专门用于显示字母、数字、符号等点阵式LCD,目前常用16*1,16*2,20*2和40*2行等的模块。下面以长沙太阳人电子有限公司的1602字符型液晶显示器为例,介绍其用法。一般1602字符型液晶显示器实物如图:
图- 4 1602字符型液晶显示器实物图
2. 1602LCD的基本参数及引脚功能
1602LCD分为带背光和不带背光两种,基控制器大部分为HD44780,带背光的比不带背光的厚,是否带背光在应用中并无差别,两者尺寸差别如下图所示:
- 9 -
性能参数:
显示容量:16×2个字符 芯片工作电压:4.5—5.5V 工作电流:2.0mA(5.0V) 模块最佳工作电压:5.0V 字符尺寸:2.95×4.35(W×H)mm
引脚功能说明: 编号
符号
引脚说明
编号 符号 引脚说明
1 VSS 电源地 9 D2 数据 2 VDD 电源正极
10 D3 数据
11 D4 数据
3 VL 液晶显示偏压
4 RS 数据/命令选择 12 D5 数据 5 R/W 读/写选择 13 D6 数据 6 E 使能信号 7 D0 数据 8 D1 数据 第1脚:VSS为地电源。 第2脚:VDD接5V正电源。
第3脚:VL为液晶显示器对比度调整端,接正电源时对比度最弱,接地时对比度最高,对比度过高时 会产生“鬼影”,使用时可以通过一个10K的电位器调整对比度。 第4脚:RS为寄存器选择,高电平时选择数据寄存器、低电平时选择指令寄存器。
第5脚:R/W为读写信号线,高电平时进行读操作,低电平时进行写操作。当RS和R/W共同为低电平
14 D7 数据
15 BLA 背光源正极 16 BLK 背光源负极
时可以写入指令或者显示地址,当RS为低电平R/W为高电平时可以读忙信号,当RS为高电 平R/W为低电平时可以写入数据。
第6脚:E端为使能端,当E端由高电平跳变成低电平时,液晶模块执行命令。
- 10 -
第7~14脚:D0~D7为8位双向数据线。 第15脚:背光源正极。 第16脚:背光源负极。 3. 1602LCD的指令说明及时序
1602液晶模块内部的控制器共有11条控制指令,如表-4所示: 序号
指令
RS R/W D7 D6 D5 D4 D3 D2 D1 D0
1 清显示 0 0 0 0 0 0 0 0 0 1 2 光标返回
0 0 0 0 0 0 0 0 1 *
0 0 0 0 0 0 0 1 I/D S 0 0 0 0 0 0 1 D C B
3 置输入模式 4 显示开/关控制
5 光标或字符移位 0 0 0 0 0 1 S/C R/L * * 6 置功能 0 0 0 0 1 DL N F * * 7 置字符发生存贮器地址0 0 0 1 字符发生存贮器地址 8 置数据存贮器地址
0 0 1 显示数据存贮器地址
9 读忙标志或地址 0 1 BF 计数器地址 10 写数到CGRAM或DDRAM)1 0 要写的数据内容 11 从CGRAM或DDRAM读数1 1 读出的数据内容
表-4:控制命令表
1602液晶模块的读写操作、屏幕和光标的操作都是通过指令编程来实现的。(说明:1为高电平、0为低电平)
指令1:清显示,指令码01H,光标复位到地址00H位置。 指令2:光标复位,光标返回到地址00H。
指令3:光标和显示模式设置 I/D:光标移动方向,高电平右移,低电平左移 屏幕上所有文字是否左 移或者右移。高电平表示有效,低电平则无效。
指令4:显示开关控制。 D:控制整体显示的开与关,高电平表示开显示,低电平表示关显示 C:控 制光标的开与关,高电平表示有光标,低电平表示无光标 B:控制光标是否闪烁,高电平闪烁, 低电平不闪烁。
指令5:光标或显示移位 S/C:高电平时移动显示的文字,低电平时移动光标。
指令6:功能设置命令 DL:高电平时为4位总线,低电平时为8位总线 N:低电平时为单行显示,高
- 11 -
电平时双行显示 F: 低电平时显示5x7的点阵字符,高电平时显示5x10的点阵字符。 指令7:字符发生器RAM地址设置。 指令8:DDRAM地址设置。
指令9:读忙信号和光标地址 BF:为忙标志位,高电平表示忙,此时模块不能接收命令或者数据,如 果为低电平表示不忙。 指令10:写数据。 指令11:读数据。
- 12 -
第四章 软件编程设计
4.1流程图
- 13 -
4.2 设计思路分析
总的来说,我的设计思路主要由以下几个模块来构成的: 1. 实时键盘扫描模块(4*4矩阵键盘和多个独立按键):键盘作为输入设备,提供给使用者使用。因为要做到使用者一按键,系统就能响应,因此此模块需实时进行扫描输入,故此模块使用了一个定时器(T0)进行间隔10ms的扫描。
2. 实时显示模块(LCD1602):1602液晶显示屏用作按键所对应的键值的显示和数据运算结果的输出。因为要实时显示按键所对应的值,故此模块也需通过定时器(T1)进行实时的显示输出。
3. 数据处理模块:此模块又可细分为四个小模块来进行更好的理解;第一个是‘取值存储’模块,第二个是‘运算处理’模块,第三个是‘符号判断’模块,第四个是‘结果输出’模块。此模块的四个小模块为顺序执行。
当一个键盘按下的时候,经过按键程序判断按键所代表的字符,转向显示子程序来显示,并在相应的数组中进行存储,然后等待下一个按键按下,直到所按下的键代表“=”时,程序转向数据处理模块,经过上述所讲的四个小模块就能得到运算的结果并进行结果的输出显示。
资源分配(包含端口):
P1口用做4*4矩形键盘的输出响应端口;
P0口用作LCD1602液晶显示模块的输入控制端口; P3.2 、P3.3端口用作独立键盘的输出响应端口; 定时器T0用作定时(30ms)扫描4*4矩阵键盘; 定时器T1用作定时(10ms)扫描显示输出;
端口和变量的定义:因在下述程序中可以看到本程序所有使用的变量,故可参考下述 详细程序的变量定义。
- 14 -
4.3 源程序
4.3.1主程序 #include #define uchar unsigned char #define uint unsigned int uchar code tab[]={'1','7','8','9','/','4','5','6','*','1','2','3','-','0','.','=','+'}; uchar flag=0,flag1=0,fuhao=0; uint keyvalue,result4,sign=0,p=0; //全局变量 int i,datagroup[15],signgroup[15]; sbit reset=P3^2; sbit delete=P3^3; void timer0_init(); void timer1_init(); void jianpan_scanning(); void delay10ms(uint z); void display(); void catch_keyvalue(/*uint keyvalue1*/); //取数并存于数组函数 //void outputview1(uint result1); //输出显示函数 void outputview2(uint result2); uint judge_fuhao(int result3); void signgroup_prioritycompare();//符号数组优先级比较及数据处理函数 void sortfunction1( uint flag); // 数中 void sortfunction2( uint flag); //void datagroupinit(); //数据数组清零 //void signgroupinit(); //符号数组清零 //void signmatch();//符号对比函数 uint add(uint temp1,uint temp2); //‘+’函数 uint sub(uint temp1,uint temp2); //‘-’函数 uint mul(uint temp1,uint temp2); //‘*’函数 uint div(uint temp1,uint temp2); //‘/’函数 void reset(); //独立按键复位 void delete(); void main() { timer0_init(); timer1_init(); LcdInit(); LcdWriteData(0x30); LcdWriteCom(0x80); while(1) { // display(); - 15 - // datagroupinit(); signgroupinit(); //独立按键回删 数组变化后重新排序子函数,应用于void signgroup_prioritycompare()函 注: int为2个字节 } } catch_keyvalue(); //取值 signgroup_prioritycompare(); //处理 judge_fuhao(datagroup[0]); //处理结果判断正负数 outputview2(result4);//结果输出显示 keyvalue=0;p=0; sign=0; //i=0; datagroupinit(); signgroupinit(); // datagroup[0]=-123/3; void catch_keyvalue(/*uint keyvalue1*/) //取值函数 { uint a=0,keyvalue1; do{ //display(); if( (keyvalue != 0) && (flag1==1) ) { keyvalue1 = tab[keyvalue]; keyvalue1=keyvalue /* datagroup[10],signgroup[10],pointer=0,sign=0,*/ if( (keyvalue1 >= '0') && (keyvalue1 <= '9') /*&& (flag1==1)*/ ) { a=(keyvalue1-48); /*字符0-9转变为数字0-9*/ datagroup[p]=datagroup[p]*10+a; } else if((keyvalue1 != '=') && (keyvalue1 != '.') /*&& (flag1==1)*/ ) { // if(signgroup[sign]) signgroup[sign]=keyvalue1; /* temp2=data_group[pointer];*/ p=p+1; sign=sign+1; // else { signgroup[sign]=keyvalue1; /* temp1=data_group[pointer];*/ } ++pointer; ++sign; } flag1=0; } } while((keyvalue1 != '=')); signgroup[sign]='!'; } void signgroup_prioritycompare() //处理函数,一按‘=’号就运行此函数, signgroup[sign] { // i, 全局变量 i,sign,pointer, for(i=0;i if((signgroup[i]=='*') || (signgroup[i]=='/')) { if( signgroup[i]=='*' ) - 16 - 中 } { } datagroup[i+1]=mul(datagroup[i],datagroup[i+1]); sortfunction1(p); sortfunction2(sign); i=i-1; // sortfunction()为数组排序子函数 从i开始排 } else //if( signgroup[i]=='/' ) { datagroup[i+1]=div(datagroup[i],datagroup[i+1]); sortfunction1(p); sortfunction2(sign); i=i-1; } } //依次'+','-'号运算 do { i=0; if(signgroup[0]=='+' || signgroup[i]=='-') // for(i=0;i<=sign;i++) { if( (signgroup[0]=='+') ) { else //if( signgroup[0]=='-' ) { datagroup[1]=sub(datagroup[0],datagroup[1]); sortfunction1(p); sortfunction2(sign); } datagroup[1]=add(datagroup[0],datagroup[1]); sortfunction1(p); sortfunction2(sign); }//sortfunction()为数组排序子函数 从i开始排 } } while( (signgroup[0] != '!') /*&& signgroup[0] != '='*/ ); //最终运算结果存放在 datagroup[0] uint judge_fuhao(int result3) //负号判断 { if(result3>=0) {result4=result3;return (result4);} - 17 - } else{fuhao=1;result4=(-result3);return (result4);} /*void outputview1(uint result1) //输出特殊情况运算结果 { } */ void outputview2(uint result2) //输出运算结果 { uint adress,result; // LcdWriteCom(0x01); adress=0xcf; do { result=result2%10; LcdWriteCom(adress); LcdWriteData(result+48); adress=adress-1; result2=(result2-result)/10; result2=result uint adress; adress=0xcf; LcdWriteCom(adress); LcdWriteData(result1); } while(result2 != 0); } void sortfunction1( uint length) //数据数组排序函数 { } void sortfunction2( uint length) // 运算符数组排序函数 - 18 - uchar j; // j 为全局变量 for( j=i; j if(fuhao==1) { fuhao=0;LcdWriteCom(adress);LcdWriteData('-'); adress=adress-1; } LcdWriteCom(adress); LcdWriteData('='); // LcdWriteCom(0x80); // return (newgroup[]); { } uchar j; // j 为全局变量 for( j=i; j // return (newgroup[]); uint add(uint temp1,uint temp2) { uint result; //‘+’函数 result=temp1+temp2; return (result); } uint sub(uint temp1,uint temp2) { uint result; result=temp1-temp2; return (result); //输出结果最前加负号 } uint mul(uint temp1,uint temp2) //‘*’函数 { uint result; result=temp1*temp2; return (result); } uint div(uint temp1,uint temp2) //‘/’函数 { uint result; /* if(temp2 != 0) {*/result=temp1/temp2; return (result);//} //输出“error” } void timer1_init() //定时器T1初始化 { TMOD=0X10; TH0=(65536-1000)/256; TL0=(65536-1000) %256; EA=1; ET1=1; - 19 - //else { result='!'; return(result); } //‘-’函数 TR1=1; } void timer1() interrupt 3 //定时器T1中断函数 { TH1=(65536-1000)/256; TL1=(65536-1000)%256; display(); } /* **************** 定时器T0初始化 ******************/ void timer0_init() { TMOD=0X01; TH0=(65536-30000)/256; TL0=(65536-30000)%256; EA=1; ET0=1; TR0=1; } /* **************** 定时器T0中断函数 **************** */ void timer0() interrupt 1 { TH0=(65536-30000)/256; TL0=(65536-30000)%256; jianpan_scanning(); } /* ******************** 键盘扫描子函数 ********************* */ void jianpan_scanning() { P1=0xf0; if(P1 != 0xf0) { delay10ms(1); //延时消抖 if(P1 != 0xf0) { switch(P1) 矩阵键盘的扫描 - 20 - // } { case(0x70): keyvalue=1; break; } P1=0x0f; switch(P1) { case(0x07): keyvalue=keyvalue; break; } delay10ms(3); } flag=1; flag1=1; case(0x0b): keyvalue=keyvalue+1; break; case(0x0d): keyvalue=keyvalue+2; break; case(0x0e): keyvalue=keyvalue+3; case(0xb0): keyvalue=5; break; case(0xd0): keyvalue=9; break; case(0xe0): keyvalue=13; } //若干个独立键盘的扫描 if(reset==0) {flag2=0;} if(delete==0) {flag3=0;} /* ************************** 延时子函数 ************************** */ void delay10ms(uint z) { uint i,j; for(i=z;i>0;i--) for(j=2500;j>0;j--); } void display() { reset= 1; delete= 1; if(flag==1 && (keyvalue != 15)) flag=0; { LcdWriteData(tab[keyvalue]) //delay10ms(50); /* if(reset==0) LcdWriteCom(0x01); - 21 - //即时显示函数 } { delay10ms(2); LcdWriteData(0x30); LcdWriteCom(0x80); } if(delete==0) { LcdWriteCom(0x04); LcdWriteData(0x20); delay10ms(10); LcdWriteCom(0x06); } */ } /*void datagroupinit() //数据数组清零函数 { uchar m; for( m=0; m<15;m++ ) { datagroup[m]=0; } } void signgroupinit() //符号数组清零函数 { uchar m; for( m=0; m<15;m++ ) { signgroup[m]=0; } } */ void reset() { LcdWriteCom(0x01); delay10ms(2); LcdWriteData(0x30); LcdWriteCom(0x80); } void delete() { LcdWriteCom(0x04); LcdWriteData(0x20); delay10ms(10); LcdWriteCom(0x06); } - 22 - 4.3.2 LCD1602.c程序 #include\"lcd.h\" /******************************************************************************* * 函 数 名 : Lcd1602_Delay1ms * 函数功能 : 延时函数,延时1ms * 输 入 : c * 输 出 : 无 * 说 名 : 该函数是在12MHZ晶振下,12分频单片机的延时。 *******************************************************************************/ void Lcd1602_Delay1ms(uint c) //误差 0us { uchar a,b; } /******************************************************************************* * 函 数 名 : LcdWriteCom * 函数功能 : 向LCD写入一个字节的命令 * 输 入 : com * 输 出 : 无 *******************************************************************************/ #ifndef LCD1602_4PINS //当没有定义这个LCD1602_4PINS时 void LcdWriteCom(uchar com) //写入命令 { } #else void LcdWriteCom(uchar com) //写入命令 { - 23 - LCD1602_E = 0; //使能 LCD1602_RS = 0; LCD1602_RW = 0; //选择发送命令 //选择写入 //等待数据稳定 for (; c>0; c--) { } for (b=199;b>0;b--) { for(a=1;a>0;a--); } LCD1602_DATAPINS = com; //放入命令 Lcd1602_Delay1ms(1); LCD1602_E = 1; LCD1602_E = 0; Lcd1602_Delay1ms(5); //写入时序 //保持时间 } LCD1602_E = 0; LCD1602_RS = 0; LCD1602_RW = 0; //使能清零 //选择写入命令 //选择写入 LCD1602_DATAPINS = com; //由于4位的接线是接到P0口的高四位,所以传送高四位不用改 Lcd1602_Delay1ms(1); LCD1602_E = 1; LCD1602_E = 0; LCD1602_DATAPINS = com << 4; //发送低四位 Lcd1602_Delay1ms(1); LCD1602_E = 1; LCD1602_E = 0; //写入时序 Lcd1602_Delay1ms(5); //写入时序 Lcd1602_Delay1ms(5); #endif /******************************************************************************* * 函 数 名 : LcdWriteData * 函数功能 : 向LCD写入一个字节的数据 * 输 入 : dat * 输 出 : 无 *******************************************************************************/ #ifndef LCD1602_4PINS { } #else void LcdWriteData(uchar dat) { - 24 - LCD1602_E = 0; LCD1602_RS = 1; LCD1602_RW = 0; //使能清零 //选择写入数据 //选择写入 //写入数据 LCD1602_E = 0; LCD1602_RS = 1; LCD1602_RW = 0; //使能清零 //选择输入数据 //选择写入 //写入数据 void LcdWriteData(uchar dat) LCD1602_DATAPINS = dat; //写入数据 Lcd1602_Delay1ms(1); LCD1602_E = 1; //写入时序 Lcd1602_Delay1ms(5); //保持时间 LCD1602_E = 0; LCD1602_DATAPINS = dat; //由于4位的接线是接到P0口的高四位,所以传送高四位不用改 Lcd1602_Delay1ms(1); LCD1602_E = 1; LCD1602_E = 0; //写入时序 Lcd1602_Delay1ms(5); } LCD1602_DATAPINS = dat << 4; //写入低四位 Lcd1602_Delay1ms(1); LCD1602_E = 1; LCD1602_E = 0; //写入时序 Lcd1602_Delay1ms(5); #endif /******************************************************************************* * 函 数 名 : LcdInit() * 函数功能 : 初始化LCD屏 * 输 入 : 无 * 输 出 : 无 *******************************************************************************/ #ifndef { } #else void LcdInit() { } #endif 4.3.3 LCD1602.h头文件 LcdWriteCom(0x32); //将8位总线转为4位总线 LcdWriteCom(0x28); //在四位线下的初始化 LcdWriteCom(0x0c); //开显示不显示光标 LcdWriteCom(0x06); //写一个指针加1 LcdWriteCom(0x01); //清屏 LcdWriteCom(0x80); //设置数据指针起点 //LCD初始化子程序 LcdWriteCom(0x38); //设置类型 LcdWriteCom(0x01); //清屏 LcdWriteCom(0x06); //写一个指针加1 LcdWriteCom(0x0f); //开显示不显示光标 LcdWriteCom(0x80); //设置数据指针起点 // LcdwriteCom(0x08); //关显示 LCD1602_4PINS //LCD初始化子程序 void LcdInit() #ifndef __LCD_H_ #define __LCD_H_ /********************************** 当使用的是4位数据传输的时候定义, 使用8位取消这个定义 **********************************/ - 25 - #define LCD1602_4PINS /********************************** 包含头文件 **********************************/ #include //---重定义关键词---// #ifndef uchar #define uchar unsigned char #endif #ifndef uint #define uint unsigned int #endif /********************************** PIN口定义 **********************************/ #define LCD1602_DATAPINS P0 sbit LCD1602_E=P2^7; sbit LCD1602_RW=P2^5; sbit LCD1602_RS=P2^6; /********************************** 函数声明 **********************************/ /*在51单片机12MHZ时钟下的延时函数*/ void Lcd1602_Delay1ms(uint c); //误差 0us /*LCD1602写入8位命令子函数*/ void LcdWriteCom(uchar com); /*LCD1602写入8位数据子函数*/ void LcdWriteData(uchar dat); /*LCD1602初始化子程序*/ void LcdInit(); #endif - 26 - 第五章 调试程序 5.1调试结果分析 1.能进行多字节的整数的单独运算,结果用十进制的数字显示; 加法演示: 减法演示: 乘法演示: 除法演示: 2.尝试编写连加、连减的运算程序并进行演示; 连加演示: - 27 - 连减演示: 3.尝试编写混合的运算程序并进行演示; 混合演示: 总的来说,此次课程设计的程序调试还是比较顺利的,因为程序模块的构思是正确可行的,且模块划分也是比较容易理解和编写程序的。程序的调试结果是能够实现课题的要求。 5.2 存在的问题及解决方法 1. 取值存储模块的清零问题。原本设想自己设计的程序能够实现多次的运算,即 一次运算完之后,可以不按开关键就能继续的进行下一次的运算(此功能不是本课题的设计要求,是本人自己想加上去的)。但是在程序的调试中发现,本程序不能实现此功能,也就是在做完一次运算之后,要想做下一次的运算就必须重新上电初始化。经过多次的调试发现,可以在每次运算结束之后,在其后面加上一个数组清零的的子函数并把1602液晶显示清屏就可以完成此功能。 2. 小数处理的问题。刚开始编程时所有变量都是定义的unsigned char和unsigned int 类型,后来在程序调试的结果中发现,根本就没有找到小数的运算和结果。在查看和调试程序多次之后才发现变量的定义和存在问题,之后通过调整变量和结果的类型定义,以及在程序中加入了小数点符号处理的子函数,再通过多次的程序调试和改正软件提示的错误和警告,最终得以解决此问题。 3. 程序复位的问题(此功能并非课题要求,是为了是设计的程序更接近使用,自己添加的)。一开始在设计此程序时,考虑到如果能够添加一个复位键,即按下此键就能够重新开始运算,而不需要每次在做完一次运算之后再进行上电初始化之后,才能够再 - 28 - 进行第二次的运算,因此才自己想加上此功能,便于使用者使用。在经过多次的子函数的编写和调试,最后此功能只完成在显示模块1602液晶显示屏上的复位功能,而没能在主程序中完成复位的功能,因此此功能的设计还是没能取得成功。 4. 删除功能键的设计问题(此功能并非课题要求,是为了是设计的程序更接近使用,自己添加的)。 在设计程序之初,考虑到在按键输入数据的时候可能会按错或者按完之后又不需要的的数据,因此自己想要添加此功能函数。使程序能够更贴近实用情况。一开始是想通过在程序中添加数据删除子函数模块来实现此功能的,但是在调试过程中发现删除数据子函数只能够实现在1602液晶显示屏上的数据删除和显示,还不能够在主程序中实现存储数组里的数据删除,因此功能键函数还有待完善。 - 29 - 设计总结及体会 通过这一个礼拜左右的设计,我完成了简易计算器的课程设计,虽然只是一个非常简单的计算器,可也是我经过一翻很大的努力才完全达到设计要求的,从心底里说,还是挺高兴的,毕竟这次设计所要求的东西都做了出来,然而在设计中部分程序还有待完善。 在本次设计的过程中,我发现一些问题,虽然以前还做过这样的设计但这次设计真的让我长进了很多,设计重点就在于软件程序的设计,需要有很巧妙的编程方法,在编程时,由于粗心大意马虎,有些语句看似没问题,可就是不出效果,经仔细揣摩修改后,程序才正常运行。自己深深感觉到程序只有在经常的写与读的过程中才能提高。 从这次的设计中,我真真正正的意识到,在以后的学习中,要理论联系实际,把我们所学的理论知识用到实际当中,理论指导实践,在实践中对理论知识加以理解,要有独立思考能力。 由于时间比较仓促,我所设计的这个计算器非常简单,可以考虑在此程序上来改进一下,使它的功能更加完善。 - 30 - 参考文献 [1] 林君,程德福. 智能仪器(第2版) 北京:机械工业出版社,2009.8 [2] 朱兆优,陈坚,邓文娟. 单片机原理与应用— 基于STC系列增强型8051单片(第2版)北京:电子工业出版社2012.7 [3] 郭惠,吴迅. 单片机C语言程序设计 北京:电子工业出版社,2008 [4] 刘建清 从零开始学单片机C语言 北京:国防工业出版社,2006 - 31 - 因篇幅问题不能全部显示,请点此查看更多更全内容