tft每日頭條

 > 生活

 > 初學者怎樣給單片機寫程序

初學者怎樣給單片機寫程序

生活 更新时间:2024-11-29 12:40:20

計算器簡介

計算器是現代人發明的可以進行數字運算的電子機器。現代的電子計算器能進行數學運算的手持電子機器,擁有集成電路芯片,但結構簡單,功能弱,但較為方便與廉價,可廣泛運用于商業交易中,主要是計算結果是必備的辦公用品之一。為節省電能,計算器都采用CMOS工藝制作的大規模集成電路。低檔計算器的運算器、控制器由數字邏輯電路實現簡單的串行運算,鍵盤是計算器的輸入部件,一般采用接觸式或傳感式。為減小計算器的尺寸,一鍵常常有多種功能。顯示器是計算器的輸出部件,有數碼管顯示器或液晶顯示器等。那麼今天給各位朋友介紹的是用單片機制作的簡單的模拟計算器。

初學者怎樣給單片機寫程序(送給初學單片機朋友幹貨之二)1

簡易計算器

為了幫助單片機愛好者學習單片機,本計算器是系列STC89C51RC單片機為核心構成的簡易計算器系統。該系統通過單片機控制,實現對4*4鍵盤掃描進行實時的按鍵檢測,并由LCD1602顯示屏将過程與結果顯示出來。硬件相對比較簡單,主要由四部分組成。第一部分是單片機最小系統;第二部分是 4*4矩陣鍵盤;第三部分是LCD1602顯示屏;第四部分是系統 5V電源。還是先說說制作流程:

第一步是繪制電路原理圖

初學者怎樣給單片機寫程序(送給初學單片機朋友幹貨之二)2

以單片機為主控芯片的簡易計算器原理圖

第二步是根據設計繪制的原理圖購買電子元器件

所用電子元器件明細表

單片機主控芯片——-STC89C52RC 1片

LCD液晶顯示模塊——LCD1602 1片

點動按鍵BUTTON -- -- 17個

自鎖開關按鍵------ 1個

10K可調電阻-----1個

晶振----- 1個

10K電阻-----9個

10UF電解電容-----1個

穩頻電容30PF----2個

PCB萬能實驗闆 ----1塊

第三步是萬能實驗闆的焊接

由于所用原件比較少焊接相對比較簡單,焊接時主要注意單片機芯片最好用插座,便于燒寫程序,還有就是LCD1602 的引腳要焊接正确,不能焊接錯誤,再一個就是16X16的矩陣按鍵焊接時也要留心。其他都比較好焊接。

初學者怎樣給單片機寫程序(送給初學單片機朋友幹貨之二)3

簡易計算器電路闆正面

初學者怎樣給單片機寫程序(送給初學單片機朋友幹貨之二)4

簡易計算器焊接反面

下面主要說說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 ----背光源負極(輸入輸出口)

初學者怎樣給單片機寫程序(送給初學單片機朋友幹貨之二)5

LCD1602正面

初學者怎樣給單片機寫程序(送給初學單片機朋友幹貨之二)6

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 文件的編程,另外一個方面學會多個函數之間的靈活調用。可以把這個程序看成是一個簡單的小項目進行練習。

第五部分是調試

調試分為硬件調試和軟件調試兩部分,調試時先調試硬件電路,然後再調試軟件。硬件比較簡單容易調試,主要是軟件的調試要費些時間,隻要有信心,就能在調試過程中鍛煉自己的發現問題、分析問題、解決問題的能力。這個程序有點長,希望喜歡單片機的小夥伴們耐下心,慢慢理解,有問題可以留言、讨論!

初學者怎樣給單片機寫程序(送給初學單片機朋友幹貨之二)7

調試完成的界面3795X593的結果

初學者怎樣給單片機寫程序(送給初學單片機朋友幹貨之二)8

調試好的界面 3795x593

希望這個小項目能對喜歡單片機的小夥伴們有一定的幫助!喜歡的話請關注、留言、别忘了點個贊哦!你的支持是我前進的動力!

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关生活资讯推荐

热门生活资讯推荐

网友关注

Copyright 2023-2024 - www.tftnews.com All Rights Reserved