秒表計(jì)時(shí)器設(shè)計(jì):從定時(shí)器中斷到數(shù)碼管動(dòng)態(tài)掃描的嵌入式入門實(shí)踐)
簡介本資源是一套面向電子類本科畢業(yè)設(shè)計(jì)與課程課題實(shí)踐的單片機(jī)綜合應(yīng)用項(xiàng)目聚焦于嵌入式時(shí)間測量系統(tǒng)開發(fā)解決學(xué)生在硬件設(shè)計(jì)、程序調(diào)試與仿真驗(yàn)證環(huán)節(jié)缺乏完整參考方案的問題。壓縮包共含源程序工程、Proteus仿真工程、電路原理圖及配套技術(shù)手冊四類核心文件涵蓋從代碼邏輯、虛擬調(diào)試到硬件實(shí)現(xiàn)的全鏈路資料總大小9.82MB。已有88人學(xué)習(xí)下載適用于51/52單片機(jī)初學(xué)者及課程設(shè)計(jì)者尤其適合需快速掌握數(shù)碼管動(dòng)態(tài)掃描、按鍵狀態(tài)機(jī)、EEPROM數(shù)據(jù)存儲(chǔ)、蜂鳴器驅(qū)動(dòng)與倒計(jì)時(shí)中斷處理等關(guān)鍵技術(shù)點(diǎn)的學(xué)習(xí)場景。讀者可直接導(dǎo)入Keil與Proteus運(yùn)行調(diào)試復(fù)現(xiàn)分鐘秒雙顯、正/倒計(jì)時(shí)切換、10組時(shí)刻點(diǎn)存儲(chǔ)與回溯等全部功能技術(shù)手冊還對各模塊接口定義、寄存器配置與常見仿真異常給出詳細(xì)說明顯著降低入門門檻與試錯(cuò)成本。1. 項(xiàng)目概述與核心價(jià)值最近在整理以前的項(xiàng)目資料翻出來一個(gè)大學(xué)時(shí)期做的經(jīng)典小玩意兒——基于51單片機(jī)的秒表計(jì)時(shí)器。別看它現(xiàn)在看起來簡單當(dāng)年為了調(diào)通那個(gè)四位數(shù)碼管顯示和蜂鳴器報(bào)警可沒少在實(shí)驗(yàn)室熬夜。這個(gè)項(xiàng)目標(biāo)題“基于單片機(jī)的秒表計(jì)時(shí)器設(shè)計(jì)(程序仿真電路)(51SEG4BZKEY5) #0400”幾乎把核心要素都點(diǎn)明了主控是經(jīng)典的51內(nèi)核單片機(jī)顯示用了4位數(shù)碼管SEG4配了蜂鳴器BZ做提示音還有5個(gè)獨(dú)立按鍵KEY5進(jìn)行控制。后綴的“#0400”像是個(gè)項(xiàng)目編號無關(guān)緊要。這其實(shí)就是個(gè)典型的嵌入式入門級綜合應(yīng)用麻雀雖小五臟俱全涵蓋了I/O控制、定時(shí)器中斷、數(shù)碼管動(dòng)態(tài)掃描、按鍵消抖、蜂鳴器驅(qū)動(dòng)等嵌入式開發(fā)最基礎(chǔ)也最重要的技能點(diǎn)。對于剛接觸單片機(jī)的新手或者電子相關(guān)專業(yè)的學(xué)生來說自己動(dòng)手從零搭建這樣一個(gè)秒表價(jià)值遠(yuǎn)超單純看教程。它能讓你把書本上離散的知識點(diǎn)比如“定時(shí)器怎么配置”、“中斷服務(wù)程序怎么寫”、“數(shù)碼管段碼表是啥”串聯(lián)成一個(gè)完整的、可以實(shí)際運(yùn)行的系統(tǒng)。你不僅能學(xué)會(huì)寫代碼更能理解硬件電路如何與軟件配合比如為什么按鍵要接上拉電阻為什么驅(qū)動(dòng)數(shù)碼管需要三極管或?qū)S眯酒瑏碓鰪?qiáng)電流。市面上很多開發(fā)板配套的實(shí)驗(yàn)雖然方便但往往把硬件底層封裝好了你只需要調(diào)用庫函數(shù)容易導(dǎo)致“知其然不知其所以然”。而這個(gè)自搭電路、自寫代碼的項(xiàng)目能幫你打下更堅(jiān)實(shí)的硬件基礎(chǔ)。這個(gè)項(xiàng)目的目標(biāo)很明確實(shí)現(xiàn)一個(gè)精度為0.01秒即10毫秒的4位數(shù)碼管顯示秒表具備啟動(dòng)、暫停、繼續(xù)、復(fù)位/清零以及計(jì)次Lap功能并通過蜂鳴器在關(guān)鍵操作如啟動(dòng)、暫停、計(jì)次時(shí)提供聲音反饋。最終我們不僅要有能在實(shí)物上跑通的程序.c和.h文件還要有用Proteus等軟件搭建的仿真電路確保邏輯正確后再動(dòng)手焊接避免浪費(fèi)元器件。下面我就結(jié)合當(dāng)年的筆記和后來積累的經(jīng)驗(yàn)把這個(gè)項(xiàng)目的里里外外、需要注意的坑都詳細(xì)拆解一遍。2. 核心硬件方案設(shè)計(jì)與器件選型2.1 主控芯片為什么依然是STC89C52RC標(biāo)題里明確寫了“51”這指的就是采用Intel 8051內(nèi)核架構(gòu)的單片機(jī)。雖然現(xiàn)在STM32等32位ARM Cortex-M芯片大行其道但對于秒表這種對實(shí)時(shí)性和計(jì)算復(fù)雜度要求不高的應(yīng)用經(jīng)典的51單片機(jī)依然是絕佳的入門選擇。它結(jié)構(gòu)簡單寄存器數(shù)量少易于理解市面上資料浩如煙海。我選擇的是國內(nèi)最常用的STC89C52RC它是宏晶科技生產(chǎn)的增強(qiáng)型51單片機(jī)完全兼容標(biāo)準(zhǔn)8051指令集但增加了片內(nèi)RAM256字節(jié)和Flash8KB還有看門狗和額外的定時(shí)器最關(guān)鍵的是支持ISP在系統(tǒng)編程直接用USB轉(zhuǎn)TTL串口線就能下載程序省去了昂貴的專用編程器。這里有個(gè)關(guān)鍵點(diǎn)定時(shí)器資源。標(biāo)準(zhǔn)51有Timer0和Timer1兩個(gè)16位定時(shí)器。我們的秒表需要10ms0.01秒的基礎(chǔ)計(jì)時(shí)單位。假設(shè)使用12MHz晶振傳統(tǒng)51單片機(jī)最常用的頻率機(jī)器周期為1μs。定時(shí)器每計(jì)數(shù)一次是1μs要產(chǎn)生10ms中斷需要計(jì)數(shù)10000次。而16位定時(shí)器的最大計(jì)數(shù)值是65536所以我們可以將定時(shí)器設(shè)置為模式116位定時(shí)模式初值設(shè)置為65536-10000 55536即0xD8F0。這樣每次定時(shí)器溢出就會(huì)進(jìn)入中斷服務(wù)程序我們在中斷里對一個(gè)軟件計(jì)數(shù)器進(jìn)行累加當(dāng)累加到100次時(shí)就表示過去了1秒然后更新秒、十分之一秒、百分之一秒的顯示數(shù)據(jù)。這個(gè)計(jì)算過程是理解定時(shí)器應(yīng)用的核心。注意很多新手會(huì)忽略晶振頻率的影響。如果你的板子用的是11.0592MHz的晶振常用于串口通信因?yàn)榭梢援a(chǎn)生精確的波特率那么機(jī)器周期約為1.085μs要產(chǎn)生10ms中斷需要的初值就不是55536了需要重新計(jì)算。公式為初值 65536 - (定時(shí)時(shí)間 / 機(jī)器周期)。務(wù)必根據(jù)實(shí)際硬件調(diào)整。2.2 顯示模塊4位共陽數(shù)碼管與驅(qū)動(dòng)考量“SEG4”代表4位數(shù)碼管。數(shù)碼管有共陽和共陰之分我們項(xiàng)目常用共陽的。所謂共陽就是所有數(shù)碼管的陽極正極連接在一起接VCC每個(gè)數(shù)碼管的8個(gè)段a, b, c, d, e, f, g, dp通過限流電阻連接到單片機(jī)的I/O口。當(dāng)某個(gè)I/O口輸出低電平時(shí)對應(yīng)的段就被點(diǎn)亮。為什么是4位對于秒表我們通常顯示“分:秒.百分秒”例如“01:23.45”。這就需要4位數(shù)字來顯示兩位秒兩位百分秒中間用小數(shù)點(diǎn)分隔。實(shí)際上我們用了4位數(shù)碼管中的兩位來顯示秒可能超過60所以需要兩位兩位顯示百分秒。分鐘的顯示可以通過擴(kuò)展位數(shù)或者用閃爍提示等方式實(shí)現(xiàn)在基礎(chǔ)版本中我們可以先實(shí)現(xiàn)99.99秒以內(nèi)的計(jì)時(shí)。直接驅(qū)動(dòng)問題51單片機(jī)單個(gè)I/O口的拉電流輸出高電平時(shí)的驅(qū)動(dòng)能力和灌電流輸出低電平時(shí)的吸入能力都很有限通常只有幾個(gè)mA。而點(diǎn)亮一個(gè)數(shù)碼管的一段可能需要10-20mA的電流。如果直接用單片機(jī)I/O口驅(qū)動(dòng)不僅亮度不足還可能燒壞I/O口。因此必須增加驅(qū)動(dòng)電路。常見方案有兩種使用三極管如PNP型8550作為位選驅(qū)動(dòng)單片機(jī)的I/O口控制三極管的基極由三極管來承擔(dān)導(dǎo)通數(shù)碼管公共端所需的較大電流。使用專用數(shù)碼管驅(qū)動(dòng)芯片如74HC595串行轉(zhuǎn)并行帶鎖存、TM1650I2C接口等。這類芯片可以大大節(jié)省單片機(jī)的I/O口資源并且驅(qū)動(dòng)能力強(qiáng)顯示穩(wěn)定。對于4位數(shù)碼管用兩個(gè)74HC595級聯(lián)就能控制所有段選和位選只需要占用單片機(jī)3個(gè)I/O口數(shù)據(jù)、時(shí)鐘、鎖存。在仿真和簡單實(shí)物中為了直觀理解原理可以先用三極管驅(qū)動(dòng)。但在要求穩(wěn)定、節(jié)省I/O的實(shí)際項(xiàng)目中強(qiáng)烈推薦使用TM1650這類集成驅(qū)動(dòng)芯片軟件上也更簡單。2.3 輸入與輸出5個(gè)獨(dú)立按鍵與蜂鳴器“KEY5”代表5個(gè)獨(dú)立按鍵。我們需要為秒表定義清晰的功能KEY1: 啟動(dòng)/暫停Toggle功能按一下開始再按一下暫停KEY2: 復(fù)位/清零在任何狀態(tài)下按下后時(shí)間歸零KEY3: 計(jì)次/分段計(jì)時(shí) (Lap)在計(jì)時(shí)過程中按下此鍵記錄當(dāng)前時(shí)間并顯示但后臺計(jì)時(shí)不停止。這需要軟件上設(shè)計(jì)一個(gè)緩沖區(qū)來存儲(chǔ)計(jì)次時(shí)間。KEY4: 模式切換可選例如切換顯示“當(dāng)前計(jì)時(shí)”還是“最后一次計(jì)次”的時(shí)間KEY5: 備用或功能擴(kuò)展例如長按復(fù)位等按鍵電路一般采用獨(dú)立式按鍵一端接地另一端通過一個(gè)上拉電阻通常10kΩ接到單片機(jī)的I/O口和VCC。當(dāng)按鍵未按下時(shí)I/O口被上拉電阻拉到高電平按鍵按下時(shí)I/O口直接接地變?yōu)榈碗娖?。單片機(jī)通過檢測I/O口的電平變化來判斷按鍵動(dòng)作。按鍵消抖是必須處理的。機(jī)械按鍵在閉合和斷開的瞬間會(huì)產(chǎn)生持續(xù)的抖動(dòng)約5-10ms單片機(jī)檢測速度極快會(huì)誤判為多次按下。軟件消抖的常用方法是第一次檢測到低電平后延時(shí)10-20ms再次檢測如果仍然是低電平則確認(rèn)為有效按鍵。更優(yōu)雅的方式是使用定時(shí)器中斷周期性地掃描按鍵狀態(tài)通過狀態(tài)機(jī)來識別穩(wěn)定的按下和釋放事件?!癇Z”代表蜂鳴器。這里通常指無源蜂鳴器。無源蜂鳴器內(nèi)部沒有振蕩源需要單片機(jī)產(chǎn)生一定頻率的方波例如2kHz-5kHz驅(qū)動(dòng)才能發(fā)聲。通過改變方波的頻率可以改變音調(diào)通過控制方波輸出時(shí)間可以控制發(fā)聲長短。驅(qū)動(dòng)蜂鳴器也需要一個(gè)三極管如NPN型8050來放大電流因?yàn)閱纹瑱C(jī)I/O口無法直接提供足夠的驅(qū)動(dòng)電流。蜂鳴器用于提供操作反饋比如短“滴”一聲表示啟動(dòng)/暫停長“滴”一聲表示復(fù)位不同的聲音模式可以提升用戶體驗(yàn)。3. 軟件架構(gòu)與核心代碼解析3.1 程序主循環(huán)與狀態(tài)機(jī)設(shè)計(jì)秒表程序的核心是一個(gè)狀態(tài)機(jī)。秒表通常有幾種狀態(tài)RESET已復(fù)位顯示0.00、RUNNING正在計(jì)時(shí)、PAUSED已暫停。按鍵動(dòng)作會(huì)觸發(fā)狀態(tài)遷移。主程序main()函數(shù)通常是一個(gè)無限循環(huán)里面只做兩件事調(diào)用按鍵掃描函數(shù)和處理顯示更新。所有精確的計(jì)時(shí)工作都在定時(shí)器中斷服務(wù)程序中完成這是保證計(jì)時(shí)精度的關(guān)鍵。// 狀態(tài)定義 typedef enum { STATE_RESET, STATE_RUNNING, STATE_PAUSED } TimerState; TimerState g_state STATE_RESET; // 全局狀態(tài)變量 unsigned int g_time_ms 0; // 累計(jì)的毫秒數(shù)或10ms的倍數(shù) unsigned char g_display_buffer[4]; // 顯示緩沖區(qū)存放4位要顯示的數(shù)字的段碼 void main() { Sys_Init(); // 系統(tǒng)初始化配置定時(shí)器、中斷、I/O口 Timer_Init(); // 定時(shí)器初始化設(shè)置為10ms中斷一次 Buzzer_Beep(1); // 開機(jī)提示音 while(1) { Key_Scan(); // 掃描按鍵根據(jù)當(dāng)前g_state和按鍵值更新狀態(tài)和g_time_ms Display_Refresh(); // 根據(jù)g_time_ms和g_state更新g_display_buffer并驅(qū)動(dòng)數(shù)碼管顯示 // 其他邏輯如檢查計(jì)次等 } }3.2 定時(shí)器中斷服務(wù)程序計(jì)時(shí)的心臟這是整個(gè)系統(tǒng)最核心、最需要精準(zhǔn)的地方。我們使用Timer0工作在模式116位定時(shí)模式。void Timer0_Init(void) { TMOD 0xF0; // 清零Timer0的模式位 TMOD | 0x01; // 設(shè)置Timer0為模式1: 16位定時(shí)器 // 假設(shè)晶振12MHz機(jī)器周期1us定時(shí)10ms需要10000個(gè)計(jì)數(shù) TH0 (65536 - 10000) / 256; // 計(jì)算初值高8位 TL0 (65536 - 10000) % 256; // 計(jì)算初值低8位 ET0 1; // 允許Timer0中斷 TR0 1; // 啟動(dòng)Timer0 EA 1; // 開啟總中斷 } void Timer0_ISR(void) interrupt 1 { // 重裝初值保證下一次中斷仍然是10ms后 TH0 (65536 - 10000) / 256; TL0 (65536 - 10000) % 256; static unsigned int cnt_10ms 0; // 10ms計(jì)數(shù)器靜態(tài)變量保持值 if (g_state STATE_RUNNING) { cnt_10ms; if (cnt_10ms 100) { // 100 * 10ms 1000ms 1s cnt_10ms 0; g_time_ms 1000; // 秒數(shù)增加1秒以毫秒為單位存儲(chǔ) // 注意g_time_ms是總毫秒數(shù)我們也可以選擇用單獨(dú)的變量存儲(chǔ)分、秒、毫秒 } // 更常見的做法是g_time_ms直接累加10然后在顯示函數(shù)里將其分解為分、秒、百分秒 // 我們采用另一種清晰的結(jié)構(gòu) // g_time_centisec 累計(jì)“百分之一秒” } }實(shí)際上為了更精確和方便顯示我們通常定義一個(gè)全局變量來存儲(chǔ)總的“百分之一秒”數(shù)也就是0.01秒的單位。unsigned long g_time_centisec 0; // 計(jì)時(shí)器走過的總百分之一秒數(shù) void Timer0_ISR(void) interrupt 1 { // ... 重裝初值 ... if (g_state STATE_RUNNING) { g_time_centisec; // 每10ms中斷一次所以這里加1代表過去了0.01秒 // 限制最大計(jì)時(shí)時(shí)間比如99分59秒99 if(g_time_centisec 600000) { // 100*60*100 600000 (100分鐘) g_time_centisec 600000 - 1; g_state STATE_PAUSED; // 計(jì)時(shí)滿自動(dòng)停止 Buzzer_Beep(3); // 長鳴提示 } } }3.3 按鍵掃描與消抖實(shí)現(xiàn)按鍵掃描函數(shù)Key_Scan()不能放在主循環(huán)里簡單判斷必須用狀態(tài)機(jī)實(shí)現(xiàn)消抖。這里介紹一種常用的“兩次掃描”狀態(tài)機(jī)方法但更推薦使用“定時(shí)掃描狀態(tài)記錄”的方式將按鍵掃描也放在一個(gè)定時(shí)中斷比如5ms一次中這樣主循環(huán)和按鍵響應(yīng)會(huì)更及時(shí)。// 假設(shè)有5個(gè)按鍵連接到P3口的低5位 #define KEY_PORT P3 unsigned char Key_Read(void) { unsigned char key_value 0xFF; key_value KEY_PORT 0x1F; // 只取低5位 return key_value; // 按鍵按下時(shí)對應(yīng)位為0 } // 簡單的延時(shí)消抖掃描在主循環(huán)中調(diào)用非最佳但易于理解 unsigned char Key_Scan_Simple(void) { unsigned char key_down 0; static unsigned char key_last 0x1F; // 上一次的狀態(tài)默認(rèn)全為高未按下 unsigned char key_now; key_now Key_Read(); if (key_now ! key_last) { // 狀態(tài)發(fā)生變化 DelayMs(10); // 延時(shí)10ms消抖 key_now Key_Read(); if (key_now ! key_last) { key_last key_now; if (key_now ! 0x1F) { // 有按鍵被按下低電平 // 找出具體哪個(gè)按鍵被按下返回鍵值 switch (~key_now 0x1F) { case 0x01: key_down 1; break; // KEY1 case 0x02: key_down 2; break; // KEY2 case 0x04: key_down 3; break; // KEY3 case 0x08: key_down 4; break; // KEY4 case 0x10: key_down 5; break; // KEY5 default: break; } } } } else { key_last key_now; } return key_down; // 返回0表示無按鍵1-5表示對應(yīng)按鍵被按下 } void Key_Process(unsigned char key) { switch(key) { case 1: // 啟動(dòng)/暫停 if (g_state STATE_RESET || g_state STATE_PAUSED) { g_state STATE_RUNNING; Buzzer_Beep(1); } else if (g_state STATE_RUNNING) { g_state STATE_PAUSED; Buzzer_Beep(1); } break; case 2: // 復(fù)位 g_state STATE_RESET; g_time_centisec 0; Buzzer_Beep(2); break; case 3: // 計(jì)次(Lap) if (g_state STATE_RUNNING) { // 將當(dāng)前g_time_centisec保存到一個(gè)Lap緩沖區(qū) g_lap_time g_time_centisec; Buzzer_Beep(1); // 可以設(shè)置一個(gè)標(biāo)志讓顯示模塊暫時(shí)顯示計(jì)次時(shí)間 } break; // ... 其他按鍵處理 } }3.4 數(shù)碼管動(dòng)態(tài)顯示驅(qū)動(dòng)4位數(shù)碼管顯示需要?jiǎng)討B(tài)掃描。因?yàn)槿绻瑫r(shí)點(diǎn)亮所有位單片機(jī)需要提供4*832個(gè)驅(qū)動(dòng)電流這是不可能的。動(dòng)態(tài)掃描的原理是在極短的時(shí)間內(nèi)比如每位數(shù)碼管點(diǎn)亮1-5ms依次點(diǎn)亮每一位數(shù)碼管并送出該位對應(yīng)的段碼。由于人眼的視覺暫留效應(yīng)我們會(huì)看到所有數(shù)碼管同時(shí)穩(wěn)定地顯示。這個(gè)掃描過程必須足夠快通常掃描整個(gè)4位的時(shí)間要小于20ms否則會(huì)出現(xiàn)閃爍。// 共陽數(shù)碼管0-9的數(shù)字段碼表a~g, dp假設(shè)連接在P0口 unsigned char code SEG7_CODE[10] { 0xC0, // 0 - 對應(yīng)段 a,b,c,d,e,f 亮g,dp滅 0xF9, // 1 0xA4, // 2 0xB0, // 3 0x99, // 4 0x92, // 5 0x82, // 6 0xF8, // 7 0x80, // 8 0x90 // 9 }; unsigned char g_digit_pos 0; // 當(dāng)前正在掃描的位0-3 unsigned char g_display_num[4]; // 要顯示的4位數(shù)字0-9 void Display_Refresh(void) { // 根據(jù)g_time_centisec計(jì)算要顯示的4位數(shù)字 // 例如g_time_centisec 12345 (代表123.45秒) // 秒數(shù) 12345 / 100 123秒 // 百分秒 12345 % 100 45 // 顯示格式 12秒十位和個(gè)位 34百分秒十位和個(gè)位 // 注意秒數(shù)可能超過60我們按總秒數(shù)顯示。也可以拆分成“分:秒.百分秒” unsigned int total_sec g_time_centisec / 100; unsigned char sec_units total_sec % 10; // 秒的個(gè)位 unsigned char sec_tens (total_sec / 10) % 10; // 秒的十位 // 更合理的拆分顯示“分:秒.百分秒”需要更多位數(shù)4位不夠。 // 因此一個(gè)常見的4位秒表顯示方案是SS.CC (秒.百分秒)最大99.99秒。 // 所以我們假設(shè)g_time_centisec最大9999 g_display_num[0] (g_time_centisec / 1000) % 10; // 秒的十位 g_display_num[1] (g_time_centisec / 100) % 10; // 秒的個(gè)位 g_display_num[2] (g_time_centisec / 10) % 10; // 百分秒的十位 g_display_num[3] g_time_centisec % 10; // 百分秒的個(gè)位 } // 此函數(shù)需要放在一個(gè)定時(shí)中斷如2ms中調(diào)用以實(shí)現(xiàn)穩(wěn)定掃描 void Display_Scan(void) { // 先關(guān)閉所有位選防止鬼影 Close_All_Digits(); // 送出當(dāng)前位的段碼 P0 SEG7_CODE[g_display_num[g_digit_pos]]; // 如果是第1位秒個(gè)位即數(shù)組下標(biāo)1可能需要點(diǎn)亮小數(shù)點(diǎn)dp以顯示“秒.百分秒”的分隔 if (g_digit_pos 1) { P0 0x7F; // 將段碼的最高位dp段清零點(diǎn)亮小數(shù)點(diǎn)。具體取決于你的段碼表定義。 } // 打開當(dāng)前位的位選 switch(g_digit_pos) { case 0: DIGIT0 0; break; // 位選低電平有效共陽數(shù)碼管位選端低電平導(dǎo)通 case 1: DIGIT1 0; break; case 2: DIGIT2 0; break; case 3: DIGIT3 0; break; } // 移動(dòng)到下一位 g_digit_pos; if (g_digit_pos 4) { g_digit_pos 0; } }4. Proteus仿真電路搭建與調(diào)試要點(diǎn)4.1 核心元件清單與電路連接在Proteus中搭建仿真電路是驗(yàn)證代碼邏輯、避免硬件焊接錯(cuò)誤的關(guān)鍵一步。我們需要以下主要元件單片機(jī)AT89C52 (Proteus中常用) 或 STC89C52RC的兼容模型。晶振電路一個(gè)12MHz的晶振CRYSTAL兩個(gè)22-33pF的電容CAP接地。復(fù)位電路一個(gè)10uF的電解電容CAP-ELEC和一個(gè)10kΩ的電阻RES組成上電復(fù)位電路。數(shù)碼管4位共陽數(shù)碼管7SEG-MPX4-CA。注意CA表示共陽。按鍵5個(gè)按鈕BUTTON。蜂鳴器一個(gè)無源蜂鳴器SOUNDER或揚(yáng)聲器SPEAKER。注意Proteus中的SOUNDER需要頻率信號驅(qū)動(dòng)。驅(qū)動(dòng)三極管4個(gè)PNP三極管如2N2907用于數(shù)碼管位選驅(qū)動(dòng)1個(gè)NPN三極管如2N2222用于驅(qū)動(dòng)蜂鳴器。電阻若干限流電阻220Ω-1kΩ用于數(shù)碼管段限流10kΩ用于按鍵上拉。電源5V電源POWER。連接要點(diǎn)單片機(jī)I/O分配規(guī)劃好P0、P1、P2、P3口的使用。例如P0口連接數(shù)碼管的段選a~g, dp每個(gè)引腳串聯(lián)一個(gè)220Ω限流電阻。P2口低4位通過PNP三極管控制4位數(shù)碼管的位選公共端。P3口低5位連接5個(gè)獨(dú)立按鍵按鍵另一端接地P3口內(nèi)部上拉或外部接10kΩ上拉電阻到VCC。P1口某個(gè)引腳如P1.0通過一個(gè)NPN三極管驅(qū)動(dòng)蜂鳴器。驅(qū)動(dòng)電路連接數(shù)碼管的位選公共端接PNP三極管的發(fā)射極集電極接VCC基極通過一個(gè)1kΩ電阻接到單片機(jī)的I/O口。當(dāng)I/O輸出低電平時(shí)三極管導(dǎo)通該位數(shù)碼管公共端得電。蜂鳴器一端接VCC另一端接NPN三極管的集電極發(fā)射極接地基極通過電阻接單片機(jī)I/O口。I/O輸出高電平時(shí)三極管導(dǎo)通蜂鳴器通電發(fā)聲。電源與地確保所有芯片、元件的VCC和GND正確連接。4.2 仿真調(diào)試流程與常見問題繪制原理圖在Proteus ISIS中按上述清單放置元件并連線。務(wù)必仔細(xì)檢查網(wǎng)絡(luò)標(biāo)簽Net Label和總線Bus連接是否正確特別是數(shù)碼管段選線較多容易接錯(cuò)。加載程序雙擊單片機(jī)元件在“Program File”一欄選擇你編譯生成的.hex文件。確保“Clock Frequency”設(shè)置為與你程序匹配的頻率如12MHz。運(yùn)行仿真點(diǎn)擊運(yùn)行按鈕。觀察數(shù)碼管是否顯示初始狀態(tài)如00.00。交互測試點(diǎn)擊仿真圖中的按鍵觀察數(shù)碼管顯示變化是否符合預(yù)期啟動(dòng)、暫停、復(fù)位。聽蜂鳴器在Proteus中可能以波形或日志形式體現(xiàn)是否在對應(yīng)操作時(shí)“發(fā)聲”。仿真中常見問題及排查數(shù)碼管不亮或顯示亂碼檢查段選線a~g, dp連接是否正確限流電阻是否加上。檢查位選控制信號是否正確。在仿真運(yùn)行時(shí)右鍵點(diǎn)擊位選控制線選擇“Place Wire Label”查看其電平變化是否在動(dòng)態(tài)掃描。檢查代碼中的段碼表是否正確特別是共陽/共陰是否匹配。共陽數(shù)碼管是段選低電平點(diǎn)亮所以段碼表里0表示亮1表示滅。檢查動(dòng)態(tài)掃描函數(shù)Display_Scan()是否被定期調(diào)用例如放在一個(gè)定時(shí)中斷里。掃描間隔太長會(huì)導(dǎo)致閃爍太短可能導(dǎo)致亮度不均。按鍵無反應(yīng)檢查按鍵是否一端接地另一端接單片機(jī)I/O口和上拉電阻。在仿真中右鍵點(diǎn)擊按鍵連接的I/O口選擇“Place Probe”放置電壓探針觀察按鍵按下時(shí)電平是否從高變低。檢查代碼中的按鍵掃描函數(shù)Key_Scan()是否被主循環(huán)頻繁調(diào)用消抖邏輯是否正確。確認(rèn)按鍵處理函數(shù)Key_Process()是否正確修改了全局狀態(tài)變量g_state。蜂鳴器不響檢查驅(qū)動(dòng)三極管電路是否正確。NPN三極管基極高電平時(shí)導(dǎo)通。檢查代碼中蜂鳴器驅(qū)動(dòng)引腳是否輸出了頻率方波。對于無源蜂鳴器需要Buzzer_Beep()函數(shù)中控制I/O口以一定頻率如2kHz翻轉(zhuǎn)??梢栽赑roteus中對該引腳添加數(shù)字圖表Digital Graph觀察波形。確認(rèn)蜂鳴器元件在Proteus中是否支持頻率驅(qū)動(dòng)SOUNDER元件可以。計(jì)時(shí)不準(zhǔn)這是最可能的問題。首先確認(rèn)單片機(jī)晶振頻率設(shè)置。在Proteus中雙擊單片機(jī)查看“Clock Frequency”屬性。在代碼中Timer0_Init()函數(shù)里的定時(shí)器初值計(jì)算必須基于此頻率。檢查定時(shí)器中斷服務(wù)程序是否重裝了初值。模式116位模式不會(huì)自動(dòng)重裝必須在中斷中手動(dòng)重裝TH0和TL0。確保中斷服務(wù)程序盡可能短小精悍不要在里面做復(fù)雜的運(yùn)算或調(diào)用耗時(shí)的函數(shù)否則會(huì)影響中斷發(fā)生的精確周期??梢栽谥袛喾?wù)程序里對一個(gè)I/O口進(jìn)行取反操作然后用Proteus的虛擬示波器測量該引腳方波的周期應(yīng)該是20ms因?yàn)?0ms中斷一次取反一次就是20ms。這是驗(yàn)證定時(shí)器中斷是否精確的最直接方法。5. 實(shí)物制作、焊接與調(diào)試實(shí)錄5.1 PCB設(shè)計(jì)與焊接注意事項(xiàng)仿真通過后就可以著手制作實(shí)物了。對于初學(xué)者建議先用洞洞板萬用板進(jìn)行焊接成本低靈活性高。布局規(guī)劃在焊接前先用鉛筆在洞洞板背面非銅箔面大致規(guī)劃一下元件位置。遵循“信號流”原則單片機(jī)在中心晶振和復(fù)位電容緊靠單片機(jī)對應(yīng)引腳數(shù)碼管和按鍵放在板子邊緣方便觀察和操作驅(qū)動(dòng)三極管和限流電阻靠近被驅(qū)動(dòng)元件。電源VCC和地GND走線要粗一些并且盡量形成“網(wǎng)格”或“星型”結(jié)構(gòu)避免共地干擾。焊接順序先矮后高先焊接電阻、電容、晶振、IC座等矮小元件再焊接數(shù)碼管、蜂鳴器、按鍵等較高的元件。先核心后外圍先焊接單片機(jī)最小系統(tǒng)單片機(jī)芯片座、晶振電路、復(fù)位電路確保能通電并下載程序??梢詫懸粋€(gè)最簡單的LED閃爍程序測試最小系統(tǒng)是否工作正常。分段焊接測試不要一次性焊完所有元件。例如焊好數(shù)碼管段選部分連接P0口和限流電阻寫一個(gè)固定數(shù)字顯示程序測試再焊接位選驅(qū)動(dòng)三極管部分測試動(dòng)態(tài)掃描然后焊接按鍵部分測試按鍵功能最后焊接蜂鳴器。每完成一個(gè)模塊就測試一下便于定位故障。焊接要點(diǎn)使用松香芯焊錫溫度控制在350°C左右。焊點(diǎn)要光滑、飽滿呈圓錐形避免虛焊焊錫只包住引腳未與焊盤融合和橋接相鄰焊點(diǎn)被焊錫短路。對于數(shù)碼管這種多引腳元件可以先將其插入IC座或使用排針轉(zhuǎn)接避免直接焊接在板子上導(dǎo)致?lián)p壞或難以更換。給單片機(jī)加上電源濾波電容在VCC和GND之間靠近單片機(jī)電源引腳處并聯(lián)一個(gè)10uF的電解電容濾低頻干擾和一個(gè)0.1uF的瓷片電容濾高頻干擾能極大提高系統(tǒng)穩(wěn)定性。5.2 程序下載與聯(lián)合調(diào)試使用USB轉(zhuǎn)TTL下載線如CH340G、PL2303模塊給STC單片機(jī)下載程序。連接TTL模塊的TXD接單片機(jī)的RXDP3.0RXD接單片機(jī)的TXDP3.1GND共地。STC單片機(jī)冷啟動(dòng)下載先讓單片機(jī)斷電點(diǎn)擊下載軟件中的“下載”按鈕然后再給單片機(jī)上電。軟件配置在STC-ISP下載軟件中選擇正確的單片機(jī)型號如STC89C52RC選擇正確的串口號設(shè)置合適的波特率默認(rèn)即可。打開編譯好的.hex文件。下載按上述冷啟動(dòng)流程操作看到軟件提示“操作成功”即可。上電調(diào)試電源檢查首先用萬用表測量單片機(jī)VCC引腳40腳和GND20腳之間的電壓確保是穩(wěn)定的5V左右?;竟δ軠y試下載一個(gè)最簡單的程序比如讓P1.0口的LED閃爍確認(rèn)單片機(jī)能夠運(yùn)行程序。模塊逐一測試顯示測試寫一個(gè)讓4位數(shù)碼管靜態(tài)顯示“1234”的程序檢查每個(gè)段、每個(gè)位是否都能正常點(diǎn)亮有無缺筆畫或常亮。按鍵測試寫一個(gè)程序按下不同按鍵讓對應(yīng)的LED亮或讓數(shù)碼管顯示不同數(shù)字。蜂鳴器測試寫一個(gè)程序讓蜂鳴器發(fā)出不同頻率的聲音。系統(tǒng)聯(lián)調(diào)將完整的秒表程序下載進(jìn)去。使用手機(jī)上的秒表功能作為基準(zhǔn)對比你的秒表計(jì)時(shí)精度。如果發(fā)現(xiàn)誤差較大回到軟件部分檢查定時(shí)器初值計(jì)算和中斷服務(wù)程序。如果按鍵或顯示異常用萬用表或邏輯分析儀檢查相關(guān)引腳的電平變化。5.3 常見硬件故障排查技巧單片機(jī)不工作不上電檢查電源極性是否接反電壓是否5V。檢查復(fù)位電路上電瞬間復(fù)位引腳9腳應(yīng)有一個(gè)從高到低的跳變。可以用示波器觀察。檢查晶振晶振兩腳電壓大約在1-2V左右且略有不同。用示波器能看到正弦波注意探頭電容可能使晶振停振最好用×10檔。最粗暴的測試方法換一個(gè)同頻率的晶振試試。數(shù)碼管顯示暗淡或亮度不均段限流電阻阻值過大嘗試減小如從1kΩ降到330Ω。位選驅(qū)動(dòng)三極管β值不夠或基極電阻過大導(dǎo)致驅(qū)動(dòng)電流不足??梢試L試減小基極電阻如從10kΩ降到2kΩ或更換β值更大的三極管。動(dòng)態(tài)掃描速度太快或太慢。調(diào)整Display_Scan()函數(shù)的調(diào)用頻率每位點(diǎn)亮?xí)r間1-5ms為宜。按鍵失靈或連擊檢查上拉電阻是否接好。如果使用單片機(jī)內(nèi)部上拉如51的P0口無內(nèi)部上拉P1/P2/P3有外部上拉電阻可以省略但為了穩(wěn)定建議外部加上10kΩ上拉。消抖程序有問題。嘗試增加消抖延時(shí)時(shí)間或者改用狀態(tài)機(jī)方式的按鍵掃描。按鍵本身接觸不良更換按鍵。蜂鳴器聲音小或不響檢查驅(qū)動(dòng)三極管是否飽和導(dǎo)通。測量蜂鳴器兩端電壓在發(fā)聲時(shí)應(yīng)該接近VCC5V。無源蜂鳴器需要一定頻率的方波檢查代碼中輸出的頻率是否在可聽范圍內(nèi)幾百Hz到幾kHz。蜂鳴器正負(fù)極接反有源蜂鳴器有極性無源的一般沒有但最好按標(biāo)識接。系統(tǒng)運(yùn)行不穩(wěn)定偶爾復(fù)位或死機(jī)電源問題用示波器觀察電源紋波。電機(jī)、繼電器等大電流設(shè)備啟停會(huì)造成電源電壓跌落。解決方法電源輸入端加大容量電解電容如100uF單片機(jī)電源引腳附近加0.1uF去耦電容。信號干擾長的并行線如到數(shù)碼管的排線可能成為天線引入干擾。盡量縮短走線或使用排線。看門狗未處理如果程序開啟了看門狗WDT必須在主循環(huán)中定期喂狗否則會(huì)導(dǎo)致復(fù)位。本項(xiàng)目未使用看門狗但需注意。6. 功能擴(kuò)展與優(yōu)化思路基礎(chǔ)功能實(shí)現(xiàn)后可以嘗試以下擴(kuò)展讓項(xiàng)目更具挑戰(zhàn)性和實(shí)用性提高計(jì)時(shí)精度與穩(wěn)定性使用定時(shí)器的自動(dòng)重裝模式模式2。模式2是8位自動(dòng)重裝模式中斷頻率更高但重裝初值自動(dòng)完成中斷響應(yīng)更及時(shí)能減少由于手動(dòng)重裝初值帶來的微小誤差。采用更高精度的外部晶振如11.0592MHz對串口友好或22.1184MHz甚至溫補(bǔ)晶振。在中斷服務(wù)程序中避免進(jìn)行浮點(diǎn)運(yùn)算、長循環(huán)等耗時(shí)操作。只做最簡單的計(jì)數(shù)和標(biāo)志位設(shè)置復(fù)雜的顯示更新放在主循環(huán)。增加更豐富的顯示與功能顯示擴(kuò)展使用更多位數(shù)碼管如6位來顯示“分:秒.百分秒”?;蛘呤褂肔CD1602液晶屏可以顯示更多信息如當(dāng)前模式、計(jì)次記錄等。多組計(jì)次存儲(chǔ)用數(shù)組存儲(chǔ)多組計(jì)次時(shí)間如最近10次并通過按鍵翻看。倒計(jì)時(shí)功能增加一個(gè)模式可以預(yù)設(shè)時(shí)間進(jìn)行倒計(jì)時(shí)時(shí)間到后蜂鳴器長鳴。報(bào)警功能倒計(jì)時(shí)結(jié)束或達(dá)到預(yù)設(shè)時(shí)間點(diǎn)蜂鳴器以特定模式報(bào)警。優(yōu)化功耗適用于電池供電在暫?;虼龣C(jī)狀態(tài)關(guān)閉數(shù)碼管顯示和蜂鳴器驅(qū)動(dòng)。將單片機(jī)設(shè)置為空閑模式Idle Mode或掉電模式Power Down Mode通過外部中斷如按鍵中斷喚醒。這需要硬件上支持將按鍵連接到外部中斷引腳如INT0, INT1。提升用戶體驗(yàn)按鍵音效差異化給啟動(dòng)、暫停、復(fù)位、計(jì)次分配不同的蜂鳴器聲音不同頻率或節(jié)奏。顯示效果復(fù)位時(shí)讓數(shù)碼管數(shù)字滾動(dòng)歸零或者計(jì)時(shí)到達(dá)整數(shù)秒時(shí)讓小數(shù)點(diǎn)閃爍。抗干擾設(shè)計(jì)在按鍵I/O口對地并聯(lián)一個(gè)小電容如0.1uF進(jìn)一步濾除抖動(dòng)和噪聲。這個(gè)基于51單片機(jī)的秒表項(xiàng)目從仿真到實(shí)物從軟件到硬件完整地走了一遍嵌入式系統(tǒng)開發(fā)的基本流程。它就像一把鑰匙幫你打開了單片機(jī)世界的大門。過程中遇到的每一個(gè)問題查資料、調(diào)試、解決的過程都是寶貴的經(jīng)驗(yàn)。當(dāng)你看到自己親手制作的秒表精準(zhǔn)地跳動(dòng)那種成就感是無可替代的。希望這份超詳細(xì)的拆解能幫你少走些彎路更順暢地完成這個(gè)經(jīng)典而有趣的項(xiàng)目。本文還有配套的精品資源點(diǎn)擊獲取