单片机原理课程设计OLED电子计算器.docx
《单片机原理课程设计OLED电子计算器.docx》由会员分享,可在线阅读,更多相关《单片机原理课程设计OLED电子计算器.docx(32页珍藏版)》请在冰豆网上搜索。
单片机原理课程设计OLED电子计算器
课程设计(论文)任务书
电气与电子工程学院系自动化专业13班级1班
一、课程设计(论文)题目计算器设计
二、课程设计(论文)工作自2016年1月5日起至2016年1月15日止。
三、课程设计(论文)的内容要求:
实现五位数(可为带小数数)的加减乘除运算,通过按键输入十进制数据。
利用八位数码管显示运算结果。
学生签名:
2016年1月日
课程设计(论文)评审意见
(1)题目复杂程度:
复杂()、较复杂()、一般()、简单()
(2)总体方案的选择是否正确:
正确()、较正确()、欠正确()、不正确()
(3)系统能否满足任务要求:
满足()、较满足()、欠满足()、不满足()
(4)元器件选择是否合理:
合理()、较合理()、欠合理()、不太合理()
(5)学习实践态度:
好()、较好()、一般()、不太好()
(6)独立工作能力:
强()、较强()、一般()、较差()
(7)回答问题是否正确:
正确()、较正确()、基本正确()、大多不正确()
(8)图表是否符合标准:
符合()、较符合()、基本符合()、大多不符合()
(9)撰写是否规范整洁:
规范整洁()、较规范()、欠规范()、不太规范()
总评成绩:
优()、良()、中()、及格()、不及格()
评阅人职称副教授
2016年1月日
第一章课程设计题目分析
1.1课程设计题目要求
本次课程设计要求设计一个能实现五位数(可为带小数数)的加减乘除运算的计算器,通过按键输入十进制数据。
利用八位数据码管显示运算结果。
在经过指导老师的批准后,将题目要求改为用LCD显示运算的结果。
1.2课程设计题目要求分析
分析题目要求可知,本次设计要用到的知识有键盘的设计、单片机I/O口的操作、数制的转换与数值的运算、LCD的驱动显示。
键盘设计部分的主要任务是设计一个用来输入数值、运算符以及结果显示操作的键盘。
单片机的I/O口一是用来接收外部输入的数据,主要是键盘输入的数据,并对这些数据进行判断和处理,第二是用来输出处理过后的数据,主要是将数据输出到LCD上以驱动LCD显示相应的内容。
数制转换与数值运算部分的主要任务有:
将键盘输入的十进制数据进行十六进制以及二进制的转换,这些操作由单片机内部自动完成;输入数值的整型以及浮点型的处理,这部分需要通过编程实现;运算结果的整型以及浮点型的控制,这部分需要通过编程实现,LCD驱动数据的整型和浮点型向字符型的转换,这部分需要通过编程实现。
LCD驱动显示部分主要用来显示输入的两个操作数、一个操作运算符及一个运算结果。
通过以上分析可知,本次课程设计的难点在于数制转换与数值运算部分以及LCD驱动显示部分。
在进行课程设计之前需要准备的知识有:
Keil软件的使用,AltiumDesigner软件的使用,数值转换与数值运算相关算法的了解,LCD接口以及驱动的了解。
第二章设计思想和设计说明
2.1设计思想
从以上的分析可知,本次课程设计可分为两大部分,第一部分是键盘数据的输入及处理,第二部分是LCD部分的输出驱动及显示。
按照上述分类,本次设计的硬件部分采用先分开调试后联合调试的方法,即先分开调试键盘部分和LCD部分的硬件电路,待各部分的电路都调试通过后,再将两部分电路联合调试,采用这种调试方法,不仅能提高设计的效率,降低出错率,还能在出现问题时快速找到问题的所在。
考虑到LCD底层驱动程序较为复杂,以及本人对汇编语言的掌握不是太好,本次课程设计的程序语言采用C语言进行编写,并采用模块化编程的思想,将LCD的驱动以及键盘部分的驱动分为两个模块进行编写,同样采用先独立调试再联合调试的方法。
2.2设计说明
本次设计的详细软硬件资料见附录。
设计报告中使用的简写如下:
●LCD:
LiquidCrystalDisplay(液晶显示器);
●ROM:
Readonlymemory(只读存储器);
●RAM:
Randomaccessmemory(随机存取存储器);
●SRAM:
Staticrandomaccessmemory(静态随机存取存储器);
●PCB:
Printedcircuitboard(印制电路板)。
第三章硬件选型
3.1单片机选型
在设计初期采用AT89C51单片机进行调试,发现在LCD的驱动调试好以后占用的ROM已经接近4K,考虑到键盘部分的程序还未编写,所以最终的程序大小可能会超过4K,因此就需要扩展片外的ROM,为了节约设计的制作成本和简化设计的硬件电路,于是采用STC89C52RC单片机进行调试,STC89C52RC单片机拥有8K的FlashRAM以及512B的SRAM和5K的EEPROM,RAM大大小以及ROM的大小完全足够本次设计使用,而且价格适中。
STC89C52RC的具体特性见附录1。
3.2LCD选型
根据以往做项目的经验,LCD的驱动芯片选用SSD1306128×64液晶显示屏驱动芯片,芯片接口支持SPI协议和IIC协议,通过单片机I/O口的模拟配置就可以驱动显示。
LCD模块选择中景园电子的0.96寸LCD模块,其内部集成SSD1306驱动芯片,支持3.3~5V电源输入,符合本次设计的要求。
3.3其他器件选型
考虑到PCB制作成本的问题,本次设计的电路板使用万能板进行焊制。
由于LCD的驱动代码较多,为了加快系统的运行速度,晶振采用11.0592MHz的频率,同时也为了方便串口调试时的使用。
其他器件的选型见附录2。
3.4使用软件说明
本次课程设计的软硬件设计在windows7系统下进行。
程序编写使用KeiluVision4软件;硬件电路图的绘制使用AltiumDesigner软件;原理框图的绘制使用MicrosoftViso软件;使用stc-isp-15xx-v6.85I软件进行程序的仿真调试与下载。
第四章硬件原理
4.1硬件整体框图
硬件整体框图如图1所示。
图1硬件整体框图
本设计硬件整体分为六个小部分,分别为:
矩阵键盘电路、复位电路、晶振电路、LCD电路、电源电路和STC89C52RC电路,下面对前四部分进行详细说明。
4.2矩阵键盘电路
矩阵键盘电路如图2所示,采用4×4矩阵键盘,共四行四列十六个按键,占用八个I/O口。
图2矩阵键盘电路
矩阵键盘的四个行分别与单片机的P1.0~P1.3相连接,四个列分别与单片机的P3.4~P3.7相连接,采用行列扫描的方法来检测按下的按键,具体的做法是:
检测时先让一列为低电平(此时确定了列),其余几列为高电平,然后立即轮流检测各个行是否有低电平出现,如果检测到了某一行有低电平(此时确定了行),就可以确定是那个按键被按下了。
图中的S1~S10分别对用十进制数0~9,S11代表小数点,S12代表等号,S13~S16分别代表加、减、乘、除四个运算符。
4.3复位电路
复位电路如图3所示。
图3复位电路
在复位按键没有按下时,复位引脚通过下拉电阻和地GND相连接,为低电平,当复位按键按下时,复位引脚和VCC接通,使单片机复位。
4.4晶振电路
晶振电路如图4所示。
11.0592MHz的晶振两端通过两个33皮法的电容接地,组成时钟电路,通过引脚输入到单片机内部以提供时钟。
图4晶振电路
4.5LCD电路
LCD电路如图5所示。
图5LCD电路
LCD与STC89C52RC之间通过J1相连接,由单片机的I/O口模拟SPI接口驱动LCD进行工作。
接口引脚CLK、MOSI、D/C、CS1、FSO、CS2分别与单片机的P0.0~P0.5通过上拉电阻相连接。
第五章程序设计原理
5.1程序设计流程
程序的设计流程图如图6所示。
下面对程序的几个主要部分进行说明。
图6程序设计流程图
5.2按键扫描程序
键盘扫描函数的定义为voidkeyScan();在程序中依次给PO口赋值0XFE、0XFD、0XFB、0XF7来依次使键盘的四个行线为0,然后用temp=P0来读取P0口的状态以用于后面的计算,然后将temp的值与0XF0相“与“判断temp的高四位是否有0,如果temp的高四位没有0,那么与0XF0相与的结果必然等于0XF0,就说明没有按键被按下,如果temp的高四位有0,那么与0XF0相与的结果必然不等于0XF0,就说明有按键被按下,然后再通过P0口的值来判断具体是那个按键被按下。
5.3输入数据存储与转换算法
输入的数据用两个整型变量和两个字符串来存储其定义分别为:
✧intBP_dat,AP_dat
✧unsignedcharBP_str[]=“”,AP_str[]=“”。
输入的数字用变量key表示,小数点前的整数部分的计算方式为
BP_dat=BP_dat*10+key。
小数点后小时部分的计算方式为((float)(AP_dat))/(10^(strlen(AP_str))),然后使用语句BP_dat+((float)(AP_dat))/(10^(strlen(AP_str)))便可得到输入的操作数。
由于LCD显示函数的输入只能是字符型的数据,因此使用sprintf(BP_str,"%d",BP_dat)函数将整型的数据转换成字符型。
其他部分程序见附录4。
5.4LCD驱动
LCD的底层驱动使用SSD1306的开源驱动程序,在此底层驱动的基础上自己编写显示函数。
第六章系统功能描述
本次课程设计的作品实物图如图7所示。
图7作品实物图
按键的对应关系如图8所示。
图8按键功能对应关系
作品可实现四个浮点数的加减乘除四则运算,但在计算乘除法,特别是两个浮点数的乘除法时会会产生数据精度被削减的问题,此问题暂时没有找到解决的方法,有待日后改进。
第七章设计总结和心得体会
本次课程设计历时两周,在课程设计的过程中遇到了许多的问题,同时也学到了许多知识。
在最初的硬件调试时总是遇到下载器检测不到单片机的情况,为了找出问题所在,我首先检查单片机的最小系统是否正常工作,检查发现电源供电正常,复位电路连接正常,晶振电路能正常起振,最后仔细查阅数据手册才知道,STC89C52RC单片机和以前使用的其他类型的单片机不同,它采用的是高电平复位,而以前自己使用的单片机是低电平复位,在修改了复位电路后,下载器便能正常检测到单片机。
在调试LCD的驱动时总是遇到显示乱码和错位的问题,通过在程序中设置断点的调试方法发现是数据格式转换是出现了问题,主要是在整型数据和浮点数转为字符型时出现的问题,通过查阅C语言的相关书籍发现,使用sprintf()函数可以很好地实现数值向字符串的转换,但是8051内核运行这个函数总是显得有些吃力,为了到达较好的显示效果,只能舍弃了运行速度。
在对两个数进行运算时,特别是浮点数的运算时,运算结果有时出现精度降低或者是数值溢出的问题,通过查阅开发手册得知,在KeilC51中unsignedint型数据占两个字节,数据范围为0~65535,signedint型数据占两个字节数据范围为-32768~+32767,float类型数据占四个字节,数据范围为:
±1.175494E-38~±3.402823E+38,而自己输入的数据或运算的结果可能超出了上述的范围值,因此造成数据溢出并显示错误,在修改了数据的存储类型后,上述的问题得到了有效的解决,但是输入的数值和运算的结果却有了一定的范围限制。
本次课程设计的作品到目前位置仍有一些缺陷,比如输入数据位数的问题和计算结果精度降低的问题,由于时间的原因,只能在以后的时间里慢慢完善。
通过本次课程设计,不仅让我在课堂上学到的东西学以致用,更重要的是提高了我的动手能力和解决问题的能力,为以后的工作打下了很好的基础。
附录1STC89C52RC资料
STC89C52RC单片机是宏晶科技推出的新一代高速/低功耗/超强抗干扰的8051内核单片机,指令代码完全兼容传统8051单片机,12时钟/机器周期和6时钟/机器周期可以任意选择。
其主要特性如下:
●增强型8051单片机,12时钟/机器周期和6时钟/机器周期可以任意选择,指令代码完全兼容传统8051。
●工作电压:
5.5V~3.3V。
●工作频率范围:
0~40MHz。
●用户应用程序存储空间为8K。
●片上集成512B的RAM。
●32个I/O口,复位后P1/P2/P3为准双向口/弱上拉,PO口为漏极开路,作为总线扩展时不用加上拉电阻,作为I/O口时需要加上拉电阻。
●ISP/IAP。
●3个16为定时/计数器。
●4路外部中断,下降沿或低电平触发。
STC89C52RC的引脚图如图9所示。
图9STC89C52RC引脚图
附录2器件选型
器件名称
型号
数量
单片机
STC89C52RC
1
按键
四角按键
17
电阻
10K欧姆
1
晶振
11.0592MHz
1
瓷片电容
33pF
2
芯片座
40DIP
1
排孔
1*8
1
液晶
LCD12864
1
附录3硬件原理图
附录4程序清单
源文件1:
calculator.c
#include
#include
#include
#include
#include"user_define.h"
ucharpoint[]='.';
floatop1;
floatop2;
floatans;
intBP_dat=0;//小数点之前的数据
intAP_dat=0;//小数点之后的数据
uinten89=1;
uintpFlag=0;//小数点输入标志
uintasf=0;//等号按键标志
uintoptCS;//输入运算符类型
uintline_f=0;//行控制
uintbplen;//整数部分长度
ucharBP_str[]="";//整数部分存放数组
ucharAP_str[]="";//小数部分存放数组
ucharansCode[]="";//运算结果存放数组
/************初始化显示****************/
voidinit_dis()
{
oledDisp(0,0,">>");
oledDisp(4,0,">>");
oledDisp(6,0,"ans:
");
}
/**********数值计算函数*************/
voidnumSelc(intkey)
{
if(pFlag==0)
{
BP_dat=BP_dat*10+key;
sprintf(BP_str,"%d",BP_dat);
bplen=strlen(BP_str)*8+24;
oledDisp(line_f,16,BP_str);
}
else
{
AP_dat=AP_dat*10+key;
sprintf(AP_str,"%d",AP_dat);
oledDisp(line_f,bplen,AP_str);
}
}
/**********延时函数,毫秒级************/
voiddelay_ms(uinta)
{
uinti,j;
for(i=a;i>0;i--)
for(j=110;j>0;j--);
}
/*************键盘扫描*****************/
voidkeyScan()
{
uchartemp;
intkey;
P1=0XFE;
temp=P1;
temp=temp&0XF0;
if(temp!
=0XF0)
{
delay_ms(10);
temp=P1;
temp=temp&0XF0;
if(temp!
=0XF0)
{
temp=P1;
switch(temp)
{
case0XEE:
key=0;break;
case0XDE:
key=1;break;
case0XBE:
key=2;break;
case0X7E:
key=3;break;
}
while(temp!
=0XF0)
{
temp=P1;
temp=temp&0XF0;
}
numSelc(key);
}
}
P1=0XFD;
temp=P1;
temp=temp&0XF0;
if(temp!
=0XF0)
{
delay_ms(10);
temp=P1;
temp=temp&0XF0;
if(temp!
=0XF0)
{
temp=P1;
switch(temp)
{
case0XED:
key=4;break;
case0XDD:
key=5;break;
case0XBD:
key=6;break;
case0X7D:
key=7;break;
}
while(temp!
=0XF0)
{
temp=P1;
temp=temp&0XF0;
}
numSelc(key);
}
}
P1=0XFB;
temp=P1;
temp=temp&0XF0;
if(temp!
=0XF0)
{
delay_ms(10);
temp=P1;
temp=temp&0XF0;
if(temp!
=0XF0)
{
temp=P1;
switch(temp)
{
case0XEB:
key=8;break;
case0XDB:
key=9;break;
case0XBB:
pFlag=1;break;
case0X7B:
asf=1;pFlag=0;break;
}
while(temp!
=0XF0)
{
temp=P1;
temp=temp&0XF0;
}
if(pFlag==1&&en89==1)
{
oledDisp(line_f,strlen(BP_str)*8+16,point);//计算小数点显示的地址
en89=0;
}
elseif(asf==1)
{
if(strlen(AP_str)==0)
op2=BP_dat;
else
op2=BP_dat+((float)(AP_dat))/(10^(strlen(AP_str)));//得到第二个运算数
switch(optCS)
{
case0:
ans=op1+op2;break;
case1:
ans=op1-op2;break;
case2:
ans=op1*op2;break;
case3:
ans=op1/op2;break;
}
sprintf(ansCode,"%f",ans);//将浮点型数据转换为字符串以显示在LCD上
oledDisp(6,32,ansCode);
pFlag=0;
BP_dat=0;
AP_dat=0;
}
else
numSelc(key);
}
}
P1=0XF7;
temp=P1;
temp=temp&0XF0;
if(temp!
=0XF0)
{
delay_ms(10);
temp=P1;
temp=temp&0XF0;
if(temp!
=0XF0)
{
temp=P1;
switch(temp)
{
case0XE7:
optCS=0;break;
case0XD7:
optCS=1;break;
case0XB7:
optCS=2;break;
case0X77:
optCS=3;break;
}
while(temp!
=0XF0)
{
temp=P1;
temp=temp&0XF0;
}
if(strlen(AP_str)==0)
op1=BP_dat;
else
op1=BP_dat+((float)(AP_dat))/(10^(strlen(AP_str)));//转换得到第一个运算数
line_f=4;
AP_dat=0;
BP_dat=0;
pFlag=0;
en89=1;
switch(optCS)
{
case0:
oledDisp(2,1,"+");break;
case1:
oledDisp(2,1,"-");break;
case2:
oledDisp(2,1,"*");break;
case3:
oledDisp(2,1,"/");break;
}
}
}
}
voidmain(void)
{
CS2=1;
CS1=0;
initLcd();//初始化LCD
clr_SCR();
init_dis();//初始化显示
P3=0X00;
while
(1)
{
keyScan();//循环按键扫描
}
}
头文件:
user_define.h
#ifndef__USER_DEFINE_H__
#define__USER_DEFINE_H__
#defineucharunsignedchar
#defineuintunsignedint
#defineulongunsignedlong
sbitCLK=P0^0;//接口定义:
CLK
sbitMOSI=P0^1;//接口定义:
MOSI
sbitDC=P0^2;//接口定义:
DC
sbitCS1=P0^3;//接口定义:
OLED的片选CS1
sbitFSO=P0^4;//字库IC接口定义:
FSO
sbitCS2=P0^5;//字库IC接口定义:
CS2
voidoledDisp(uchary,ucharx,uchar*text);//函数声明
voidclr_SCR();
voidinitLcd();
voiddelay_ms(uinta);
#endif
源文件:
oled_driver.c
#include
#include
#include"user_define.h"
/*******写指令到LCD模块*******/
void