评阅教师评语: 课程设计成绩 考勤成绩 实做成绩 报告成绩 总评成绩 指导教师签名: 《嵌入式系统》 课 程 设 计 报 告 论文题目: 简易计算器设计 学院(系): 电子信息与自动化学院 班 级: 113070303 学生姓名: 杨智 学号: 11307030316 指导教师: 杨泽林 王先全 冯济琴 杨继森 时间: 从2016年6月20日 到2016年7月8日 目录 前言................................................................ 3 摘要................................................................ 3 一、原理与总体方案.................................................. 4 ⒈总体方案 ...................................................... 4 二、硬件设计........................................................ 5 ⒈元器件 如表2.1:.............................................. 5 ⒉键盘接口电路 .................................................. 5 3.各模块的介绍和功能 ............................................ 7 三、调试............................................................ 9 ⒈ GPIO特性..................................................... 9 ⒉ 硬件和程序原理 ............................................... 9 四、测试与分析..................................................... 10 五、结束语......................................................... 13 附件:程序代码..................................................... 13 2 前言:近几年随着大规模集成电路的发展和应用,各种便携式嵌入式设备具有十分广阔的市场前景。嵌入式系统是一种专用的计算机系统作为装置或设备的一部分。通常嵌入式系统是一个控制程序存储在ROM中的嵌入式处理器控制板。事实上所有带有数字接口的设备如手表、微波炉、录像机、汽车等都使用嵌入式系统,有些嵌入式系统还包含操作系统,但大多数嵌入式系统都是由单个程序实现整个控制逻辑。在嵌入式系统中数据和命令通过网络接口或串行口经过ARM程序处理后或显示在LCD上或传输到远端PC上,本次课程设计是通国LPC2138芯片完成的简易计算器,正是对嵌入式应用的学习、思考和探索。 【摘要】 计算器一般是指“电子计算器”是能进行数学运算的手持机器拥有集成电路芯片。对于嵌入式系统以其占用资源少、专用性强,在汽车电子、航空和工控领域得到了广泛地应用。本设计就是先在KELL软件中进行相应程序的编写、运行,最后导入PROTUES进行仿真。最后利用ARM中的LPC2138芯片来控制液晶显示器和4X6矩阵式键盘从而实现简单的加、减、乘、除等四则运算功能。 【关键字】 中断 扫描 仿真 计算 显示 3 一、原理与总体方案 ⒈总体方案 主程序在初始化后调用键盘程序再判断返回的值。若为数字0 -9,则根据按键的次数进行保存和显示处理。若为功能键则先判断上次的功能键,根据代号执行不同功能并将按键次数清零。程序中键盘部分使用行列式扫描原理若无键按下则调用动态显示程序并继续检测键盘情况,若有键按下则得其键值并通过查表转换为数字0—9和功能键与清零键的代号最后将计算结果拆分成个、十、百位再返回主程序继续检测键盘并显示,若为清零键则返回主程序的最开始。电路设计与原理通过LPC2128芯片进行相应的设置来控制LCD显示器。而通过对键盘上的值进行扫描把相应的键值让ARM芯片接收。 2.系统流程图: 计算键值,调用参数设定子程序 按键扫描 显示时间 调用显示模式设置 中断定时初始时钟初始化 LCD初始化 端口初始化 引脚功能初始化 向量中断初始化 外部中断初始化 开始 N Y 4 二、硬件设计 ⒈元器件 如表2.1: 器件 Lpc2138 LCD1602 KEYPAD 电缆线 USB电源线 电源 导线 数量 1 1 1 1 1 若干 若干 表2.1 元器件表 ⒉键盘接口电路 计算器输入数字和其他功能按键要用到很多按键,如果采用独立按键的方式在这种情况下。,程会很简单,但是会占用大量的I/O 口资源。因此在很多情况下都不采用这种方式,而是采用矩阵键盘的方案。在本次课程设计中矩阵键盘采用四条I/O 线作为行线,六条I/O 线作为列线,组成键盘在行线和列线的每个交叉点上设置一个按键。这样键盘上按键的个数就为4×6个。这种行列式键盘结构能有效地提高单片机系统中I/O 的利用率。 矩阵键盘的工作原理如图2.1: 5 图2.1 矩阵键盘的工作原理 计算器的键盘布局如图2.2,有24个键组成,在lpc2138芯片中,由于芯片某些引脚无输出,我们将键盘的4根行线和6根列线接入P12 -P21口作为引键盘输入端口,而且这种行列式键盘结构能有效地提高单片机系统中I/O 口的利用率。 图2.2 计算器的键盘布局 显示模块:本设计采用LCD1液晶显示屏来显示输出数据。通过D0-D7引脚向LCD1写指令字或写数据以使LCD1实现不同的功能或显示相应数据。 6 3.各模块的介绍和功能 ①Lpc2138特性和引脚功能:Philips公式的32位ARM7微控制器lpc2138具有强大的储存空间,内嵌有32KB片内静态ARM和512KB的Flash存储器,可以实现在系统可编程(ISP),在应用可编程(IAP);2个8路10位A/D转换器,1个D/A转换器,转换迅速准确;引脚资源丰富,多达47个可承受5V的通用I/O口;多个串行接口,包括2个16C550工业标准的UART,2个高速12C借口,SPI,以及具有缓冲作用和数据长度可变功能的SSP协议,lpc2138可以移植u C/COII操偶做系统,软件的可移植性好,工作可靠(如图2.3)。 图2.3 LPC2138仿真图 管脚说明:V3、VBAT、VREP、供电电压 Vss、VSSA:接地 ; 7 XTAL反向振荡放大器的输入及内部时钟工作电路的入; XTAL来自反向振荡器的输出; P1.12-P1.23作为GPIO接口; P1.16—P1.26作为LCD显示电路接口; ②LCD的特性说明: LCD 显示屏(图2.4)可以采用字符显示,当使用字符显示方式时可以直接用外码作为输入数据,由显示屏自动给出显示字模数据而无需由微处理器提供字模。 图2.4 LCD显示屏 ③键盘特性: 数字式键盘(图2.5)的实质仍是行列式键盘,4 根行线和6 根列线通过下拉电阻接地,在其表面贴有标示对应按键的膜,使得易于观察和操作,更加贴近使用者的需求。 图2.5 数字式键盘 本设计中选用LPC2138的P1.16—P1.26口与LCD 显示屏相连,它们所输出信号控制显示屏上的显示,其中P1.16-P1.23 分别接显示屏D0-D7,P1.24—8 P1.26分别接显示屏的RS、RW、E 接口;选用LPC2138的P0.12—P0.21口连续10个IO口计算器键盘相连,可通过按键检测到按下的按键,再通过查键盘对应的字符编码使LCD 显示,其中P0.0—P0.3口依次接键盘的4根行线,P0.4—P0.9口依次接键盘的6根列线。 ④管脚说明: P0.0 -P0.3口为行键扫描电平输出端,P0.4 -P0.9口为列键输入读取端; 三、调试 ⒈ GPIO特性 LPC2000系列ARM的GPIO具有如下特性: a、 可以独立控制每个GPIO口的方向,输入/输出模式; b、 可以独立设置每个GPIO的输出状态,高/低电平; c、 所有GPIO口在复位后默认为输入状态。 每个作为GPIO功能的引脚受到四个寄存器控制;分别为控制方向的IOxDIR、控制输出电平状态的IOxSET和IOxCLR、反映引脚电平状态的IOxPIN。这四个寄存器构成一组;而一组寄存器控制着一个端口,P0、P1、P2或P3。 ⒉ 硬件和程序原理 在键盘按键后;产生一个中断;KEYSCAN对键盘进行扫描;确定按下的键;如果不是等号,就把按下的字符依次存入数组,并用LCD显示表达式,如果是等号,就计算出数组中存入的表达式的值,并显示在LCD中。 计算表达式值的方法:把表达式中的表示数值的字符转成数值存入一个数值数组,把表达式中的表示运算符的字符存入运算符数组; 关于四则运算优先级算法:依次检索运算符数组,当遇到 * 号或者 / 号的时候,就先计算乘法或除法,算出结果后存入数值数组相应位置,然后删除此9 运算符和多余的数值,再重复检索,直到数值数组中只有一个数值为止,这个数值即是运算结果。 调用stdio.h库里的sprintf函数,把数值转化成字符数组,然后显示到LCD中。 在本次课程设计中,我们在简易就算器的基础上进行拓展,不仅具有加、减、乘、除功能,还具有开方,开根,求对数的功能,在于输入错误可以进行退格处理,结束运算后可以进行清零处理,不仅如此,还有时间显示的功能,并且可以通过按键调整时间。 四、测试与分析 ⒈ KEIL工程文件: 见附件1; ⒉ Proteus实时仿真如图4.1; 10 图4.1 Proteus实时仿真 ⒊ Proteus实时仿真子功能如图4.2—4.7 图4.2 时钟显示 图4.3 对数运算 图4.4 加法 图4.5减法 图4.6 乘法 图4.7 除法 11 图4.8 平方 图4.9 开根 ⒋ 实物图图4.10 图4.10 12 五、结束语 对我们而言,知识上的收获重要,对ARM处理系统有了更深刻的认识。挫折是一份财富:经历是一份拥有。这次实习必将成为我人生旅途上一个非常美好的回忆通过这次课程设计使我懂得了理论与实际相结合是很重要的。只有理论知识是远远不够的:只有把所学的理论知识与实践相结合起来:从理论中得出结论:才能真正为社会服务:从而提高自己的实际动手能力和独立思考的能力。在设计的过程中遇到问题,例如无法下载程序时后来通过排查,发现芯片的某些I/O无效,通过修改程序,采用其他端口才得以解决,同时在设计的过程中发现了自己的不足之处:对以前所学过的知识理解得不够深刻:掌握得不够牢固。 附件:程序代码 /************************************ * File: main.c * 功能: 计算器基本程序 *************************************/ #include \"config.h\" #include \"stdlib.h\" #include \"stdio.h\" #include \"string.h\"//memset hanshu #include \"math.h\" 13 #define LCD_RS (1<<24) #define LCD_RW (1<<25) #define LCD_E (1<<26) #define nop() {__asm(\"nop\");} //#define busy (1<<23) uint8 txt0[]={\"Hello! \ uint8 time[2]={12,34}; // 时、分、秒计数 void timeshow(void); void timer0_init(void); void __irq timerInt(void); /*********************************** * 名称 函数及变量声明 * 功能 显示文本 *********************************/ void port_init(void); void cov(long int b,uint8 *p); void LCD_Cursor (char row, char column); void ShowInt(uint8 addr,uint16 num); void deal_date(void); void deal_ope(void); double calculate(void); double calculate1(void); uint8 KEYSCAN(void); uint8 KEYCODE1[24]={ 'C','7','8','9','*','/', 'H','4','5','6','-','L', 'M','1','2','3','+','E', 'B','0','.','=','+','D' }; /*ASCII码*/ long int re_temp1=0,re_temp2=0; uint8 BCD[7]={0}; uint8 show[7]={0}; char keypressed; char last_keypres = '='; double date[5]={0,0}; double *date_pt=&date[1]; char operater[5]={'N'}; char *oper_pt=operater; uint8 place=1; char date_temp[17]; char *temp_pt=date_temp; /*********************************** 14 * 名称 DelayNS * 功能 延时函数 **********************************/ void DelayNS(uint32 dly) { uint32 i; for(;dly>0;dly--) for(i=0;i<50000;i++); } /*********************************** * 名称 void port_init(void) * 功能 端口初始化 *********************************/ void port_init(void) { PINSEL0=0x00000000; PINSEL1=0x00000000; IO0DIR=0x0000F000; //键盘行输入 IO0CLR=0x0000F000; //初始化 PINSEL2=0x00000000; //1111 1111 1100 0000 0000 0000 0000 0000 IO1DIR|=0x7ff0000; //0000 0111 1111 1111 IO1SET|=0xff0000; //1111 1111 } /*********************************** * 名称 cov(long int b,unsigned char *p) * 功能 数据拆分 *********************************/ void cov(long int b,unsigned char *p) {unsigned char j=8; while(--j) { p[j]=b%10;b=b/10; } p[j]=b; } /******************************** * 名称ChkBusy() * 功能 检查总线是否忙 *********************************/ void ChkBusy() { int status; IO1DIR=0x7000000; //0111 0000 0000 0X00等于输入 do { 15 IO1CLR|=LCD_RS; IO1SET|=LCD_RW; IO1SET|=LCD_E; nop();nop();nop();nop(); status = IO1PIN; IO1CLR |= LCD_E; } while (status & 0x800000); IO1DIR=0x7ff0000; //0X00等于输出 } /************************************ * 名称 WrOp() * 功能 写命令函数 **********************************/ void LCD_WriteControl (uint8 dat) { ChkBusy(); IO1CLR|=LCD_RS; IO1CLR|=LCD_RW; IO1CLR=0xff0000; IO1SET|=(dat<<16); nop();nop();nop();nop(); IO1SET|=LCD_E; IO1CLR|=LCD_E; } /********************************* * 名称 CD_Cursor * 功能 光标定位 *********************************/ void LCD_Cursor (char row, char column) { switch (row) { case 1: LCD_WriteControl (0x80 + column - 1); break; case 2: LCD_WriteControl (0xc0 + column - 1); break; case 3: LCD_WriteControl (0x94 + column - 1); break; case 4: LCD_WriteControl (0xd4 + column - 1); break; default: break; } } /******************************** * 名称 WrDat() * 功能 写数据函数 *********************************/ 16 //全部清零 //先清零 //再送数 void WrDat(uint8 dat) { ChkBusy();调用检测繁忙函数 IO1SET|=LCD_RS; IO1CLR|=LCD_RW; IO1CLR=0xff0000; IO1SET|=(dat<<16); nop();nop();nop();nop(); IO1SET|=LCD_E; IO1CLR|=LCD_E; } /************************************* * 名称 lcd_init() * 功能 lcd初始化函数 *********************************/ void lcd_init(void) { LCD_WriteControl(0x38); //显示模式设置,开始要求每次检测忙信号 LCD_WriteControl(0x08); //关闭显示 LCD_WriteControl(0x01); //显示清屏 LCD_WriteControl(0x06); // 显示光标移动设置 LCD_WriteControl(0x0C); // 显示开及光标设置 } /********************************* * 名称 DisText() * 功能 显示文本函数 *********************************/ void DisText(uint8 addr,uint8 *p) { LCD_WriteControl(addr); while(*p !='\\0')WrDat(*(p++)); } /********************************* * 名称 ShowInt(uint8 addr,uint16 num) * 功能 数字显示程序 ********************************/ void ShowInt(uint8 addr,uint16 num) //在addr处显示数字num { uint8 i; for(i=8;i>0;i--) { BCD[i-1]=(uint8)(num%10+0x30); num/=10; } 17 //先清零 //再送数 i=0; while(BCD[i] ==0x30 && i<7) BCD[i++]=' '; //NUM转换成数组存放,但还没有加上小数点 DisText(addr,BCD); } /******************************************** 名称 timeshow(void) * 功能 时间显示程序 *******************************************/ void timeshow(void) { uint8 i,j=0; uint8 timebuff[5]={2,3,4,5,6}; { for (i=0;i<5;i++) { timebuff[j++] = time[i] / 10; timebuff[j++] = time[i] % 10; j++; } } for(i=0;i<2;i++) {LCD_Cursor(1,i+12); WrDat(timebuff[i]+'0');} {LCD_Cursor(1,i+12); WrDat(0x3A);i++;} for(i=3;i<5;i++) {LCD_Cursor(1,i+12); WrDat(timebuff[i]+'0');} LCD_Cursor (1, place); } /********************************** 名称 timer0_init(void) * 功能 中断定时初始化程序 *********************************/ void timer0_init(void) { VICIntSelect=0x00000000; //IRQ中断 VICIntEnable=0x00000010; //中断使能保留 VICVectCntl0=0x00000024; //将定时器0匹配通道优先级0 VICVectAddr0=(int)timerInt; T0PR=1000; //预分频寄存器 T0MR0=60000; //MR0与TC值匹配 T0MCR=0x00000003; T0TCR=0x00000003; //定时器控制复位 T0TCR=0x00000001; //定时器控制启动 } /********************************* 名称irq timerInt(void) 18 * 功能中断处理程序 *********************************/ void __irq timerInt(void) { time[1]++; //min if(time[1]>=60) { time[1]=0; time[0]++; //hour if(time[0]>=24) { time[0]=0;} } VICVectAddr=0; T0IR=0x00000001; //匹配通道0的中断标志 } /************************************ * 名称 ate_or_ope() * 功能 判断是数值还是计算符 *********************************/ int date_or_ope(char key) { if ((key=='.')||(key >= '0' && key <= '9')) { return 1; } else return 0; } /******************************************** 名称 main() * 功能 显示文本 *********************************/ int main(void) { uint8 keyvalue1,keyvalue2; uint8 flag; port_init(); lcd_init(); timer0_init();//时钟初始化 DisText(0x80,txt0); while(1) { timeshow(); keyvalue1=KEYSCAN(); if(keyvalue1!=0xff) //有键按下 { do{ keyvalue2=KEYSCAN(); 19 } while(keyvalue1==keyvalue2); keypressed=KEYCODE1[keyvalue1]; } else keypressed=0xff; if(keypressed!=0xff) { flag = date_or_ope(keypressed); if (flag == 1) { deal_date(); } else { deal_ope(); } } }//while end } //main 函数结束 /*********************************** 名称 deal_date(void) * 功能 数值处理 *********************************/ void deal_date(void) { if (date_or_ope(last_keypres) == 1) //如果上一个键值是数字字符 { *temp_pt++=keypressed; } else { memset(date_temp,0,15); //用字符‘0’填充date_temp的前15个字节 temp_pt=date_temp; *temp_pt++=keypressed; } last_keypres = keypressed; WrDat(keypressed);//显示字符可以在主函数中 place++; } /******************************************** 名称 void deal_ope(void) * 功能 符号处理 ***************************/ void deal_ope(void) //符号处理 { 20 //作按键释放检查 //得到键符 double date_value; double date_result; uint8 i; if (keypressed == 'C') { memset(date_temp,0,15); last_keypres = '='; date[0]=0; date[1]=0; date_pt=&date[1]; oper_pt=operater; operater[0]='N'; LCD_WriteControl(0x01); LCD_Cursor(2,9);WrDat('0'); place=1; } // else if else if (keypressed == 'B') //清除一位数或符号 { if (date_or_ope(last_keypres) == 1) { int j; j=strlen(date_temp); date_temp[j-1]='\\0'; LCD_Cursor(1,place-1); WrDat(' '); LCD_Cursor (1, place--); return ; } else{ *oper_pt='\\0'; oper_pt--; LCD_Cursor(1,place-1); WrDat(' '); LCD_Cursor (1, place--); } }//else if end else if (keypressed=='M'||keypressed=='H') { switch (keypressed) { case 'H': time[0]++; if(time[0]==24) time[0]=0;break; case 'M': time[1]++; 21 if(time[1]==60) time[1]=0;break; default:break; } } else if (keypressed == 'E'||keypressed == 'D'||keypressed == 'L') { WrDat(keypressed);//显示 if (date_or_ope(last_keypres) == 0) { return ; } else { date_result= calculate1(); re_temp1= (long int)(date_result); re_temp2= (long int)((date_result-re_temp1)*100000000); if(date_result<1) { re_temp1=0; re_temp2=(long int)((date_result)*100000000); } ShowInt(0xc1,re_temp1);//整数部分 LCD_Cursor(2,10); WrDat('.'); cov(re_temp2,show); //小数部分显示 for(i=0;i<4;i++) //jiwei? { LCD_Cursor(2,i+11); WrDat(show[i]+'0'); } } last_keypres = '='; } //else if end else if (keypressed == '=') //如果是‘=’ { WrDat(keypressed);//显示 date_value = atof(date_temp);//把date_temp中的数字字符转 数值 *(++date_pt) = date_value; date_result=calculate(); re_temp1= (long int)(date_result); re_temp2= (long int)((date_result-re_temp1)*100000000); if(date_result<1) { re_temp1=0; re_temp2= (long int)((date_result)*100000000); 22 } ShowInt(0xc1,re_temp1);//整数部分 LCD_Cursor(2,10); WrDat('.'); cov(re_temp2,show); //小数部分显示 for(i=0;i<4;i++) { LCD_Cursor(2,i+11); WrDat(show[i]+'0'); } last_keypres = '='; //还原 } else //如果不是‘C’也不是‘=’ { switch (keypressed) { case '*': case '/': case '+': case '-': WrDat(keypressed); date_value = atof(date_temp); *(++date_pt) = date_value; if( *(oper_pt)!='N') //判断前一个是=否 { date_result=calculate(); } *(++oper_pt) = keypressed; last_keypres = keypressed; place++; break; }//switch }//else }//deal-ope /************************************** * 名称 double calculate1() * 功能 开平方等运算 *********************************/ double calculate1(void) { double res2,result; char oper; res2= atof(date_temp);//将转化为数值 oper = keypressed; switch (oper) { case 'L': result =log10(res2); break; 23 case 'E': result = res2*res2; break; case 'D': result = sqrt(res2); break; //开方 default :break; } *(++date_pt) = result; memset(date_temp,0,15); return result; } /********************************** 名称 double calculate(void) * 功能 基本四则运算 *********************************/ double calculate(void) { double res1,res2,result; char oper; oper = *(oper_pt); res2 = *(date_pt--); res1 = *(date_pt--); switch (oper) { case '+': result = res1+res2; break; case '-': if(res1>res2) { result=res1-res2; LCD_Cursor(2,1); WrDat(' '); } else { result=res2-res1; LCD_Cursor(2,1); WrDat('-'); } break; case '*': result = res1*res2; break; case '/': result = res1/res2; break; } *(++date_pt) = result; memset(date_temp,0,15); return result; } 24 /****************************************** * 名称KEYSCAN(void) * 功能键盘扫描程序*********************************/ uint8 KEYSCAN(void) { uint8 i,temp,temp2=0xff; PINSEL0=0x00000000; PINSEL1=0x00000000; IO0DIR=0x0000F000; IO0CLR=0x0000F000; temp=(IO0PIN&0x003F0000)>>16; for(i=0;i<16;i++) { IO0SET=((1<
>16; if(temp!=0x00) //若有键按下 { DelayNS(5); //延时消除抖动 temp=(IO0PIN&0x003F0000)>>16; temp=temp&0x0000003F; // while(((IO0PIN&0x003F0000)>>16)!=0); //再读键盘 if(temp!=0x00) //若有键按下 { switch(temp) //计算键值 { case 0x20:temp2=29-(16-i)*6;break; case 0x10:temp2=28-(16-i)*6;break; case 0x08:temp2=27-(16-i)*6;break; case 0x04:temp2=26-(16-i)*6;break; case 0x02:temp2=25-(16-i)*6;break; case 0x01:temp2=24-(16-i)*6;break; default:temp2=0xff; } } return temp2; } } return 0xff; } 25