单片机计算器实例

发布者:Joyful444Life最新更新时间:2016-12-24 来源: eefocus关键字:单片机  计算器 手机看文章 扫描二维码
随时随地手机看文章

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

/***************************Lcd1602.c 文件程序源代码*****************************/

#include

#define LCD1602_DB P0

sbit LCD1602_RS = P1^0;

sbit LCD1602_RW = P1^1;

sbit LCD1602_E = P1^5;

/* 等待液晶准备好 */

void LcdWaitReady(){

    unsigned char sta;

    LCD1602_DB = 0xFF;

    LCD1602_RS = 0;

    LCD1602_RW = 1;

    do {

        LCD1602_E = 1;

        sta = LCD1602_DB; //读取状态字

        LCD1602_E = 0;

    //bit7 等于 1 表示液晶正忙,重复检测直到其等于 0 为止

    }while (sta & 0x80);

}

/* 向 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 起始

    }else{

        addr = 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); //文字不动,地址自动+1

    LcdWriteCmd(0x01); //清屏

}

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

/**************************keyboard.c 文件程序源代码*****************************/

#include

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 KeyAction(unsigned char keycode);

/* 按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用 */

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;

    }

}

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

/*****************************main.c 文件程序源代码******************************/

#include

unsigned char step = 0; //操作步骤

unsigned char oprt = 0; //运算类型

signed long num1 = 0; //操作数 1

signed long num2 = 0; //操作数 2

signed long result = 0; //运算结果

unsigned char T0RH = 0; //T0 重载值的高字节

unsigned char T0RL = 0; //T0 重载值的低字节

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 LcdAreaClear(unsigned char x, unsigned char y, unsigned char len);

extern void LcdFullClear();

void main(){

    EA = 1; //开总中断

    ConfigTimer0(1); //配置 T0 定时 1ms

    InitLcd1602(); //初始化液晶

    LcdShowStr(15, 1, "0"); //初始显示一个数字 0

    while (1){

        KeyDriver(); //调用按键驱动

    }

}

/* 长整型数转换为字符串,str-字符串指针,dat-待转换数,返回值-字符串长度 */

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; //i 最后的值就是有效字符的个数

    while (i-- > 0){ //将数组值转换为 ASCII 码反向拷贝到接收指针上

        *str++ = buf[i] + '0';

    }

    *str = '\0'; //添加字符串结束符

    return len; //返回字符串长度

}

/* 显示运算符,显示位置 y,运算符类型 type */

void ShowOprt(unsigned char y, unsigned char type){

    switch (type){

        case 0: LcdShowStr(0, y, "+"); break; //0 代表+

        case 1: LcdShowStr(0, y, "-"); break; //1 代表-

        case 2: LcdShowStr(0, y, "*"); break; //2 代表*

        case 3: LcdShowStr(0, y, "/"); break; //3 代表/

        default: break;

    }

}

/* 计算器复位,清零变量值,清除屏幕显示 */

void Reset(){

    num1 = 0;

    num2 = 0;

    step = 0;

    LcdFullClear();

}

/* 数字键动作函数,n-按键输入的数值 */

void NumKeyAction(unsigned char n){

    unsigned char len;

    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); //显示到液晶第二行上

    }

}

/* 运算符按键动作函数,运算符类型 type */

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"); //在第二行最右端显示 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);

    }

}

/* 按键动作函数,根据键码执行相应的操作,keycode-按键键码 */

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){ //Esc 键,清除

        Reset();

        LcdShowStr(15, 1, "0");

    }

}

/* 配置并启动 T0,ms-T0 定时时间 */

void ConfigTimer0(unsigned int ms){

    unsigned long tmp; //临时变量

    tmp = 11059200 / 12; //定时器计数频率

    tmp = (tmp * ms) / 1000; //计算所需的计数值

    tmp = 65536 - tmp; //计算定时器重载值

    tmp = tmp + 28; //补偿中断响应延时造成的误差

    T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节

    T0RL = (unsigned char)tmp;

   

    TMOD &= 0xF0; //清零 T0 的控制位

    TMOD |= 0x01; //配置 T0 为模式 1

    TH0 = T0RH; //加载 T0 重载值

    TL0 = T0RL;

    ET0 = 1; //使能 T0 中断

    TR0 = 1; //启动 T0

}

/* T0 中断服务函数,执行按键扫描 */

void InterruptTimer0() interrupt 1{

    TH0 = T0RH; //重新加载重载值

    TL0 = T0RL;

    KeyScan(); //按键扫描

}

main.c 文件实现所有应用层的操作函数,即计算器功能所需要信息显示、按键动作响应等,另外还包括主循环和定时中断的调度。


通过这样一个程序,大家一方面学习如何进行多个.c 文件的编程,另外一个方面学会多个函数之间的灵活调用。可以把这个程序看成是一个简单的小项目,学习一下项目编程都是如何进行和布局的。不要把项目想象的太难,再复杂的项目也是这种简单程序的组合和扩展而已。


关键字:单片机  计算器 引用地址:单片机计算器实例

上一篇:多个.c文件的初步认识
下一篇:串口通信原理和控制程序

推荐阅读最新更新时间:2024-03-16 15:26

单片机典型案例开发(二)
一、宿舍智能报警系统设计方案   摘要:该系统以凌阳公司的16位单片机SPCE061A作为主控制器,通过烟雾传感器与人体热释电红外传感器分别感应烟雾与人体红外信号,通过单片机做出报警动作。本系统设计主要是满足宿舍自动报警的需要,设计的目标是要实现语音报警、时间设定和显示、灯光报警并实现网络报警等功能。   1 设计方案   1.1 主控制器的选择   采用凌阳公司的16位单片机SPCE061A作为主控制器。由于SPCE061A内置有2 KB的SRAM和32 KB的内存FLASH,能满足本系统存储密码及各类数据的要求,且CPU时钟频率高达49.152 MHz,能保证响应的快速性,内置的语音模块更提高了系统人机界面的友好性。
[模拟电子]
<font color='red'>单片机</font>典型案例开发(二)
从零开始51单片机教程 —— 13 单片机逻辑运算类指令
对单片机的累加器A的逻辑操作 : CLR A ;将A中的值清0,单周期单字节指令,与MOV A,#00H效果相同。 CPL A ;将A中的值按位取反 RL A ;将A中的值逻辑左移 RLC A ;将A中的值加上进位位进行逻辑左移 RR A ;将A中的值进行逻辑右移 RRC A ;将A中的值加上进位位进行逻辑右移 SWAP A ;将A中的值高、低4位交换。 例:(A)=73H,则执行CPL A,这样进行: 73H化为二进制为01110011, 逐位取反即为 10001100,也就是8CH。 RL A是将(A)中的值的第7位送到第0位,第0位送1位,依次类推。 例:A中的值为68H,执行RL A。68H化为二
[单片机]
基于单片机系统采用DMA块传输方式实现高速数据采集
   摘 要: 介绍一种基于单片机系统设计的DMA硬件电路,以字块传输方式与高速A/D接口。结合在数字式磁通表设计中的应用给出其硬件软件设计方案实例。     关键词: 单片机系统 直接存储器存取(DMA)方式 高速A/D     PC机中外设与内存储器之间数据直接传输的DMA功能以其高效、高速、CPU资源占用少等特点已被广泛应用,这一功能通过安装在主板上的专用DMA控制器芯片或集成在外围控制芯片来实现。单片机的应用领域也常常需要有高速数据传输或数据采集,虽然近些年单片机速度有所提高,仍然无法应付类似单脉冲信号捕获、周期信号频谱分析等需要采用高速A/D的场合。对于速率在100ksps以上的数据采集或传输一般的中断查
[应用]
STM32F103C8T6单片机IAP升级
关于IAP升级的方法和原理,网上已经有很多资料了,这块就不再说了,现在就将bootloader和app配置方法整理如下: APP程序就是一个简单的LED闪烁。 APP设置为从FLASH中启动: STM32F103C8T6单片机flash有64K,前20K空间留给bootloader,从20K之后开始存放APP程序。所以IROM1开始地址设置为 0x8005000,大小为20K。如果APP程序比较大的话,可以修改这个大小值。 然后在程序开始位置设置重新映射复位向量表。让程序从0x8005000位置开始执行。 APP设置为从SRAM中启动: APP要从SRAM中运行,那么就要重新映射SRAM中的复位向量表。 由于在
[单片机]
STM32F103C8T6<font color='red'>单片机</font>IAP升级
8051单片机I/O引脚工作原理
一、P0端口的结构及工作原理 P0端口8位中的一位结构图见下图: 由上图可见,P0端口由锁存器、输入缓冲器、切换开关、一个与非门、一个与门及场效应管驱动电路构成。再看图的右边,标号为P0.X引脚的图标,也就是说P0.X引脚可以是P0.0到P0.7的任何一位,即在P0口有8个与上图相同的电路组成。 下面,我们先就组成P0口的每个单元部份跟大家介绍一下: 先看输入缓冲器:在P0口中,有两个三态的缓冲器,在学数字电路时,我们已知道,三态门有三个状态,即在其的输出端可以是高电平、低电平,同时还有一种就是高阻状态(或称为禁止状态),大家看上图,上面一个是读锁存器的缓冲器,也就是说,要读取D锁存器输出端Q的数据,那就得使读锁
[单片机]
8051<font color='red'>单片机</font>I/O引脚工作原理
爱特梅尔推出全新AVR® 微控制器系列
  爱特梅尔公司(Atmel® Corporation)宣布推出专为满足具有CAN和LIN连接能力的先进电机控制应用对高精度脉宽调制(PWM)的需求而开发的全新AVR® 微控制器系列ATmega16M1、ATmega32M1和ATmega64M1,瞄准工业控制市场。新器件系列具有16、32和64KB闪存、通用IO引脚、模数转换器、模拟比较器、功率级控制器,以及8位和16位定时器,是需要CAN和LIN连接能力之工业控制应用的理想选择。   ATmega16M1、ATmega32M1和ATmega64M1基于高性能AVR 8位RISC架构,集成了复杂电机控制算法所有必须的基础功能。这些器件提供了独特的功能组合,能够通过合适的驱动器和
[工业控制]
有关STC单片机发热、复位的问题
最近有个项目,需要一个继电器,接收到低电平就动作,用的STC15W104、使用的市面上的开关电源5V/3A. 本来看似简单到底的一个东西,却花费了将近3天的时间搞定。 1.现象一 产品是1U机箱 内部一个ARM主板,需要定时硬重启一次。暂且叫STC控制板,控制板在不带载的情况下,触发正常,与程序很符合,但是带载后,第一次触发正常,第二次单片机就开始发烫,能到80多度,然后直接冒烟,这个现象100%会出现,控制板的继电器没有使用隔离。 解决办法: 在5V电源处并了一个1000UF的大电容。曾尝试并5.1V稳压管,发现不顶用。 2.现象二 板子会误动作,尤其是周围有人,或者是工作一段时间,或者是开关963电
[单片机]
单片机与CPCI总线的脉冲信号检测系统的工作方法简述
引言 在工业控制领域,通常有大量的脉冲信号用于控制其他设备或部件的开关或者工作状态切换。这些脉冲信号除了常规计算机系统采用的+5 V接口电平外,还有+12 V、+30 V,乃至更高幅度的接口电平,通常为功率型电流驱动信号。本文提出了一种两级测试系统的设计思路,给出了在较宽的范围内兼容不同接口电平的脉冲信号检测系统的设计方案,采用标准CPCI总线接口设计,具有良好的兼容性和扩展性,适用于产品功能测试或系统集成测试。 1 测试系统架构 如图1所示,测试系统采用二级(主控机、下位机)结构设计,由主控计算机(即主控机)、测试客户机(即下位机)、局域网、电缆及运行于各设备中的测试软件共同构成。主控计算机属主控机一级,控制测试客户机,测
[单片机]
<font color='red'>单片机</font>与CPCI总线的脉冲信号检测系统的工作方法简述
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved