鍍金池/ 教程/ 數(shù)據(jù)庫/ 13.4 單片機(jī)計(jì)算器程序設(shè)計(jì)[詳細(xì)]
18. RS485 通信與 Modbus 協(xié)議
17.5 A/D 差分輸入信號(hào)
15.8 C 語言復(fù)合數(shù)據(jù)類型(結(jié)構(gòu)體,共用體,枚舉類型)
16.3 NEC 協(xié)議紅外遙控器
13.1 單片機(jī)通信時(shí)序解析
14.4 單片機(jī) EEPROM 單字節(jié)讀寫操作時(shí)序
13.3 多個(gè) .c 文件的初步認(rèn)識(shí)
18.2 Modbus 通信協(xié)議介紹
15.1 BCD 碼介紹
18.3 單片機(jī) Modbus 多機(jī)通信程序設(shè)計(jì)
18.1 單片機(jī) RS485 通信接口、控制線、原理圖及程序?qū)嵗?/span>
15. 實(shí)時(shí)時(shí)鐘 DS1302
14.7 單片機(jī) I2C 和 EEPROM 的綜合編程
17. 模數(shù)轉(zhuǎn)換與數(shù)模轉(zhuǎn)換
16.2 紅外遙控通信原理
13.2 1602 液晶整屏移動(dòng)程序
17.6 D/A 輸出
17.7 單片機(jī)信號(hào)發(fā)生器程序
16.4 溫度傳感器 DS18B20
14.6 單片機(jī)EEPROM的頁寫入
13.4 單片機(jī)計(jì)算器程序設(shè)計(jì)[詳細(xì)]
17.2 A/D(模數(shù)轉(zhuǎn)換)的主要指標(biāo)
17.4 PCF8591 應(yīng)用程序
17.1 A/D 和 D/A 的基本概念
17.3 PCF8591硬件接口(電路圖引腳圖)
14.3 單片機(jī) EEPROM 簡介
13.5 單片機(jī)串口通信原理和控制程序
15.5 DS1302 寄存器介紹
15.2 單片機(jī) SPI 通信接口
15.6 DS1302 通信時(shí)序介紹
14.5 單片機(jī) EEPROM 多字節(jié)讀寫操作時(shí)序
16. 紅外通信與 DS18B20 溫度傳感器
14.1 單片機(jī) I2C 時(shí)序介紹
15.3 實(shí)時(shí)時(shí)鐘芯片 DS1302 介紹
15.9 單片機(jī)電子時(shí)鐘程序設(shè)計(jì)
16.1 紅外光的基本原理
15.4 DS1302 的硬件信息
15.7 DS1302 的 BURST 模式
14.2 單片機(jī) I2C 尋址模式
14. 單片機(jī) I2C 總線與 EEPROM
13. 單片機(jī) 1602 液晶與串口的應(yīng)用實(shí)例

13.4 單片機(jī)計(jì)算器程序設(shè)計(jì)[詳細(xì)]

按鍵和液晶,可以組成我們最簡易的計(jì)算器。下面我們來寫一個(gè)簡易整數(shù)計(jì)算器提供給大家學(xué)習(xí)。為了讓程序不過于復(fù)雜,我們這個(gè)計(jì)算器不考慮連加,連減等連續(xù)計(jì)算,不考慮小數(shù)情況。加減乘除分別用上下左右來替代,回車表示等于,ESC 表示歸0。程序共分為三部分,一部分是 1602 液晶顯示,一部分是按鍵動(dòng)作和掃描,一部分是主函數(shù)功能。

/***************************Lcd1602.c 文件程序源代碼*****************************/
#include <reg52.h>
#define LCD1602_DB P0
sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E = P1^5;

/* 等待液晶準(zhǔn)備好 */
void LcdWaitReady(){
    unsigned char sta;
    LCD1602_DB = 0xFF;
    LCD1602_RS = 0;
    LCD1602_RW = 1;
    do {
        LCD1602_E = 1;
        sta = LCD1602_DB; //讀取狀態(tài)字
        LCD1602_E = 0;
    //bit7 等于 1 表示液晶正忙,重復(fù)檢測直到其等于 0 為止
    }while (sta & 0x80);
}
/* 向 LCD1602 液晶寫入一字節(jié)命令,cmd-待寫入命令值 */
void LcdWriteCmd(unsigned char cmd){
    LcdWaitReady();
    LCD1602_RS = 0;
    LCD1602_RW = 0;
    LCD1602_DB = cmd;
    LCD1602_E = 1;
    LCD1602_E = 0;
}
/* 向 LCD1602 液晶寫入一字節(jié)數(shù)據(jù),dat-待寫入數(shù)據(jù)值 */
void LcdWriteDat(unsigned char dat){
    LcdWaitReady();
    LCD1602_RS = 1;
    LCD1602_RW = 0;
    LCD1602_DB = dat;
    LCD1602_E = 1;
    LCD1602_E = 0;
}
/* 設(shè)置顯示 RAM 起始地址,亦即光標(biāo)位置,(x,y)-對應(yīng)屏幕上的字符坐標(biāo) */
void LcdSetCursor(unsigned char x, unsigned char y){
    unsigned char addr;
    if (y == 0){ //由輸入的屏幕坐標(biāo)計(jì)算顯示 RAM 的地址
        addr = 0x00 + x; //第一行字符地址從 0x00 起始
    }else{
        addr = 0x40 + x; //第二行字符地址從 0x40 起始
    }
    LcdWriteCmd(addr | 0x80); //設(shè)置 RAM 地址
}
/* 在液晶上顯示字符串,(x,y)-對應(yīng)屏幕上的起始坐標(biāo),str-字符串指針 */
void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str){
    LcdSetCursor(x, y); //設(shè)置起始地址
    while (*str != '\0'){ //連續(xù)寫入字符串?dāng)?shù)據(jù),直到檢測到結(jié)束符
        LcdWriteDat(*str++);
    }
}
/* 區(qū)域清除,清除從(x,y)坐標(biāo)起始的 len 個(gè)字符位 */
void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len){
    LcdSetCursor(x, y); //設(shè)置起始地址
    while (len--){ //連續(xù)寫入空格
        LcdWriteDat(' ');
    }
}
/* 整屏清除 */
void LcdFullClear(){
    LcdWriteCmd(0x01);
}
/* 初始化 1602 液晶 */
void InitLcd1602(){
    LcdWriteCmd(0x38); //16*2 顯示,5*7 點(diǎn)陣,8 位數(shù)據(jù)接口
    LcdWriteCmd(0x0C); //顯示器開,光標(biāo)關(guān)閉
    LcdWriteCmd(0x06); //文字不動(dòng),地址自動(dòng)+1
    LcdWriteCmd(0x01); //清屏
}

Lcd1602.c 文件中根據(jù)上層應(yīng)用的需要增加了2個(gè)清屏函數(shù):區(qū)域清屏——LcdAreaClear,整屏清屏——LcdFullClear。

/**************************keyboard.c 文件程序源代碼*****************************/
#include <reg52.h>
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] = { //矩陣按鍵編號(hào)到標(biāo)準(zhǔn)鍵盤鍵碼的映射表
    { '1', '2', '3', 0x26 }, //數(shù)字鍵 1、數(shù)字鍵 2、數(shù)字鍵 3、向上鍵
    { '4', '5', '6', 0x25 }, //數(shù)字鍵 4、數(shù)字鍵 5、數(shù)字鍵 6、向左鍵
    { '7', '8', '9', 0x28 }, //數(shù)字鍵 7、數(shù)字鍵 8、數(shù)字鍵 9、向下鍵
    { '0', 0x1B, 0x0D, 0x27 } //數(shù)字鍵 0、ESC 鍵、 回車鍵、 向右鍵
};
unsigned char pdata KeySta[4][4] = { //全部矩陣按鍵的當(dāng)前狀態(tài)
    {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}
};

extern void KeyAction(unsigned char keycode);

/* 按鍵驅(qū)動(dòng)函數(shù),檢測按鍵動(dòng)作,調(diào)度相應(yīng)動(dòng)作函數(shù),需在主循環(huán)中調(diào)用 */
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++){ //循環(huán)檢測 4*4 的矩陣按鍵
        for (j=0; j<4; j++){
            if (backup[i][j] != KeySta[i][j]){ //檢測按鍵動(dòng)作
                if (backup[i][j] != 0){ //按鍵按下時(shí)執(zhí)行動(dòng)作
                    KeyAction(KeyCodeMap[i][j]); //調(diào)用按鍵動(dòng)作函數(shù)
                }
                backup[i][j] = KeySta[i][j]; //刷新前一次的備份值
            }
        }
    }
}
/* 按鍵掃描函數(shù),需在定時(shí)中斷中調(diào)用,推薦調(diào)用間隔 1ms */
void KeyScan(){
    unsigned char i;
    static unsigned char keyout = 0; //矩陣按鍵掃描輸出索引
    static unsigned char keybuf[4][4] = { //矩陣按鍵掃描緩沖區(qū)
        {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF},
        {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}
    };

    //將一行的 4 個(gè)按鍵值移入緩沖區(qū)
    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;
    //消抖后更新按鍵狀態(tài)
    for (i=0; i<4; i++){ //每行 4 個(gè)按鍵,所以循環(huán) 4 次
        if ((keybuf[keyout][i] & 0x0F) == 0x00){
            //連續(xù) 4 次掃描值為 0,即 4*4ms 內(nèi)都是按下狀態(tài)時(shí),可認(rèn)為按鍵已穩(wěn)定的按下
            KeySta[keyout][i] = 0;
        }else if ((keybuf[keyout][i] & 0x0F) == 0x0F){
            //連續(xù) 4 次掃描值為 1,即 4*4ms 內(nèi)都是彈起狀態(tài)時(shí),可認(rèn)為按鍵已穩(wěn)定的彈起
            KeySta[keyout][i] = 1;
        }
    }

    //執(zhí)行下一次的掃描輸出
    keyout++; //輸出索引遞增
    keyout &= 0x03; //索引值加到 4 即歸零
    switch (keyout){ //根據(jù)索引,釋放當(dāng)前輸出引腳,拉低下次的輸出引腳
        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 是對之前已經(jīng)用過多次的矩陣按鍵驅(qū)動(dòng)的封裝,具體到某個(gè)按鍵要執(zhí)行的動(dòng)作函數(shù)都放到上層的 main.c 中實(shí)現(xiàn),在這個(gè)按鍵驅(qū)動(dòng)文件中只負(fù)責(zé)調(diào)用上層實(shí)現(xiàn)的按鍵動(dòng)作函數(shù)即可。

/*****************************main.c 文件程序源代碼******************************/
#include <reg52.h>
unsigned char step = 0; //操作步驟
unsigned char oprt = 0; //運(yùn)算類型
signed long num1 = 0; //操作數(shù) 1
signed long num2 = 0; //操作數(shù) 2
signed long result = 0; //運(yùn)算結(jié)果
unsigned char T0RH = 0; //T0 重載值的高字節(jié)
unsigned char T0RL = 0; //T0 重載值的低字節(jié)

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 定時(shí) 1ms
    InitLcd1602(); //初始化液晶
    LcdShowStr(15, 1, "0"); //初始顯示一個(gè)數(shù)字 0
    while (1){
        KeyDriver(); //調(diào)用按鍵驅(qū)動(dòng)
    }
}
/* 長整型數(shù)轉(zhuǎn)換為字符串,str-字符串指針,dat-待轉(zhuǎn)換數(shù),返回值-字符串長度 */
unsigned char LongToString(unsigned char *str, signed long dat){
    signed char i = 0;
    unsigned char len = 0;
    unsigned char buf[12];

    if (dat < 0){ //如果為負(fù)數(shù),首先取絕對值,并在指針上添加負(fù)號(hào)
        dat = -dat;
        *str++ = '-';
        len++;
    }

    do { //先轉(zhuǎn)換為低位在前的十進(jìn)制數(shù)組
        buf[i++] = dat % 10;
        dat /= 10;
    } while (dat > 0);
    len += i; //i 最后的值就是有效字符的個(gè)數(shù)
    while (i-- > 0){ //將數(shù)組值轉(zhuǎn)換為 ASCII 碼反向拷貝到接收指針上
        *str++ = buf[i] + '0';
    }
    *str = '\0'; //添加字符串結(jié)束符
    return len; //返回字符串長度
}
/* 顯示運(yùn)算符,顯示位置 y,運(yùn)算符類型 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;
    }
}
/* 計(jì)算器復(fù)位,清零變量值,清除屏幕顯示 */
void Reset(){
    num1 = 0;
    num2 = 0;
    step = 0;
    LcdFullClear();
}
/* 數(shù)字鍵動(dòng)作函數(shù),n-按鍵輸入的數(shù)值 */
void NumKeyAction(unsigned char n){
    unsigned char len;
    unsigned char str[12];

    if (step > 1){ //如計(jì)算已完成,則重新開始新的計(jì)算
        Reset();
    }
    if (step == 0){ //輸入第一操作數(shù)
        num1 = num1*10 + n; //輸入數(shù)值累加到原操作數(shù)上
        len = LongToString(str, num1); //新數(shù)值轉(zhuǎn)換為字符串
        LcdShowStr(16-len, 1, str); //顯示到液晶第二行上
    }else{ //輸入第二操作數(shù)
        num2 = num2*10 + n; //輸入數(shù)值累加到原操作數(shù)上
        len = LongToString(str, num2); //新數(shù)值轉(zhuǎn)換為字符串
        LcdShowStr(16-len, 1, str); //顯示到液晶第二行上
    }
}
/* 運(yùn)算符按鍵動(dòng)作函數(shù),運(yùn)算符類型 type */
void OprtKeyAction(unsigned char type){
    unsigned char len;
    unsigned char str[12];

    if (step == 0){ //第二操作數(shù)尚未輸入時(shí)響應(yīng),即不支持連續(xù)操作
        len = LongToString(str, num1); //第一操作數(shù)轉(zhuǎn)換為字符串
        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;
    }
}
/* 計(jì)算結(jié)果函數(shù) */
void GetResult(){
    unsigned char len;
    unsigned char str[12];

    if (step == 1){ //第二操作數(shù)已輸入時(shí)才執(zhí)行計(jì)算
        step = 2;
        switch (oprt){ //根據(jù)運(yùn)算符類型計(jì)算結(jié)果,未考慮溢出問題
            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); //原第二操作數(shù)和運(yùn)算符顯示到第一行
        ShowOprt(0, oprt);
        LcdAreaClear(1, 0, 16-1-len);
        LcdShowStr(16-len, 0, str);
        len = LongToString(str, result); //計(jì)算結(jié)果和等號(hào)顯示在第二行
        LcdShowStr(0, 1, "=");
        LcdAreaClear(1, 1, 16-1-len);
        LcdShowStr(16-len, 1, str);
    }
}
/* 按鍵動(dòng)作函數(shù),根據(jù)鍵碼執(zhí)行相應(yīng)的操作,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){ //回車鍵,計(jì)算結(jié)果
        GetResult();
    }else if (keycode == 0x1B){ //Esc 鍵,清除
        Reset();
        LcdShowStr(15, 1, "0");
    }
}
/* 配置并啟動(dòng) T0,ms-T0 定時(shí)時(shí)間 */
void ConfigTimer0(unsigned int ms){
    unsigned long tmp; //臨時(shí)變量
    tmp = 11059200 / 12; //定時(shí)器計(jì)數(shù)頻率
    tmp = (tmp * ms) / 1000; //計(jì)算所需的計(jì)數(shù)值
    tmp = 65536 - tmp; //計(jì)算定時(shí)器重載值
    tmp = tmp + 28; //補(bǔ)償中斷響應(yīng)延時(shí)造成的誤差
    T0RH = (unsigned char)(tmp>>8); //定時(shí)器重載值拆分為高低字節(jié)
    T0RL = (unsigned char)tmp;

    TMOD &= 0xF0; //清零 T0 的控制位
    TMOD |= 0x01; //配置 T0 為模式 1
    TH0 = T0RH; //加載 T0 重載值
    TL0 = T0RL;
    ET0 = 1; //使能 T0 中斷
    TR0 = 1; //啟動(dòng) T0
}
/* T0 中斷服務(wù)函數(shù),執(zhí)行按鍵掃描 */
void InterruptTimer0() interrupt 1{
    TH0 = T0RH; //重新加載重載值
    TL0 = T0RL;
    KeyScan(); //按鍵掃描
}

main.c 文件實(shí)現(xiàn)所有應(yīng)用層的操作函數(shù),即計(jì)算器功能所需要信息顯示、按鍵動(dòng)作響應(yīng)等,另外還包括主循環(huán)和定時(shí)中斷的調(diào)度。

通過這樣一個(gè)程序,大家一方面學(xué)習(xí)如何進(jìn)行多個(gè) .c 文件的編程,另外一個(gè)方面學(xué)會(huì)多個(gè)函數(shù)之間的靈活調(diào)用。可以把這個(gè)程序看成是一個(gè)簡單的小項(xiàng)目,學(xué)習(xí)一下項(xiàng)目編程都是如何進(jìn)行和布局的。不要把項(xiàng)目想象的太難,再復(fù)雜的項(xiàng)目也是這種簡單程序的組合和擴(kuò)展而已。