在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

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

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

按鍵和液晶,可以組成我們最簡易的計算器。下面我們來寫一個簡易整數(shù)計算器提供給大家學(xué)習(xí)。為了讓程序不過于復(fù)雜,我們這個計算器不考慮連加,連減等連續(xù)計算,不考慮小數(shù)情況。加減乘除分別用上下左右來替代,回車表示等于,ESC 表示歸0。程序共分為三部分,一部分是 1602 液晶顯示,一部分是按鍵動作和掃描,一部分是主函數(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)計算顯示 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 個字符位 */
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 點陣,8 位數(shù)據(jù)接口
    LcdWriteCmd(0x0C); //顯示器開,光標(biāo)關(guān)閉
    LcdWriteCmd(0x06); //文字不動,地址自動+1
    LcdWriteCmd(0x01); //清屏
}

Lcd1602.c 文件中根據(jù)上層應(yīng)用的需要增加了2個清屏函數(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] = { //矩陣按鍵編號到標(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ū)動函數(shù),檢測按鍵動作,調(diào)度相應(yī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]){ //檢測按鍵動作
                if (backup[i][j] != 0){ //按鍵按下時執(zhí)行動作
                    KeyAction(KeyCodeMap[i][j]); //調(diào)用按鍵動作函數(shù)
                }
                backup[i][j] = KeySta[i][j]; //刷新前一次的備份值
            }
        }
    }
}
/* 按鍵掃描函數(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 個按鍵值移入緩沖區(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 個按鍵,所以循環(huán) 4 次
        if ((keybuf[keyout][i] & 0x0F) == 0x00){
            //連續(xù) 4 次掃描值為 0,即 4*4ms 內(nèi)都是按下狀態(tài)時,可認(rèn)為按鍵已穩(wěn)定的按下
            KeySta[keyout][i] = 0;
        }else if ((keybuf[keyout][i] & 0x0F) == 0x0F){
            //連續(xù) 4 次掃描值為 1,即 4*4ms 內(nèi)都是彈起狀態(tài)時,可認(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ū)動的封裝,具體到某個按鍵要執(zhí)行的動作函數(shù)都放到上層的 main.c 中實現(xiàn),在這個按鍵驅(qū)動文件中只負(fù)責(zé)調(diào)用上層實現(xiàn)的按鍵動作函數(shù)即可。

/*****************************main.c 文件程序源代碼******************************/
#include <reg52.h>
unsigned char step = 0; //操作步驟
unsigned char oprt = 0; //運算類型
signed long num1 = 0; //操作數(shù) 1
signed long num2 = 0; //操作數(shù) 2
signed long result = 0; //運算結(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 定時 1ms
    InitLcd1602(); //初始化液晶
    LcdShowStr(15, 1, "0"); //初始顯示一個數(shù)字 0
    while (1){
        KeyDriver(); //調(diào)用按鍵驅(qū)動
    }
}
/* 長整型數(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ù)號
        dat = -dat;
        *str++ = '-';
        len++;
    }

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

    if (step > 1){ //如計算已完成,則重新開始新的計算
        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); //顯示到液晶第二行上
    }
}
/* 運算符按鍵動作函數(shù),運算符類型 type */
void OprtKeyAction(unsigned char type){
    unsigned char len;
    unsigned char str[12];

    if (step == 0){ //第二操作數(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;
    }
}
/* 計算結(jié)果函數(shù) */
void GetResult(){
    unsigned char len;
    unsigned char str[12];

    if (step == 1){ //第二操作數(shù)已輸入時才執(zhí)行計算
        step = 2;
        switch (oprt){ //根據(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ù)和運算符顯示到第一行
        ShowOprt(0, oprt);
        LcdAreaClear(1, 0, 16-1-len);
        LcdShowStr(16-len, 0, str);
        len = LongToString(str, result); //計算結(jié)果和等號顯示在第二行
        LcdShowStr(0, 1, "=");
        LcdAreaClear(1, 1, 16-1-len);
        LcdShowStr(16-len, 1, str);
    }
}
/* 按鍵動作函數(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){ //回車鍵,計算結(jié)果
        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; //定時器計數(shù)頻率
    tmp = (tmp * ms) / 1000; //計算所需的計數(shù)值
    tmp = 65536 - tmp; //計算定時器重載值
    tmp = tmp + 28; //補償中斷響應(yīng)延時造成的誤差
    T0RH = (unsigned char)(tmp>>8); //定時器重載值拆分為高低字節(jié)
    T0RL = (unsigned char)tmp;

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

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

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