十三(4)单片机计算器实例

  按键和液晶,可以组成我们最简易的计算器。下面我们来写一个简易整数计算器提供给大家学习。为了让程序不过于复杂,我们这个计算器不考虑连加,连减等连续计算,不考虑小数情况。加减乘除分别用上下左右来替代,回车表示等于,ESC 表示归 0。程序共分为三部分,一部分是 1602 液晶显示,一部分是按键动作和扫描,一部分是主函数功能。

 
  1. /***************************Lcd1602.c 文件程序源代码*****************************/  
  2. #include <reg52.h>  
  3. #define LCD1602_DB P0  
  4. sbit LCD1602_RS = P1^0;  
  5. sbit LCD1602_RW = P1^1;  
  6. sbit LCD1602_E = P1^5;  
  7.   
  8. /* 等待液晶准备好 */  
  9. void LcdWaitReady(){  
  10.     unsigned char sta;  
  11.     LCD1602_DB = 0xFF;  
  12.     LCD1602_RS = 0;  
  13.     LCD1602_RW = 1;  
  14.     do {  
  15.         LCD1602_E = 1;  
  16.         sta = LCD1602_DB; //读取状态字  
  17.         LCD1602_E = 0;  
  18.     //bit7 等于 1 表示液晶正忙,重复检测直到其等于 0 为止  
  19.     }while (sta & 0x80);  
  20. }  
  21. /* 向 LCD1602 液晶写入一字节命令,cmd-待写入命令值 */  
  22. void LcdWriteCmd(unsigned char cmd){  
  23.     LcdWaitReady();  
  24.     LCD1602_RS = 0;  
  25.     LCD1602_RW = 0;  
  26.     LCD1602_DB = cmd;  
  27.     LCD1602_E = 1;  
  28.     LCD1602_E = 0;  
  29. }  
  30. /* 向 LCD1602 液晶写入一字节数据,dat-待写入数据值 */  
  31. void LcdWriteDat(unsigned char dat){  
  32.     LcdWaitReady();  
  33.     LCD1602_RS = 1;  
  34.     LCD1602_RW = 0;  
  35.     LCD1602_DB = dat;  
  36.     LCD1602_E = 1;  
  37.     LCD1602_E = 0;  
  38. }  
  39. /* 设置显示 RAM 起始地址,亦即光标位置,(x,y)-对应屏幕上的字符坐标 */  
  40. void LcdSetCursor(unsigned char x, unsigned char y){  
  41.     unsigned char addr;  
  42.     if (y == 0){ //由输入的屏幕坐标计算显示 RAM 的地址  
  43.         addr = 0x00 + x; //第一行字符地址从 0x00 起始  
  44.     }else{  
  45.         addr = 0x40 + x; //第二行字符地址从 0x40 起始  
  46.     }  
  47.     LcdWriteCmd(addr | 0x80); //设置 RAM 地址  
  48. }  
  49. /* 在液晶上显示字符串,(x,y)-对应屏幕上的起始坐标,str-字符串指针 */  
  50. void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str){  
  51.     LcdSetCursor(x, y); //设置起始地址  
  52.     while (*str != '\0'){ //连续写入字符串数据,直到检测到结束符  
  53.         LcdWriteDat(*str++);  
  54.     }  
  55. }  
  56. /* 区域清除,清除从(x,y)坐标起始的 len 个字符位 */  
  57. void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len){  
  58.     LcdSetCursor(x, y); //设置起始地址  
  59.     while (len--){ //连续写入空格  
  60.         LcdWriteDat(' ');  
  61.     }  
  62. }  
  63. /* 整屏清除 */  
  64. void LcdFullClear(){  
  65.     LcdWriteCmd(0x01);  
  66. }  
  67. /* 初始化 1602 液晶 */  
  68. void InitLcd1602(){  
  69.     LcdWriteCmd(0x38); //16*2 显示,5*7 点阵,8 位数据接口  
  70.     LcdWriteCmd(0x0C); //显示器开,光标关闭  
  71.     LcdWriteCmd(0x06); //文字不动,地址自动+1  
  72.     LcdWriteCmd(0x01); //清屏  
  73. }   

  Lcd1602.c 文件中根据上层应用的需要增加了 2 个清屏函数:区域清屏——LcdAreaClear,整屏清屏——LcdFullClear。

 
  1. /**************************keyboard.c 文件程序源代码*****************************/  
  2. #include <reg52.h>  
  3. sbit KEY_IN_1 = P2^4;  
  4. sbit KEY_IN_2 = P2^5;  
  5. sbit KEY_IN_3 = P2^6;  
  6. sbit KEY_IN_4 = P2^7;  
  7. sbit KEY_OUT_1 = P2^3;  
  8. sbit KEY_OUT_2 = P2^2;  
  9. sbit KEY_OUT_3 = P2^1;  
  10. sbit KEY_OUT_4 = P2^0;  
  11.   
  12. unsigned char code KeyCodeMap[4][4] = { //矩阵按键编号到标准键盘键码的映射表  
  13.     { '1''2''3', 0x26 }, //数字键 1、数字键 2、数字键 3、向上键  
  14.     { '4''5''6', 0x25 }, //数字键 4、数字键 5、数字键 6、向左键  
  15.     { '7''8''9', 0x28 }, //数字键 7、数字键 8、数字键 9、向下键  
  16.     { '0', 0x1B, 0x0D, 0x27 } //数字键 0、ESC 键、 回车键、 向右键  
  17. };  
  18. unsigned char pdata KeySta[4][4] = { //全部矩阵按键的当前状态  
  19.     {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}  
  20. };  
  21.   
  22. extern void KeyAction(unsigned char keycode);  
  23.   
  24. /* 按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用 */  
  25. void KeyDriver(){  
  26.     unsigned char i, j;  
  27.     static unsigned char pdata backup[4][4] = { //按键值备份,保存前一次的值  
  28.         {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}  
  29.     };  
  30.      
  31.     for (i=0; i<4; i++){ //循环检测 4*4 的矩阵按键  
  32.         for (j=0; j<4; j++){  
  33.             if (backup[i][j] != KeySta[i][j]){ //检测按键动作  
  34.                 if (backup[i][j] != 0){ //按键按下时执行动作  
  35.                     KeyAction(KeyCodeMap[i][j]); //调用按键动作函数  
  36.                 }  
  37.                 backup[i][j] = KeySta[i][j]; //刷新前一次的备份值  
  38.             }  
  39.         }  
  40.     }  
  41. }  
  42. /* 按键扫描函数,需在定时中断中调用,推荐调用间隔 1ms */  
  43. void KeyScan(){  
  44.     unsigned char i;  
  45.     static unsigned char keyout = 0; //矩阵按键扫描输出索引  
  46.     static unsigned char keybuf[4][4] = { //矩阵按键扫描缓冲区  
  47.         {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF},  
  48.         {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}  
  49.     };  
  50.      
  51.     //将一行的 4 个按键值移入缓冲区  
  52.     keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;  
  53.     keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;  
  54.     keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;  
  55.     keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;  
  56.     //消抖后更新按键状态  
  57.     for (i=0; i<4; i++){ //每行 4 个按键,所以循环 4 次  
  58.         if ((keybuf[keyout][i] & 0x0F) == 0x00){  
  59.             //连续 4 次扫描值为 0,即 4*4ms 内都是按下状态时,可认为按键已稳定的按下  
  60.             KeySta[keyout][i] = 0;  
  61.         }else if ((keybuf[keyout][i] & 0x0F) == 0x0F){  
  62.             //连续 4 次扫描值为 1,即 4*4ms 内都是弹起状态时,可认为按键已稳定的弹起  
  63.             KeySta[keyout][i] = 1;  
  64.         }  
  65.     }  
  66.   
  67.     //执行下一次的扫描输出  
  68.     keyout++; //输出索引递增  
  69.     keyout &= 0x03; //索引值加到 4 即归零  
  70.     switch (keyout){ //根据索引,释放当前输出引脚,拉低下次的输出引脚  
  71.         case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;  
  72.         case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;  
  73.         case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;  
  74.         case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;  
  75.         defaultbreak;  
  76.     }  
  77. }   

  keyboard.c 是对之前已经用过多次的矩阵按键驱动的封装,具体到某个按键要执行的动作函数都放到上层的 main.c 中实现,在这个按键驱动文件中只负责调用上层实现的按键动作函数即可。 

 
  1. /*****************************main.c 文件程序源代码******************************/  
  2. #include <reg52.h>  
  3. unsigned char step = 0; //操作步骤  
  4. unsigned char oprt = 0; //运算类型  
  5. signed long num1 = 0; //操作数 1  
  6. signed long num2 = 0; //操作数 2  
  7. signed long result = 0; //运算结果  
  8. unsigned char T0RH = 0; //T0 重载值的高字节  
  9. unsigned char T0RL = 0; //T0 重载值的低字节  
  10.   
  11. void ConfigTimer0(unsigned int ms);  
  12. extern void KeyScan();  
  13. extern void KeyDriver();  
  14. extern void InitLcd1602();  
  15. extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);  
  16. extern void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len);  
  17. extern void LcdFullClear();  
  18.   
  19. void main(){  
  20.     EA = 1; //开总中断  
  21.     ConfigTimer0(1); //配置 T0 定时 1ms  
  22.     InitLcd1602(); //初始化液晶  
  23.     LcdShowStr(15, 1, "0"); //初始显示一个数字 0  
  24.     while (1){  
  25.         KeyDriver(); //调用按键驱动  
  26.     }  
  27. }  
  28. /* 长整型数转换为字符串,str-字符串指针,dat-待转换数,返回值-字符串长度 */  
  29. unsigned char LongToString(unsigned char *str, signed long dat){  
  30.     signed char i = 0;  
  31.     unsigned char len = 0;  
  32.     unsigned char buf[12];  
  33.      
  34.     if (dat < 0){ //如果为负数,首先取绝对值,并在指针上添加负号  
  35.         dat = -dat;  
  36.         *str++ = '-';  
  37.         len++;  
  38.     }  
  39.      
  40.     do { //先转换为低位在前的十进制数组  
  41.         buf[i++] = dat % 10;  
  42.         dat /= 10;  
  43.     } while (dat > 0);  
  44.     len += i; //i 最后的值就是有效字符的个数  
  45.     while (i-- > 0){ //将数组值转换为 ASCII 码反向拷贝到接收指针上  
  46.         *str++ = buf[i] + '0';  
  47.     }  
  48.     *str = '\0'//添加字符串结束符  
  49.     return len; //返回字符串长度  
  50. }  
  51. /* 显示运算符,显示位置 y,运算符类型 type */  
  52. void ShowOprt(unsigned char y, unsigned char type){  
  53.     switch (type){  
  54.         case 0: LcdShowStr(0, y, "+"); break//0 代表+  
  55.         case 1: LcdShowStr(0, y, "-"); break//1 代表-  
  56.         case 2: LcdShowStr(0, y, "*"); break//2 代表*  
  57.         case 3: LcdShowStr(0, y, "/"); break//3 代表/  
  58.         defaultbreak;  
  59.     }  
  60. }  
  61. /* 计算器复位,清零变量值,清除屏幕显示 */  
  62. void Reset(){  
  63.     num1 = 0;  
  64.     num2 = 0;  
  65.     step = 0;  
  66.     LcdFullClear();  
  67. }  
  68. /* 数字键动作函数,n-按键输入的数值 */  
  69. void NumKeyAction(unsigned char n){  
  70.     unsigned char len;  
  71.     unsigned char str[12];  
  72.      
  73.     if (step > 1){ //如计算已完成,则重新开始新的计算  
  74.         Reset();  
  75.     }  
  76.     if (step == 0){ //输入第一操作数  
  77.         num1 = num1*10 + n; //输入数值累加到原操作数上  
  78.         len = LongToString(str, num1); //新数值转换为字符串  
  79.         LcdShowStr(16-len, 1, str); //显示到液晶第二行上  
  80.     }else//输入第二操作数  
  81.         num2 = num2*10 + n; //输入数值累加到原操作数上  
  82.         len = LongToString(str, num2); //新数值转换为字符串  
  83.         LcdShowStr(16-len, 1, str); //显示到液晶第二行上  
  84.     }  
  85. }  
  86. /* 运算符按键动作函数,运算符类型 type */  
  87. void OprtKeyAction(unsigned char type){  
  88.     unsigned char len;  
  89.     unsigned char str[12];  
  90.      
  91.     if (step == 0){ //第二操作数尚未输入时响应,即不支持连续操作  
  92.         len = LongToString(str, num1); //第一操作数转换为字符串  
  93.         LcdAreaClear(0, 0, 16-len); //清除第一行左边的字符位  
  94.         LcdShowStr(16-len, 0, str); //字符串靠右显示在第一行  
  95.         ShowOprt(1, type); //在第二行显示操作符  
  96.         LcdAreaClear(1, 1, 14); //清除第二行中间的字符位  
  97.         LcdShowStr(15, 1, "0"); //在第二行最右端显示 0  
  98.         oprt = type; //记录操作类型  
  99.         step = 1;  
  100.     }  
  101. }  
  102. /* 计算结果函数 */  
  103. void GetResult(){  
  104.     unsigned char len;  
  105.     unsigned char str[12];  
  106.      
  107.     if (step == 1){ //第二操作数已输入时才执行计算  
  108.         step = 2;  
  109.         switch (oprt){ //根据运算符类型计算结果,未考虑溢出问题  
  110.             case 0: result = num1 + num2; break;  
  111.             case 1: result = num1 - num2; break;  
  112.             case 2: result = num1 * num2; break;  
  113.             case 3: result = num1 / num2; break;  
  114.             defaultbreak;  
  115.         }  
  116.         len = LongToString(str, num2); //原第二操作数和运算符显示到第一行  
  117.         ShowOprt(0, oprt);  
  118.         LcdAreaClear(1, 0, 16-1-len);  
  119.         LcdShowStr(16-len, 0, str);  
  120.         len = LongToString(str, result); //计算结果和等号显示在第二行  
  121.         LcdShowStr(0, 1, "=");  
  122.         LcdAreaClear(1, 1, 16-1-len);  
  123.         LcdShowStr(16-len, 1, str);  
  124.     }  
  125. }  
  126. /* 按键动作函数,根据键码执行相应的操作,keycode-按键键码 */  
  127. void KeyAction(unsigned char keycode){  
  128.     if ((keycode>='0') && (keycode<='9')){ //输入字符  
  129.         NumKeyAction(keycode - '0');  
  130.     }else if (keycode == 0x26){ //向上键,+  
  131.         OprtKeyAction(0);  
  132.     }else if (keycode == 0x28){ //向下键,-  
  133.         OprtKeyAction(1);  
  134.     }else if (keycode == 0x25){ //向左键,*  
  135.         OprtKeyAction(2);  
  136.     }else if (keycode == 0x27){ //向右键,÷  
  137.         OprtKeyAction(3);  
  138.     }else if (keycode == 0x0D){ //回车键,计算结果  
  139.         GetResult();  
  140.     }else if (keycode == 0x1B){ //Esc 键,清除  
  141.         Reset();  
  142.         LcdShowStr(15, 1, "0");  
  143.     }  
  144. }  
  145. /* 配置并启动 T0,ms-T0 定时时间 */  
  146. void ConfigTimer0(unsigned int ms){  
  147.     unsigned long tmp; //临时变量  
  148.     tmp = 11059200 / 12; //定时器计数频率  
  149.     tmp = (tmp * ms) / 1000; //计算所需的计数值  
  150.     tmp = 65536 - tmp; //计算定时器重载值  
  151.     tmp = tmp + 28; //补偿中断响应延时造成的误差  
  152.     T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节  
  153.     T0RL = (unsigned char)tmp;  
  154.      
  155.     TMOD &= 0xF0; 
    永不止步步 发表于01-29 11:40 浏览65535次
    分享到:

已有0条评论

暂时还没有回复哟,快来抢沙发吧

添加一条新评论

只有登录用户才能评论,请先登录注册哦!
立即注册
畅学电子网,带你进入电子开发学习世界
专业电子工程技术学习交流社区,加入畅学一起充电加油吧!

x

畅学电子网订阅号