計算器簡介
計算器是現代人發明的可以進行數字運算的電子機器。現代的電子計算器能進行數學運算的手持電子機器,擁有集成電路芯片,但結構簡單,功能弱,但較為方便與廉價,可廣泛運用于商業交易中,主要是計算結果是必備的辦公用品之一。為節省電能,計算器都采用CMOS工藝制作的大規模集成電路。低檔計算器的運算器、控制器由數字邏輯電路實現簡單的串行運算,鍵盤是計算器的輸入部件,一般采用接觸式或傳感式。為減小計算器的尺寸,一鍵常常有多種功能。顯示器是計算器的輸出部件,有數碼管顯示器或液晶顯示器等。那麼今天給各位朋友介紹的是用單片機制作的簡單的模拟計算器。
簡易計算器
為了幫助單片機愛好者學習單片機,本計算器是系列STC89C51RC單片機為核心構成的簡易計算器系統。該系統通過單片機控制,實現對4*4鍵盤掃描進行實時的按鍵檢測,并由LCD1602顯示屏将過程與結果顯示出來。硬件相對比較簡單,主要由四部分組成。第一部分是單片機最小系統;第二部分是 4*4矩陣鍵盤;第三部分是LCD1602顯示屏;第四部分是系統 5V電源。還是先說說制作流程:
第一步是繪制電路原理圖
以單片機為主控芯片的簡易計算器原理圖
第二步是根據設計繪制的原理圖購買電子元器件
所用電子元器件明細表
單片機主控芯片——-STC89C52RC 1片
LCD液晶顯示模塊——LCD1602 1片
點動按鍵BUTTON -- -- 17個
自鎖開關按鍵------ 1個
10K可調電阻-----1個
晶振----- 1個
10K電阻-----9個
10UF電解電容-----1個
穩頻電容30PF----2個
PCB萬能實驗闆 ----1塊
第三步是萬能實驗闆的焊接
由于所用原件比較少焊接相對比較簡單,焊接時主要注意單片機芯片最好用插座,便于燒寫程序,還有就是LCD1602 的引腳要焊接正确,不能焊接錯誤,再一個就是16X16的矩陣按鍵焊接時也要留心。其他都比較好焊接。
簡易計算器電路闆正面
簡易計算器焊接反面
下面主要說說LCD1602液晶這個顯示模塊,那個大大的,平時第一行顯示 16 個小黑塊,第二行什麼都不顯示的東西就是 1602 液晶, 1602 液晶主要顯示容量 16 x 2 個字符,芯片工作電壓 4.5~5.5V ,工作電流 2.0mA(5.0V) ,模塊最佳工作電壓 5.0V 1602 液晶,從它的名字我們就可以看出它的顯示容量,就是可以顯示 2 行,每行 16 個字符的液晶。它的工作電壓是 4.5V~5.5V,這點我們直接按照 5V電源接上就可以了,但是保證我們的 5V 系統最低不能低于 4.5V。在 5V 工作電壓下測量它的工作電流是 2mA,大家注意,這個 2mA 僅僅是指液晶,而它的黃綠背光都是用 LED 做的,所以功耗一般有一二十毫安。1602 液晶一共 16 個引腳,每個引腳的功能如下注釋說明。
1, VSS--- 電源地
2, VDD---- 電源正極
3 ,VL---- 液晶顯示偏壓信号
4 ,RS---- 數據/命令選擇端(H/L)
5, R/W---- 讀/寫選擇端(H/L)
6, E---- 使能信号端
7, D0 --Data I/O (輸入輸出口)
8, D1--- Data I/O(輸入輸出口)
9, D2--- Data I/O (輸入輸出口)
10, D3 ---Data I/O (輸入輸出口)
11, D4--- Data I/O (輸入輸出口)
12 ,D5 ---Data I/O (輸入輸出口)
13 ,D6 ---Data I/O (輸入輸出口)
14, D7 ---Data I/O (輸入輸出口)
15, BLA ---背光源正極(輸入輸出口)
16, BLK ----背光源負極(輸入輸出口)
LCD1602正面
LCD1602背面
第四部分是程序編寫部分
我們制作最簡易的計算器可由按鍵和液晶兩個元件為核心。下面我們來共同學習一個簡易整數計算器。為了不讓程序太複雜,我們這個計算器不考慮連加,連減等連續計算,不考慮小數情況。加鍵、減鍵、乘鍵、除鍵、和0-9鍵、等于鍵、清零鍵等16個按鍵,組成一個矩陣鍵盤。我們通過模塊化編程,其程序共分為三部分,第一部分是主函數模塊,第二部分是1602 液晶顯示模塊,第三部分是按鍵動作和掃描模塊,我們先說主程序模塊。在必要的語句後面都加了注釋,方便大家理解。
主函數模塊
#include <reg52.h>
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();//外部液晶LCD1602函數初始化聲明
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( void)
{
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(); //按鍵掃描
}
1602 液晶顯示模塊
#include <reg52.h
#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;
} while (sta & 0x80); //bit7等于1表示液晶正忙,重複檢測直到其等于0為止
}
/* 向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。
按鍵動作和掃描模塊
#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] = { //矩陣按鍵編号到标準鍵盤鍵碼的映射表
{ '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、清零鍵、 等于鍵、 除号鍵
};
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 文件實現所有應用層的操作函數,即計算器功能所需要信息顯示、按鍵動作響應等,另外還包括主循環和定時中斷的調度。 通過這樣一個程序,我們可以學習如何進行多個.c 文件的編程,另外一個方面學會多個函數之間的靈活調用。可以把這個程序看成是一個簡單的小項目進行練習。
第五部分是調試
調試分為硬件調試和軟件調試兩部分,調試時先調試硬件電路,然後再調試軟件。硬件比較簡單容易調試,主要是軟件的調試要費些時間,隻要有信心,就能在調試過程中鍛煉自己的發現問題、分析問題、解決問題的能力。這個程序有點長,希望喜歡單片機的小夥伴們耐下心,慢慢理解,有問題可以留言、讨論!
調試完成的界面3795X593的結果
調試好的界面 3795x593
希望這個小項目能對喜歡單片機的小夥伴們有一定的幫助!喜歡的話請關注、留言、别忘了點個贊哦!你的支持是我前進的動力!
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!