
1. 項目概述為什么51單片機依然是工程師的“老朋友”如果你在電子、自動化或者嵌入式開發領域待過一段時間哪怕只是剛入門大概率都繞不開“51單片機”這個名字。它不像現在流行的ARM Cortex-M系列那樣性能強悍也不像ESP32那樣自帶Wi-Fi和藍牙但在我十多年的項目經歷里它就像一位可靠的老朋友總是在一些基礎、穩定、成本敏感的場景里默默發光。今天我們不聊那些高大上的新架構就聊聊這位老朋友——51單片機它到底是什么為什么到今天還有如此旺盛的生命力以及我們如何用好它。簡單來說51單片機是一個基于Intel 8051內核的8位微控制器家族的統稱。它的核心設計誕生于上世紀80年代但憑借其極致的簡單性、成熟的生態和低廉的成本被全球無數半導體公司如STC、Atmel、NXP等生產了海量的兼容型號。當你聽到“STC89C52”、“AT89S51”這些型號時它們都屬于51家族。它的核心價值在于用最低的學習成本和硬件成本讓你理解計算機最底層的運作原理并實現從想法到物理世界的控制。無論是讓一個LED燈閃爍還是驅動一個小車底盤或是做一個溫濕度計51單片機都是絕佳的起點和可靠的量產選擇。2. 51單片機的核心架構與工作原理解析要駕馭好這位老朋友不能只停留在“燒錄代碼讓它跑”的層面得先摸清它的“脾氣秉性”也就是其內部架構和工作原理。這對于后續的編程、調試和問題排查至關重要。2.1 經典哈佛結構與存儲空間51單片機采用經典的哈佛結構這意味著它的程序存儲器ROM和數據存儲器RAM在物理上是分開的有各自獨立的地址總線和數據總線。這與我們常用的PC馮·諾依曼結構不同。這種設計的好處是指令讀取和數據訪問可以并行提高了執行效率尤其適合簡單的控制任務。對于開發者而言這直接體現在存儲空間的劃分上程序存儲器ROM/Flash存放我們編寫的程序代碼和常量數據。比如STC89C52就有8KB的Flash。代碼在這里是“只讀”的上電后CPU從這里逐條讀取指令執行。數據存儲器RAM用于存放程序運行時的變量、堆棧等臨時數據。通常很小只有128字節或256字節。這就要求編程時必須精打細算避免定義大型數組或復雜數據結構。特殊功能寄存器SFR這是51單片機編程的核心所在。它是一組映射在RAM高地址區80H-FFH的特定寄存器每一個都直接對應著一個硬件功能模塊的控制開關、狀態標志或數據緩沖區。例如P1寄存器控制著P1口8個引腳的電平TCON寄存器控制著定時器的啟停和標志位。注意51的RAM資源非常緊張。我曾在一個需要處理多組傳感器數據的項目里因為定義了幾個全局數組很快就耗盡了128字節的RAM導致程序跑飛。解決方案是1) 盡量使用data/idata區的小內存模式2) 大的查表數據放在code區ROM3) 謹慎使用遞歸和深度函數調用以免堆棧溢出。2.2 CPU時序、機器周期與指令集51單片機的CPU心臟是它的振蕩電路外接一個晶振比如經典的11.0592MHz。CPU的所有動作都基于這個時鐘節拍。這里有個關鍵概念機器周期。在標準51架構中1個機器周期 12個時鐘周期。大部分指令的執行需要1個或2個機器周期。這意味著當你使用12MHz晶振時一個機器周期是1微秒。一條單周期指令如NOP執行時間就是1微秒。這個特性使得編寫精確的短延時函數變得非常簡單for(i0; i125; i) { _nop_(); }大約延時125微秒。同時這也決定了定時器、串口波特率等所有與時間相關功能的計算基礎。它的指令集是精簡的大約111條指令分為數據傳送、算術運算、邏輯運算、控制轉移和位操作幾大類。其中位操作指令是51單片機的一大特色。它可以對RAM中可位尋址區20H-2FH和大部分SFR的每一個位進行直接置1、清0、判斷等操作這使得對單個IO口引腳的控制變得極其高效和直觀比如LED 0;點亮LED這樣的語句背后就是一條位操作指令。2.3 核心外設單元淺析51單片機的功能通過其內置的外設模塊實現理解它們是項目實戰的關鍵并行I/O口P0, P1, P2, P3這是單片機與外界溝通最直接的橋梁。每個口有8個引腳。需要特別注意P0口內部沒有上拉電阻當作為通用IO口輸出時需要外接上拉電阻才能輸出高電平否則是開漏狀態。而P1, P2, P3口則內部有上拉。作為輸入口時必須先向端口鎖存器寫“1”使其處于高阻態才能正確讀取外部電平。定時器/計數器Timer51通常有2個16位定時器/計數器T0和T1。它們有兩種模式定時器模式對內部機器周期脈沖計數用于產生精確時間間隔是實現延時、PWM、測量脈寬的基礎。計數器模式對外部引腳T0/P3.4, T1/P3.5的下降沿計數用于測量外部事件頻率。 通過設置相關的SFRTMOD,TCON,THx,TLx可以配置其工作方式和初始值。溢出時會產生中斷這是實現多任務時間片輪詢的基石。串行通信口UART這是51與電腦、其他單片機或模塊如藍牙、Wi-Fi通信的主要方式。通過SCON和PCON寄存器配置波特率、數據位、停止位等。波特率發生器通常由定時器T1工作在模式28位自動重載來產生這也是為什么在串口通信項目中晶振常選用11.0592MHz——因為它可以非常精確地計算出標準的波特率如9600避免累積誤差導致通信失敗。中斷系統51單片機支持5個中斷源2個外部中斷INT0,INT12個定時器中斷TF0,TF11個串口中斷RI/TI。通過IE中斷使能和IP中斷優先級寄存器進行管理。中斷是單片機響應外部緊急事件的核心機制比如按鍵按下、數據接收完成。3. 從零開始51單片機開發環境搭建與第一個項目理論懂了手會癢。接下來我們搭建一個最經典、最通用的開發環境并完成一個“Hello World”級的硬件項目——LED閃爍。3.1 工具鏈選型與安裝對于51開發我強烈推薦“Keil C51 STC-ISP”的組合。這套組合拳經歷了無數項目的考驗穩定且高效。集成開發環境IDEKeil μVision。它是ARM公司旗下的產品對C51的支持非常成熟。你需要安裝Keil C51版本注意不是MDK-ARM。安裝過程簡單主要是注意安裝路徑不要有中文。安裝后需要破解獲取License網上有詳細教程這里不展開。編譯器/鏈接器Keil C51 IDE自帶。它將我們寫的C代碼編譯成51單片機可以執行的機器碼HEX文件。下載編程工具STC-ISP。這是深圳宏晶科技STC提供的免費下載軟件因其界面友好、功能強大附帶串口助手、范例程序等已成為廣大51開發者即使不用STC芯片最常用的下載工具之一。它通過串口USB轉TTL給單片機燒錄程序。硬件連接你需要一個USB轉TTL串口模塊如CH340G、CP2102模塊用于連接電腦USB口和單片機的串口P3.0/RxD, P3.1/TxD。同時該模塊的VCC和GND為單片機供電。對于最小系統板還需連接復位電路和晶振電路。3.2 創建第一個工程LED閃爍假設我們使用STC89C52RC單片機連接一個LED到P1.0引腳負極接LED正極通過限流電阻接VCC。步驟一在Keil中新建工程打開KeilProject - New μVision Project...選擇好工程存放目錄和名稱。在彈出的設備選擇窗口中選擇STC如果你安裝了STC的器件包或Atmel下的AT89C52完全兼容。選擇器件主要是為了初始化啟動文件對代碼影響不大。當詢問“Copy Standard 8051 Startup Code to Project Folder?”時選擇“是”。這個啟動文件STARTUP.A51負責初始化內存和堆棧指針。步驟二編寫主程序在工程中新建一個main.c文件。輸入以下代碼#include REG52.H // 包含51單片機特殊功能寄存器定義的頭文件 #include INTRINS.H // 包含_nop_()延時函數 sbit LED P1^0; // 將P1.0引腳定義為LED // 簡單延時函數大約延時ms毫秒基于12MHz晶振的粗略計算 void DelayMs(unsigned int ms) { unsigned int i, j; for(i0; ims; i) for(j0; j123; j); // 這個循環次數需要根據實際晶振校準 } void main() { // 主函數程序入口 while(1) { // 無限循環 LED 0; // P1.0輸出低電平LED點亮假設共陽接法 DelayMs(500); // 延時500ms LED 1; // P1.0輸出高電平LED熄滅 DelayMs(500); // 延時500ms } }這段代碼做了幾件事包含必要的頭文件用sbit關鍵字進行位定義這是51特有的非常方便寫了一個基于循環的粗略延時函數在主循環中不斷翻轉LED的狀態。步驟三配置與編譯點擊工具欄的Options for Target魔術棒圖標。在Output選項卡勾選Create HEX File。HEX文件是最終要燒錄到單片機里的機器碼文件。點擊BuildF7編譯。如果下方Build Output窗口顯示“0 Error(s), 0 Warning(s)”并生成了.hex文件說明成功。步驟四硬件連接與程序下載按照原理圖將USB轉TTL模塊的TXD接單片機RXDP3.0RXD接單片機TXDP3.1GND接GNDVCC接VCC注意電壓通常是5V。打開STC-ISP軟件。步驟1選擇單片機型號如STC89C52RC。步驟2選擇正確的串口號在電腦設備管理器中查看。步驟3點擊“打開程序文件”選擇剛才Keil生成的.hex文件。步驟4點擊“下載/編程”按鈕。最關鍵的一步給單片機關電再上電冷啟動。STC單片機下載需要冷啟動握手。此時STC-ISP會顯示“正在檢測目標單片機...”然后開始擦除、編程、校驗。成功后你就能看到板載的LED開始閃爍了實操心得第一次下載失敗十有八九是串口線接反了TXD與RXD要對調或冷啟動時機不對。多試兩次這是每個51玩家的必經之路。另外DelayMs函數是非常不精確的延時因為它會被中斷打斷且受編譯器優化影響。在需要精確定時的場合必須使用定時器中斷。4. 核心外設項目實戰與代碼剖析掌握了基本操作我們來攻克幾個標志性的項目深入理解定時器、中斷、PWM和串口這些核心外設。4.1 項目一基于定時器中斷的精準流水燈使用循環延時做流水燈燈會“卡頓”因為CPU被延時函數獨占。用定時器中斷則可以解放CPU實現“后臺”定時前臺處理其他任務。核心思路配置定時器T0每10ms產生一次中斷。在中斷服務程序中用一個計數器累加每計滿100次即1秒就改變一次LED的顯示模式。#include REG52.H #define LED_PORT P1 // 假設8個LED接在P1口 unsigned int T0_Count 0; // 中斷次數計數器 unsigned char LED_Mode 0x01; // LED模式初始值0x010000 0001B void Timer0_Init() { TMOD 0xF0; // 低四位清零不影響T1的設置 TMOD | 0x01; // 設置T0為工作方式116位定時器 TH0 0xD8; // 裝入初值定時10ms (65536 - 10000) / 256 TL0 0xF0; // (65536 - 10000) % 256 ET0 1; // 允許T0中斷 EA 1; // 開啟總中斷 TR0 1; // 啟動T0 } void main() { Timer0_Init(); LED_PORT ~LED_Mode; // 初始狀態低電平點亮取反 while(1) { // 主循環可以在這里做其他事情比如按鍵掃描 // 流水燈的效果完全由中斷服務函數控制不會阻塞這里 } } // 定時器0中斷服務函數 void Timer0_ISR() interrupt 1 { // interrupt 1 對應T0中斷 TH0 0xD8; // 重新裝載初值方式1需手動重裝 TL0 0xF0; T0_Count; if(T0_Count 100) { // 1秒到 T0_Count 0; LED_Mode _crol_(LED_Mode, 1); // 循環左移一位使用 intrins.h 中的函數 LED_PORT ~LED_Mode; // 更新LED顯示 } }代碼解析TMOD寄存器用于設置定時器工作方式。0x01表示T0為16位定時器模式。初值計算目標定時10ms。機器周期12/11.0592MHz≈1.085us。需計次數10ms/1.085us≈9216。對于16位定時器最大值65535初值65536-921656320轉換為十六進制即0xDC00。所以TH00xDCTL00x00。示例中用了近似值。中斷函數interrupt 1是編譯器關鍵字表示這是T0的中斷服務程序。編譯器會自動進行現場保護壓棧和恢復。_crol_()循環左移函數需要包含INTRINS.H。它讓流水燈效果更簡潔。4.2 項目二利用定時器生成PWM控制電機速度PWM脈沖寬度調制是通過調節一個周期內高電平所占的比例占空比來模擬不同電壓/功率的技術廣泛應用于電機調速、LED調光。核心思路固定PWM周期例如10ms使用一個定時器中斷來維護周期計時。在中斷中根據設定的“高電平時間”即占空比來翻轉IO口電平。#include REG52.H sbit PWM_OUT P2^1; // PWM輸出引腳接電機驅動信號 unsigned int High_Time 3000; // 高電平時間單位微秒(us) 占空比 3000/10000 30% unsigned int Low_Time 7000; // 低電平時間 unsigned int Timer_Count 0; // 定時器微秒計數器 void Timer1_Init() { // 使用T1做1us精確定時 TMOD 0x0F; // 高四位清零不影響T0 TMOD | 0x10; // 設置T1為方式116位定時器 TH1 (65536 - (11059200/12/1000000)) / 256; // 1us中斷一次晶振11.0592MHz TL1 (65536 - (11059200/12/1000000)) % 256; ET1 1; EA 1; TR1 1; } void main() { Timer1_Init(); PWM_OUT 1; // 初始輸出高電平 while(1) { // 這里可以通過按鍵或串口來動態改變 High_Time 和 Low_Time實現調速 // 例如if(Key_Pressed) High_Time 100; } } void Timer1_ISR() interrupt 3 { TH1 (65536 - (11059200/12/1000000)) / 256; // 重裝初值 TL1 (65536 - (11059200/12/1000000)) % 256; Timer_Count; if(PWM_OUT 1) { if(Timer_Count High_Time) { PWM_OUT 0; Timer_Count 0; } } else { if(Timer_Count Low_Time) { PWM_OUT 1; Timer_Count 0; } } }注意事項這個例子中定時器中斷頻率很高1MHz會大量占用CPU資源。在實際項目中更常見的做法是使用一個定時器固定周期如100us中斷在中斷里對一個全局變量累加主循環中根據這個變量和預設的占空比比較來翻轉IO口?;蛘咭恍┰鰪娦?1單片機如STC12系列自帶硬件PWM模塊直接配置寄存器即可不占用CPU。4.3 項目三串口通信實現單片機與電腦對話串口是調試和交互的利器。我們實現一個功能單片機每秒向電腦發送一次“Hello 51!”并且能接收電腦發來的一個字符然后回顯該字符。#include REG52.H #include stdio.h // 為了使用 printf需要重定向 putchar unsigned char UART_Rx_Data 0; // 接收數據緩存 bit UART_Rx_Flag 0; // 接收完成標志位 void UART_Init() { SCON 0x50; // 串口方式1允許接收REN1 TMOD 0x0F; // 清零T1控制位 TMOD | 0x20; // 設置T1為方式28位自動重載 TH1 0xFD; // 波特率9600 (11.0592MHz晶振) TL1 0xFD; ET1 0; // 禁止T1中斷因為波特率發生器不需要中斷 TR1 1; // 啟動T1 ES 1; // 允許串口中斷 EA 1; // 開總中斷 } void UART_SendByte(unsigned char dat) { SBUF dat; // 數據寫入發送緩沖區自動啟動發送 while(TI 0); // 等待發送完成 TI 0; // 軟件清零發送中斷標志位 } void UART_SendString(unsigned char *str) { while(*str ! \0) { UART_SendByte(*str); } } void main() { unsigned int i; UART_Init(); UART_SendString(UART Initialized!\r\n); // 發送初始化完成信息 while(1) { if(UART_Rx_Flag) { // 如果收到數據 UART_Rx_Flag 0; UART_SendByte(UART_Rx_Data); // 回顯收到的數據 UART_SendByte(\n); // 換行 } // 模擬每秒發送一次數據 for(i0; i30000; i); // 粗略延時 UART_SendString(Hello 51!\r\n); } } // 串口中斷服務函數 void UART_ISR() interrupt 4 { if(RI) { // 如果是接收中斷 RI 0; // 軟件清零接收中斷標志 UART_Rx_Data SBUF; // 讀取接收到的數據 UART_Rx_Flag 1; // 置位接收標志 } // 發送中斷 TI 在查詢方式發送中處理此處可不處理 }關鍵點解析SCON 0x500101 0000B即方式18位UART并允許接收REN1。TH1 TL1 0xFD這是11.0592MHz晶振下產生9600波特率的經典初值。計算原理定時器T1在方式2下溢出率 晶振頻率 / (12 * (256 - TH1))。令其等于波特率×16串口模式1的需要即可反推出TH1。中斷與查詢結合發送采用查詢等待while(TI0);因為發送時機由程序主動控制接收采用中斷方式因為數據何時到來不確定中斷能及時響應。\r\n是回車換行保證在串口助手上能正確換行顯示。5. 進階實戰智能小車驅動系統設計結合熱詞“51單片機智能小車”我們來設計一個最基礎的直流電機驅動系統。這里以最常見的L298N電機驅動模塊為例。5.1 硬件連接與驅動原理L298N是一個雙H橋驅動芯片可以同時驅動兩個直流電機。每個H橋可以控制電流的方向從而實現電機的正轉、反轉和剎車。連接方式單片機側IN1,IN2- 控制電機A的轉向。例如P2.0, P2.1。IN3,IN4- 控制電機B的轉向。例如P2.2, P2.3。ENA,ENB- 電機A/B的使能端接PWM信號可實現調速??梢灾苯咏覸CC全速或接單片機PWM引腳。電機側OUT1,OUT2接電機A的兩極OUT3,OUT4接電機B的兩極。電源驅動邏輯部分VCC接5V電機驅動部分VS接7-12V根據電機額定電壓。GND共地??刂七壿嬕噪姍CA為例IN1IN2ENA電機A狀態00X停止/剎車101正轉011反轉11X剎車5.2 軟件驅動層實現我們封裝一個電機驅動函數庫便于上層控制。// motor.h #ifndef __MOTOR_H__ #define __MOTOR_H__ typedef enum { MOTOR_STOP, MOTOR_FORWARD, MOTOR_BACKWARD, MOTOR_BRAKE } Motor_State; void Motor_Init(void); void Motor_A_Set(Motor_State state); void Motor_B_Set(Motor_State state); void Motor_PWM_Set(unsigned char pwm_a, unsigned char pwm_b); // 如果使用PWM調速 #endif// motor.c #include REG52.H #include motor.h // 引腳定義根據實際連接修改 sbit MOTOR_A_IN1 P2^0; sbit MOTOR_A_IN2 P2^1; sbit MOTOR_B_IN3 P2^2; sbit MOTOR_B_IN4 P2^3; sbit MOTOR_A_EN P2^4; // PWM使能 sbit MOTOR_B_EN P2^5; // PWM使能 void Motor_Init(void) { MOTOR_A_IN1 0; MOTOR_A_IN2 0; MOTOR_B_IN3 0; MOTOR_B_IN4 0; MOTOR_A_EN 1; // 默認使能全速 MOTOR_B_EN 1; } void Motor_A_Set(Motor_State state) { switch(state) { case MOTOR_STOP: case MOTOR_BRAKE: // 剎車和停止邏輯相同 MOTOR_A_IN1 0; MOTOR_A_IN2 0; break; case MOTOR_FORWARD: MOTOR_A_IN1 1; MOTOR_A_IN2 0; break; case MOTOR_BACKWARD: MOTOR_A_IN1 0; MOTOR_A_IN2 1; break; } } void Motor_B_Set(Motor_State state) { // 類似 Motor_A_Set 的實現 switch(state) { case MOTOR_STOP: case MOTOR_BRAKE: MOTOR_B_IN3 0; MOTOR_B_IN4 0; break; case MOTOR_FORWARD: MOTOR_B_IN3 1; MOTOR_B_IN4 0; break; case MOTOR_BACKWARD: MOTOR_B_IN3 0; MOTOR_B_IN4 1; break; } }在主程序中你就可以像搭積木一樣控制小車了Motor_A_Set(MOTOR_FORWARD); Motor_B_Set(MOTOR_FORWARD); // 小車直行 DelayMs(2000); Motor_A_Set(MOTOR_BACKWARD); Motor_B_Set(MOTOR_STOP); // 小車左轉 DelayMs(1000); Motor_A_Set(MOTOR_STOP); Motor_B_Set(MOTOR_STOP); // 小車停止5.3 集成傳感器與閉環控制基礎驅動之上可以增加紅外、超聲波等傳感器實現自動避障、循跡。例如接入一個HC-SR04超聲波模塊測距。HC-SR04基本驅動觸發給Trig引腳一個至少10us的高電平脈沖?;仨懩K自動發送8個40kHz方波并檢測回波。Echo引腳會輸出一個高電平其持續時間與距離成正比。計算距離 (高電平時間 * 聲速) / 2。sbit HC_TRIG P3^2; sbit HC_ECHO P3^3; float Get_Distance(void) { unsigned int time_us 0; HC_TRIG 0; DelayUs(2); // 短暫延時 HC_TRIG 1; DelayUs(10); // 發出10us觸發脈沖 HC_TRIG 0; while(HC_ECHO 0); // 等待Echo變高 TR0 1; // 啟動定時器T0計時 while(HC_ECHO 1); // 等待Echo變低 TR0 0; // 停止計時 time_us (TH0 8) | TL0; // 組合定時器值假設T0已配置為1us計數 TH0 0; TL0 0; // 定時器清零 return (time_us * 0.017); // 聲速340m/s 換算成cm: 34000cm/s * 1e-6 s/us / 2 ≈ 0.017 }然后在主循環中根據測得的距離控制電機dist Get_Distance(); if(dist 20) { // 距離小于20cm Motor_A_Set(MOTOR_BACKWARD); Motor_B_Set(MOTOR_FORWARD); DelayMs(500); // 倒車并轉彎 } else { Motor_A_Set(MOTOR_FORWARD); Motor_B_Set(MOTOR_FORWARD); // 直行 }這樣一個具備基礎避障功能的智能小車框架就搭建起來了。你可以在此基礎上增加更多的傳感器如紅外循跡模塊和更復雜的控制算法如PID調速。6. 開發中的常見“坑”與調試心法玩了這么多年51踩過的坑比焊過的板子還多。下面這些經驗希望能幫你少走彎路。6.1 電源與復位問題現象程序跑飛、無故復位、IO口驅動能力弱。排查電源濾波單片機電源引腳VCC附近一定要接一個1040.1uF的瓷片電容到地且盡量靠近引腳。這是消除高頻噪聲的標配缺了它系統穩定性大打折扣。復位電路確保復位引腳RST在上電時有足夠時間通常24個時鐘周期的高電平。典型的RC復位電路10uF電容10K電阻在電源紋波大時可能不可靠。對于要求高的場合使用專用復位芯片如MAX809。驅動能力51單片機IO口拉電流輸出高電平能力很弱幾十uA灌電流輸出低電平能力較強十幾mA。所以驅動LED、繼電器等盡量采用低電平驅動器件接VCCIO口接器件負極。驅動大電流負載如電機必須加三極管或MOS管絕不能用IO口直驅。6.2 程序跑飛與內存溢出現象程序運行一段時間后死機、功能錯亂。排查堆棧溢出51的RAM小堆棧區在RAM中向上生長。如果函數調用嵌套太深或中斷函數里調用大量函數極易導致堆棧覆蓋其他數據區。用Keil的調試模式可以查看SP寄存器的最大值。經驗是中斷服務函數盡量短小只置標志位復雜處理放到主循環。數組越界這是C語言的經典問題。訪問了不屬于你的內存區域。仔細檢查所有數組的索引。看門狗如果是STC單片機檢查看門狗定時器WDT是否被意外開啟。如果開啟了必須在程序循環中定期喂狗WDT_CONTR 0x37;否則會導致復位。未初始化變量局部變量和全局變量最好都賦予初始值。隨機值可能導致不可預知的行為。6.3 通信與時序故障現象串口亂碼、I2C/SPI設備無響應。排查波特率誤差確保串口波特率計算準確。11.0592MHz晶振是“串口之友”。如果用了12MHz晶振計算9600波特率會有誤差長時間通信可能出錯。電平匹配51是5V TTL電平。與3.3V設備如很多傳感器、ESP8266通信時必須加電平轉換電路如分壓電阻或電平轉換芯片否則可能損壞3.3V設備。時序問題模擬I2C、單總線如DS18B20等協議時對時序要求嚴格。務必關閉中斷再進行底層時序操作否則一個中斷進來延時就不準了。操作完再打開中斷。上拉電阻I2C的SDA、SCL線以及開漏輸出的信號線如單總線必須接上拉電阻通常4.7K-10K否則無法輸出高電平。6.4 抗干擾與可靠性設計現象在電機啟動、繼電器吸合時單片機復位或誤動作。對策物理隔離電機、繼電器等大功率負載與單片機控制電路使用獨立的電源供電地線在單點相連。信號連接使用光耦隔離。電源去耦除了104小電容在板子電源入口處加一個10-100uF的電解電容緩沖低頻干擾。信號濾波對按鍵、限位開關等機械觸點輸入軟件上必須做消抖處理延時20ms再檢測。硬件上可以在觸點兩端并聯一個104電容。未用引腳處理將未使用的IO口設置為準雙向口模式默認并輸出一個固定電平通常為1不要懸空防止靜電積累或感應干擾。調試時善用Keil的軟件仿真和串口打印。軟件仿真可以單步跟蹤查看變量和寄存器變化。串口打印則是“printf大法好”在關鍵位置輸出狀態信息是定位問題最直觀的手段。對于51由于內存小通常需要自己實現一個精簡的printf函數或者直接使用UART_SendString輸出特定字符串來標記程序執行到了哪里。