計(jì)算器項(xiàng)目實(shí)戰(zhàn):從矩陣鍵盤(pán)到數(shù)碼管顯示的嵌入式入門(mén)指南)
1. 項(xiàng)目緣起從“玩具”到“敲門(mén)磚”的51單片機(jī)計(jì)算器很多剛接觸51單片機(jī)的朋友可能都做過(guò)流水燈、按鍵掃描、數(shù)碼管顯示這些基礎(chǔ)實(shí)驗(yàn)。做多了難免會(huì)想能不能把這些零散的知識(shí)點(diǎn)串起來(lái)做一個(gè)稍微有點(diǎn)“意思”的小項(xiàng)目基于51單片機(jī)的簡(jiǎn)易計(jì)算器就是這樣一個(gè)絕佳的練手選擇。它聽(tīng)起來(lái)不復(fù)雜一個(gè)能顯示、能按鍵、能做加減乘除的“玩具”而已。但真動(dòng)手做起來(lái)你會(huì)發(fā)現(xiàn)它幾乎涵蓋了單片機(jī)入門(mén)階段所有核心技能點(diǎn)I/O口操作、中斷或掃描、數(shù)碼管動(dòng)態(tài)顯示、矩陣鍵盤(pán)識(shí)別、算術(shù)運(yùn)算邏輯以及最重要的——如何將軟件邏輯與硬件行為嚴(yán)絲合縫地對(duì)應(yīng)起來(lái)。我當(dāng)年學(xué)單片機(jī)時(shí)這個(gè)項(xiàng)目是我的第一個(gè)綜合性實(shí)驗(yàn)。做完之后對(duì)“程序是如何驅(qū)動(dòng)硬件工作的”有了豁然開(kāi)朗的理解。它不像一些單純的仿真實(shí)驗(yàn)?zāi)菢犹摶靡膊幌褚恍?fù)雜的系統(tǒng)那樣讓人望而生畏。今天我就以“普中”開(kāi)發(fā)板或其兼容板為硬件平臺(tái)帶大家從零開(kāi)始走通基于51單片機(jī)的簡(jiǎn)易計(jì)算器全流程包括Proteus仿真驗(yàn)證、程序編寫(xiě)、實(shí)物焊接調(diào)試并會(huì)附上詳細(xì)的講解。你會(huì)發(fā)現(xiàn)只要理清思路這個(gè)“小玩具”能成為你踏入嵌入式世界最扎實(shí)的一塊“敲門(mén)磚”。2. 核心需求與方案設(shè)計(jì)麻雀雖小五臟俱全在做任何項(xiàng)目之前明確需求是第一步。我們這個(gè)簡(jiǎn)易計(jì)算器的需求很清晰通過(guò)按鍵輸入數(shù)字和運(yùn)算符單片機(jī)進(jìn)行運(yùn)算并將輸入的數(shù)字、運(yùn)算符和最終結(jié)果通過(guò)數(shù)碼管顯示出來(lái)。基于這個(gè)核心需求我們需要拆解出具體的功能模塊和實(shí)現(xiàn)方案。2.1 功能模塊拆解一個(gè)完整的計(jì)算器系統(tǒng)無(wú)論簡(jiǎn)單還是復(fù)雜都離不開(kāi)輸入、處理和輸出三個(gè)部分。輸入模塊鍵盤(pán)我們需要輸入0-9的數(shù)字、加()、減(-)、乘(*)、除(/)、等于()、清除(C)等按鍵。如果使用獨(dú)立按鍵需要大量I/O口不經(jīng)濟(jì)。因此矩陣鍵盤(pán)是最優(yōu)選擇。一個(gè)4x4的矩陣鍵盤(pán)16個(gè)鍵完全能滿(mǎn)足需求且只占用8個(gè)I/O口4行4列。處理核心51單片機(jī)這里我們選擇最經(jīng)典的AT89C51或STC89C52。它的任務(wù)包括掃描矩陣鍵盤(pán)識(shí)別哪個(gè)鍵被按下。管理計(jì)算邏輯接收數(shù)字鍵組成操作數(shù)接收運(yùn)算符鍵存儲(chǔ)并準(zhǔn)備進(jìn)行運(yùn)算接收等號(hào)鍵執(zhí)行運(yùn)算接收清除鍵重置所有狀態(tài)。驅(qū)動(dòng)輸出模塊將需要顯示的內(nèi)容送到數(shù)碼管。輸出模塊數(shù)碼管我們需要顯示輸入的數(shù)字、運(yùn)算符和結(jié)果。考慮到計(jì)算器通常需要顯示多位數(shù)多位數(shù)碼管動(dòng)態(tài)顯示是標(biāo)準(zhǔn)方案。比如使用4位或6位一體的數(shù)碼管。動(dòng)態(tài)顯示的原理是利用人眼的視覺(jué)暫留快速輪流點(diǎn)亮每一位數(shù)碼管只要掃描頻率足夠高通常50Hz看起來(lái)就是所有位同時(shí)亮起的。2.2 系統(tǒng)方案框圖與引腳規(guī)劃在動(dòng)手寫(xiě)代碼和畫(huà)圖之前先在紙上畫(huà)一個(gè)簡(jiǎn)單的系統(tǒng)框圖并規(guī)劃好單片機(jī)引腳能極大避免后續(xù)的混亂。方案框圖[4x4矩陣鍵盤(pán)] --- [P3口] --- [51單片機(jī)] --- [P0口位選控制] --- [4位數(shù)碼管] 輸入 處理 輸出引腳規(guī)劃建議以STC89C52為例鍵盤(pán)接口使用P3口。例如P3.0-P3.3接矩陣鍵盤(pán)的4行Row0-Row3P3.4-P3.7接矩陣鍵盤(pán)的4列Col0-Col3。這樣我們通過(guò)掃描行線和檢測(cè)列線電平來(lái)識(shí)別按鍵。數(shù)碼管接口使用P0口和P2口的部分引腳。段選控制顯示什么數(shù)字P0口接數(shù)碼管的段選信號(hào)a, b, c, d, e, f, g, dp。注意51單片機(jī)的P0口內(nèi)部無(wú)上拉電阻驅(qū)動(dòng)數(shù)碼管時(shí)必須外接上拉電阻通常用1kΩ的排阻否則無(wú)法輸出高電平。位選控制哪一位數(shù)碼管亮使用P2口的低4位P2.0-P2.3分別控制4位數(shù)碼管的公共端共陰或共陽(yáng)。如果是共陰數(shù)碼管位選引腳輸出低電平選中該位共陽(yáng)則輸出高電平選中。注意引腳規(guī)劃不是唯一的你可以根據(jù)手頭開(kāi)發(fā)板的布局或個(gè)人習(xí)慣調(diào)整。但一旦確定在原理圖、程序和實(shí)物連接中就必須保持一致。3. Proteus仿真搭建在電腦上先“跑通”在焊接實(shí)物之前先用Proteus進(jìn)行仿真是最高效、最安全的驗(yàn)證方式。它能幫你提前發(fā)現(xiàn)電路設(shè)計(jì)或程序邏輯上的致命錯(cuò)誤。3.1 元件清單與查找打開(kāi)Proteus ISIS我們需要從元件庫(kù)中查找并放置以下關(guān)鍵元件單片機(jī)搜索 “AT89C51” 或 “AT89C52”。晶體振蕩器搜索 “CRYSTAL”。放置一個(gè)12MHz的單片機(jī)旁邊。電容搜索 “CAP”。需要兩個(gè)30pF的瓷片電容接在晶振兩端到地一個(gè)10uF的電解電容用于復(fù)位電路。電阻搜索 “RES”。需要一個(gè)10kΩ的電阻復(fù)位電路上拉。八個(gè)1kΩ的電阻作為P0口的上拉排阻可以搜索“RESPACK-8”。八個(gè)220Ω的電阻作為數(shù)碼管段選的限流電阻。按鍵搜索 “BUTTON”。用于復(fù)位電路和矩陣鍵盤(pán)需要16個(gè)。數(shù)碼管搜索 “7SEG”。根據(jù)規(guī)劃選擇例如 “7SEG-MPX4-CC” 表示4位共陰極數(shù)碼管“7SEG-MPX4-CA” 表示4位共陽(yáng)極數(shù)碼管。這里我們以共陰為例。矩陣鍵盤(pán)搜索 “KEYPAD”。選擇 “KEYPAD-PHONE” 或 “KEYPAD-SMALLCALC” 都可以本質(zhì)都是4x4矩陣。3.2 繪制仿真原理圖按照之前規(guī)劃的引腳連接所有元件。這里給出一個(gè)具體的連接示例單片機(jī)最小系統(tǒng)XTAL1,XTAL2接12MHz晶振晶振兩端各接一個(gè)30pF電容到地GND。RESET接一個(gè)10uF電解電容的正極電容負(fù)極接地。同時(shí)RESET引腳通過(guò)一個(gè)10kΩ電阻接VCC電源。這就是經(jīng)典的上電復(fù)位電路。EA/VPP接VCC表示使用片內(nèi)程序存儲(chǔ)器。VCC接5V電源。GND接地。矩陣鍵盤(pán)行線Row0-Row3接P3.0-P3.3。列線Col0-Col3接P3.4-P3.7。數(shù)碼管段選a-g, dp通過(guò)8個(gè)220Ω的限流電阻接到P0.0-P0.7。位選COM1-COM4如果是共陰數(shù)碼管“7SEG-MPX4-CC”其四個(gè)公共陰極分別接P2.0-P2.3。關(guān)鍵一步P0口P0.0-P0.7必須接一個(gè)1kΩ的8位上拉排阻RESPACK-8到VCC。繪制完成后你的仿真圖應(yīng)該是一個(gè)清晰、完整的系統(tǒng)。雙擊單片機(jī)芯片在“Program File”一欄稍后我們需要載入編譯好的.hex文件。3.3 仿真調(diào)試技巧在Proteus中你可以利用虛擬儀器和調(diào)試功能電壓探針可以放在關(guān)鍵引腳上實(shí)時(shí)查看電平變化。虛擬終端如果你在程序里用了串口打印調(diào)試信息可以接上虛擬終端查看。單步調(diào)試與Keil聯(lián)調(diào)可以設(shè)置斷點(diǎn)一步步執(zhí)行程序觀察寄存器和I/O口的變化這對(duì)于理解動(dòng)態(tài)掃描和鍵盤(pán)掃描時(shí)序非常有幫助。一個(gè)常見(jiàn)的仿真坑數(shù)碼管顯示亂碼或不亮。首先檢查P0口的上拉排阻是否接上其次檢查數(shù)碼管是共陰還是共陽(yáng)位選信號(hào)電平是否給對(duì)共陰給低電平選中最后檢查段碼表是否正確。可以在程序中先寫(xiě)死一個(gè)數(shù)字如讓所有位顯示“8.”來(lái)測(cè)試數(shù)碼管驅(qū)動(dòng)電路是否正常。4. 程序設(shè)計(jì)與代碼精講程序是項(xiàng)目的靈魂。我們將程序模塊化分為鍵盤(pán)掃描、數(shù)碼管顯示、計(jì)算邏輯三大塊。這里使用C語(yǔ)言在Keil中開(kāi)發(fā)。4.1 頭文件、引腳定義與全局變量#include reg52.h // 包含51單片機(jī)寄存器定義的頭文件 #include intrins.h // 包含_nop_()空操作指令 // 類(lèi)型重定義增強(qiáng)可讀性 typedef unsigned char u8; typedef unsigned int u16; // 數(shù)碼管位選控制引腳定義 (假設(shè)接P2口低四位) sbit LSA P2^0; sbit LSB P2^1; sbit LSC P2^2; sbit LSD P2^3; // 全局顯示緩沖區(qū)存放4位數(shù)碼管要顯示的數(shù)字的段碼 u8 DisplayBuffer[4] {0x3f, 0x3f, 0x3f, 0x3f}; // 初始顯示0000 // 共陰數(shù)碼管段碼表 (0-9, 滅, 負(fù)號(hào), 小數(shù)點(diǎn)等) u8 code SegmentCode[] { 0x3f, // 0 0x06, // 1 0x5b, // 2 0x4f, // 3 0x66, // 4 0x6d, // 5 0x7d, // 6 0x07, // 7 0x7f, // 8 0x6f, // 9 0x00, // 滅 0x40, // 負(fù)號(hào)‘-’ 0x80 // 小數(shù)點(diǎn)‘.’ }; // 計(jì)算器狀態(tài)變量 long Operand1 0; // 第一個(gè)操作數(shù) long Operand2 0; // 第二個(gè)操作數(shù) u8 Operator 0; // 運(yùn)算符0-無(wú)1-加2-減3-乘4-除 u8 InputState 0; // 輸入狀態(tài)0-等待輸入第一個(gè)數(shù)1-正在輸入第一個(gè)數(shù)2-等待輸入第二個(gè)數(shù)3-正在輸入第二個(gè)數(shù) u8 KeyValue 0; // 存儲(chǔ)當(dāng)前按下的鍵值4.2 數(shù)碼管動(dòng)態(tài)顯示驅(qū)動(dòng)這是最基礎(chǔ)也是最重要的函數(shù)之一必須放在定時(shí)器中斷里以保證穩(wěn)定的刷新頻率。// 定時(shí)器0中斷服務(wù)函數(shù)用于數(shù)碼管動(dòng)態(tài)掃描 (假設(shè)1ms中斷一次) void Timer0_ISR() interrupt 1 { static u8 display_index 0; // 當(dāng)前顯示哪一位靜態(tài)變量保持值 // 先關(guān)閉所有位選消隱防止鬼影 P2 P2 | 0x0f; // 假設(shè)位選引腳在P2.0-P2.3置高電平關(guān)閉共陰數(shù)碼管 // 送段碼 P0 DisplayBuffer[display_index]; // 打開(kāi)對(duì)應(yīng)的位選 switch(display_index) { case 0: LSA0; LSB1; LSC1; LSD1; break; case 1: LSA1; LSB0; LSC1; LSD1; break; case 2: LSA1; LSB1; LSC0; LSD1; break; case 3: LSA1; LSB1; LSC1; LSD0; break; } // 指向下一位 display_index; if(display_index 4) display_index 0; // 定時(shí)器重裝初值保證1ms中斷 (12MHz晶振) TH0 0xFC; TL0 0x18; }關(guān)鍵點(diǎn)解釋動(dòng)態(tài)掃描的精髓在于“分時(shí)復(fù)用”。每一時(shí)刻只有一位數(shù)碼管被點(diǎn)亮但由于人眼的視覺(jué)暫留只要掃描夠快這里1ms一位4位就是4ms頻率250Hz看起來(lái)就是同時(shí)亮的。消隱操作先關(guān)閉所有位選至關(guān)重要它能消除切換位選時(shí)產(chǎn)生的“鬼影”。4.3 矩陣鍵盤(pán)掃描程序矩陣鍵盤(pán)掃描采用“行掃描列檢測(cè)”法。// 矩陣鍵盤(pán)掃描函數(shù)返回鍵值1-160表示無(wú)按鍵 u8 KeyScan() { u8 row_value, col_value; u8 key_value 0; // 1. 將所有行線置低電平列線置高電平帶上拉 P3 0x0f; // 低4位行輸出0高4位列帶上拉輸入模式實(shí)際由P3口準(zhǔn)雙向口特性決定 // 2. 檢測(cè)列線是否有低電平 col_value P3 0xf0; // 讀取高4位列線狀態(tài) if(col_value ! 0xf0) // 如果有列線被拉低說(shuō)明有按鍵按下 { DelayMs(10); // 簡(jiǎn)單延時(shí)消抖 col_value P3 0xf0; if(col_value ! 0xf0) // 再次確認(rèn)軟件消抖 { // 3. 逐行掃描確定具體按鍵 row_value 0xfe; // 從第一行開(kāi)始P3.00 while((row_value 0x10) ! 0) // 掃描完4行 { P3 row_value; // 輸出行掃描碼 _nop_(); _nop_(); // 小延時(shí)等待電平穩(wěn)定 col_value P3 0xf0; // 讀取列線狀態(tài) if(col_value ! 0xf0) // 該行有按鍵按下 { // 根據(jù)行掃描碼和列檢測(cè)碼查表或計(jì)算得到鍵值 // 這里是一個(gè)簡(jiǎn)單的鍵值映射示例 switch(row_value) { case 0xfe: key_value 0; break; // Row0 case 0xfd: key_value 4; break; // Row1 case 0xfb: key_value 8; break; // Row2 case 0xf7: key_value 12; break;// Row3 } switch(col_value) { case 0xe0: key_value 1; break; // Col0 case 0xd0: key_value 2; break; // Col1 case 0xb0: key_value 3; break; // Col2 case 0x70: key_value 4; break; // Col3 } // 等待按鍵釋放 while((P3 0xf0) ! 0xf0); DelayMs(10); // 釋放消抖 return key_value; // 返回1-16的鍵值 } row_value (row_value 1) | 0x01; // 掃描下一行 } } } return 0; // 無(wú)按鍵按下 } // 毫秒級(jí)延時(shí)函數(shù)粗略延時(shí)用于消抖 void DelayMs(u16 ms) { u16 i, j; for(ims; i0; i--) for(j110; j0; j--); }避坑指南鍵盤(pán)掃描中最常見(jiàn)的問(wèn)題是“連擊”和“反應(yīng)遲鈍”。while((P3 0xf0) ! 0xf0);這行代碼實(shí)現(xiàn)了“等待按鍵釋放”這是防止連擊的關(guān)鍵。消抖延時(shí)DelayMs(10)必不可少但時(shí)間不宜過(guò)長(zhǎng)否則會(huì)影響掃描頻率和數(shù)碼管顯示。更高級(jí)的做法是使用定時(shí)器進(jìn)行狀態(tài)機(jī)掃描可以完全消除延時(shí)帶來(lái)的負(fù)面影響。4.4 計(jì)算器主邏輯與顯示更新這是整個(gè)程序的大腦負(fù)責(zé)協(xié)調(diào)鍵盤(pán)輸入、運(yùn)算和顯示更新。void main() { long result 0; u8 i, temp_key; u8 num_buffer[4]; // 用于臨時(shí)存放輸入的數(shù)字字符 // 初始化定時(shí)器0用于數(shù)碼管掃描 TMOD 0x01; // 定時(shí)器0模式116位定時(shí)器 TH0 0xFC; TL0 0x18; // 初值約1ms中斷 ET0 1; // 開(kāi)啟定時(shí)器0中斷 EA 1; // 開(kāi)啟總中斷 TR0 1; // 啟動(dòng)定時(shí)器0 // 初始化顯示為全0 for(i0; i4; i) DisplayBuffer[i] SegmentCode[0]; while(1) { temp_key KeyScan(); // 獲取鍵值 if(temp_key ! 0) { KeyValue temp_key; // 根據(jù)鍵值進(jìn)行邏輯處理 if(KeyValue 1 KeyValue 10) // 數(shù)字鍵1-90假設(shè)鍵值1-10對(duì)應(yīng)數(shù)字1,2,3,4,5,6,7,8,9,0 { u8 input_num; if(KeyValue 10) input_num 0; else input_num KeyValue; if(InputState 0 || InputState 1) // 輸入第一個(gè)數(shù) { InputState 1; Operand1 Operand1 * 10 input_num; // 更新顯示緩沖區(qū)顯示第一個(gè)數(shù)這里簡(jiǎn)化只顯示后4位 DisplayNumber(Operand1 % 10000); // 自定義函數(shù)將數(shù)字分解到顯示緩沖區(qū) } else if(InputState 2 || InputState 3) // 輸入第二個(gè)數(shù) { InputState 3; Operand2 Operand2 * 10 input_num; DisplayNumber(Operand2 % 10000); } } else if(KeyValue 11) // 加號(hào)‘’ { if(InputState 1) // 已輸入第一個(gè)數(shù) { Operator 1; InputState 2; // 等待輸入第二個(gè)數(shù) // 可以閃爍顯示運(yùn)算符這里簡(jiǎn)化清空顯示或保持不變 ClearDisplayBuffer(); DisplayBuffer[0] SegmentCode[10]; // 例如第一位顯示‘’的段碼需自定義 } } else if(KeyValue 12) // 減號(hào)‘-’ { // 類(lèi)似加號(hào)處理 Operator 2; InputState 2; // ... 更新顯示 } // ... 類(lèi)似處理乘(*)、除(/) else if(KeyValue 15) // 等號(hào)‘’ { if(InputState 3 Operator ! 0) // 已輸入第二個(gè)數(shù)和運(yùn)算符 { // 執(zhí)行運(yùn)算 switch(Operator) { case 1: result Operand1 Operand2; break; case 2: result Operand1 - Operand2; break; case 3: result Operand1 * Operand2; break; case 4: if(Operand2 ! 0) result Operand1 / Operand2; else result 0; // 簡(jiǎn)單除零處理 break; } // 顯示結(jié)果 DisplayNumber(result % 10000); // 顯示結(jié)果的后4位 // 重置狀態(tài)為下一次計(jì)算準(zhǔn)備或者不清空允許連續(xù)計(jì)算 // Operand1 result; // 連續(xù)計(jì)算 // Operand2 0; // InputState 1; // 或者完全重置 Operand1 0; Operand2 0; Operator 0; InputState 0; } } else if(KeyValue 16) // 清除‘C’ { // 全部重置 Operand1 0; Operand2 0; Operator 0; InputState 0; DisplayNumber(0); } } } } // 將一個(gè)數(shù)字0-9999分解到4位顯示緩沖區(qū)的函數(shù) void DisplayNumber(int num) { u8 i; if(num 0) // 處理負(fù)數(shù)簡(jiǎn)單示例 { DisplayBuffer[0] SegmentCode[11]; // 顯示負(fù)號(hào) num -num; } else { DisplayBuffer[0] SegmentCode[num / 1000]; // 千位 } DisplayBuffer[1] SegmentCode[(num % 1000) / 100]; // 百位 DisplayBuffer[2] SegmentCode[(num % 100) / 10]; // 十位 DisplayBuffer[3] SegmentCode[num % 10]; // 個(gè)位 } // 清空顯示緩沖區(qū) void ClearDisplayBuffer() { for(u8 i0; i4; i) DisplayBuffer[i] SegmentCode[10]; // 顯示為空 }邏輯核心解析主邏輯是一個(gè)狀態(tài)機(jī)。InputState變量記錄了當(dāng)前處于“輸入第一個(gè)數(shù)”、“等待運(yùn)算符”、“輸入第二個(gè)數(shù)”等哪個(gè)階段。這是處理連續(xù)輸入如輸入“1234”而不混亂的關(guān)鍵。DisplayNumber函數(shù)負(fù)責(zé)將整數(shù)分解為單個(gè)數(shù)字并查表轉(zhuǎn)換為段碼存入顯示緩沖區(qū)由定時(shí)器中斷函數(shù)自動(dòng)刷新顯示。5. 實(shí)物制作與調(diào)試從仿真到現(xiàn)實(shí)的挑戰(zhàn)仿真成功只完成了70%的工作剩下的30%——實(shí)物調(diào)試往往才是問(wèn)題最多、收獲最大的部分。5.1 元器件選型與焊接單片機(jī)STC89C52RC價(jià)格便宜資源足夠燒錄方便用USB-TTL串口模塊。數(shù)碼管推薦使用四位一體共陰數(shù)碼管。購(gòu)買(mǎi)時(shí)一定要用萬(wàn)用表二極管檔測(cè)試一下引腳排列和共陰/共陽(yáng)屬性不同廠家的封裝可能不同。矩陣鍵盤(pán)可以購(gòu)買(mǎi)現(xiàn)成的4x4薄膜鍵盤(pán)模塊也可以自己用16個(gè)輕觸按鍵焊接。模塊省事自己焊接更能理解原理。限流電阻段選限流電阻用220Ω-1kΩ均可電阻越大亮度越低但更省電。建議330Ω亮度適中。上拉排阻給P0口用的1kΩ 9引腳排阻8位上拉1腳公共端這是必須的。焊接建議使用洞洞板萬(wàn)能板進(jìn)行焊接。先焊接電源和最小系統(tǒng)晶振、復(fù)位電路確保單片機(jī)可以正常工作可以燒錄一個(gè)簡(jiǎn)單的流水燈程序測(cè)試。再焊接數(shù)碼管及其驅(qū)動(dòng)電路最后焊接矩陣鍵盤(pán)。務(wù)必注意走線清晰避免虛焊和短路。5.2 程序燒錄與上電測(cè)試編譯程序在Keil中編譯上述代碼生成.hex文件。連接燒錄器將USB-TTL模塊的TXD接單片機(jī)RXD(P3.0)RXD接單片機(jī)TXD(P3.1)GND接GND。注意燒錄時(shí)單片機(jī)的VCC由開(kāi)發(fā)板或外部電源供電USB-TTL模塊的VCC一般不接只共地即可以免電源沖突。冷啟動(dòng)燒錄使用STC-ISP等燒錄軟件選擇正確的單片機(jī)型號(hào)和串口號(hào)打開(kāi).hex文件。點(diǎn)擊“下載/編程”按鈕然后給單片機(jī)關(guān)電再上電冷啟動(dòng)軟件會(huì)自動(dòng)開(kāi)始燒錄。上電測(cè)試燒錄成功后系統(tǒng)應(yīng)自動(dòng)運(yùn)行。按下數(shù)字鍵觀察數(shù)碼管顯示是否正確。按下運(yùn)算符和等號(hào)觀察運(yùn)算邏輯。5.3 常見(jiàn)實(shí)物問(wèn)題與排查數(shù)碼管部分位不亮或全不亮檢查位選信號(hào)用萬(wàn)用表電壓檔測(cè)量位選引腳P2.0-P2.3在動(dòng)態(tài)掃描時(shí)是否有電平跳變。如果沒(méi)有檢查程序位選控制部分。檢查段選信號(hào)測(cè)量P0口在掃描時(shí)輸出段碼是否正常。務(wù)必確認(rèn)P0口接了上拉排阻檢查共陰/共陽(yáng)確認(rèn)數(shù)碼管類(lèi)型和程序里的驅(qū)動(dòng)邏輯是否匹配。共陰數(shù)碼管位選給低電平0選中段選給高電平1點(diǎn)亮段共陽(yáng)則相反。檢查限流電阻確認(rèn)每個(gè)段選線上都串聯(lián)了限流電阻。按鍵無(wú)反應(yīng)或反應(yīng)混亂檢查接線確認(rèn)矩陣鍵盤(pán)的行列線是否與程序定義P3.0-P3.7一一對(duì)應(yīng)。檢查上拉51單片機(jī)的P3口作為輸入時(shí)雖然是準(zhǔn)雙向口但內(nèi)部上拉較弱。如果按鍵按下時(shí)不能可靠地將列線拉低可以在列線P3.4-P3.7上外接10kΩ上拉電阻到VCC。消抖問(wèn)題如果按鍵有時(shí)靈有時(shí)不靈可能是消抖時(shí)間不合適。可以調(diào)整DelayMs(10)中的延時(shí)時(shí)間或者改用更優(yōu)秀的狀態(tài)機(jī)消抖算法。鍵值映射錯(cuò)誤用DisplayNumber函數(shù)將讀取到的KeyValue直接顯示出來(lái)按下每個(gè)鍵看顯示的數(shù)字是否與你定義的鍵值表一致。不一致就需要調(diào)整KeyScan函數(shù)中的鍵值映射部分。計(jì)算結(jié)果顯示錯(cuò)誤數(shù)據(jù)類(lèi)型溢出int類(lèi)型在51單片機(jī)默認(rèn)16位上范圍是-32768~32767。如果做乘法如200*20040000就會(huì)溢出。將Operand1,Operand2,result等變量定義為long型32位。顯示函數(shù)問(wèn)題DisplayNumber函數(shù)可能只處理了正數(shù)。對(duì)于減法可能出現(xiàn)的負(fù)數(shù)需要額外處理符號(hào)顯示。狀態(tài)機(jī)邏輯錯(cuò)誤通過(guò)串口打印InputState、Operand1、Operator、Operand2等變量的值跟蹤程序每一步的狀態(tài)變化這是最有效的調(diào)試方法。6. 功能擴(kuò)展與優(yōu)化思路完成基礎(chǔ)版本后你可以嘗試以下擴(kuò)展讓這個(gè)計(jì)算器更“像樣”增加運(yùn)算功能支持連續(xù)運(yùn)算如 123、括號(hào)需要棧支持難度較大、平方、開(kāi)方等。改善顯示支持負(fù)數(shù)顯示在DisplayNumber函數(shù)中增加對(duì)負(fù)數(shù)的判斷并占用一位數(shù)碼管顯示負(fù)號(hào)‘-’。支持小數(shù)點(diǎn)定義一個(gè)小數(shù)點(diǎn)標(biāo)志位和計(jì)數(shù)器當(dāng)按下小數(shù)點(diǎn)鍵時(shí)后續(xù)輸入的數(shù)字作為小數(shù)部分處理。顯示時(shí)也需要處理小數(shù)點(diǎn)的段碼段碼表已包含0x80。滾動(dòng)顯示對(duì)于超過(guò)4位的數(shù)字可以設(shè)計(jì)滾動(dòng)顯示算法。優(yōu)化輸入體驗(yàn)按鍵音提示通過(guò)一個(gè)蜂鳴器在按鍵按下時(shí)發(fā)出短促的“滴”聲。按鍵連按功能修改鍵盤(pán)掃描邏輯實(shí)現(xiàn)長(zhǎng)按加速輸入等。低功耗設(shè)計(jì)在無(wú)操作一段時(shí)間后關(guān)閉數(shù)碼管顯示單片機(jī)進(jìn)入空閑模式或掉電模式通過(guò)按鍵中斷喚醒。這對(duì)于電池供電的應(yīng)用很有意義。改用LCD顯示用1602或12864液晶屏替代數(shù)碼管可以顯示更多信息如完整的表達(dá)式“12 34 46”用戶(hù)體驗(yàn)直接提升一個(gè)檔次。這需要學(xué)習(xí)LCD的驅(qū)動(dòng)協(xié)議如并口、I2C。從仿真到實(shí)物從點(diǎn)亮第一個(gè)數(shù)碼管到完成四則運(yùn)算這個(gè)過(guò)程你會(huì)遇到無(wú)數(shù)個(gè)小問(wèn)題。每一個(gè)問(wèn)題的解決都是對(duì)“單片機(jī)系統(tǒng)如何工作”的一次深刻理解。這個(gè)簡(jiǎn)易計(jì)算器項(xiàng)目就像一把鑰匙幫你打開(kāi)了嵌入式開(kāi)發(fā)的大門(mén)。當(dāng)你看到自己焊接的板子上的數(shù)字隨著按鍵跳動(dòng)并正確計(jì)算出結(jié)果時(shí)那種成就感是單純的仿真無(wú)法比擬的。希望這份詳細(xì)的指南能幫你少走彎路順利通關(guān)。