
簡介面向51單片機學習者和智能小車愛好者這份源代碼完整實現(xiàn)了紅外循跡與超聲波避障的組合控制。程序基于STC15W4K56S4單片機在KEIL環(huán)境下開發(fā)搭配L293D電機驅動、1602液晶顯示、直流減速電機以及紅外循跡、紅外避障和超聲波測距模塊。運行邏輯清晰前方無阻礙時小車循跡行駛檢測到障礙物立即停車移開障礙物后自動恢復循跡可直接用于課程設計或競賽原型驗證。資源包共62個文件壓縮包大小約150KB。文件類型覆蓋C源碼、H頭文件、HEX燒錄文件、KEIL工程文件及說明文檔還包含lst、obj、m51等編譯過程文件便于二次修改與重新編譯。源碼按功能模塊拆分如電機驅動、超聲波測距、紅外避障、循跡控制和液晶顯示等配合必看說明可快速上手。該資源已有3431人學習下載作者在自建小車上親測可用。下載后可直接導入KEIL查看邏輯也可燒錄HEX到實際硬件驗證適合想掌握51單片機多傳感器融合控制的開發(fā)者參考。 拿到這套“51單片機智能小車紅外循跡超聲波避障程序源代碼”的時候我第一反應是這幾乎是每一個做嵌入式或者電子設計入門的人都繞不開的一個完整項目。很多人最開始玩單片機就是點個流水燈或者做個電子時鐘但真正讓你從“點亮一顆LED”過渡到“一個能自動跑的小車”這個項目剛好卡在兩者中間。它涵蓋了傳感器讀取、電機驅動、PWM調速、定時器中斷、邏輯判斷甚至還有一點簡單的避障策略麻雀雖小五臟俱全。如果你正在準備課程設計、電子設計競賽或者單純想搞一臺能在桌面上追著黑線跑的小車這套源碼的參考價值是實實在在的。它在硬件上用的是經典的51單片機STC89C52或者AT89S52都行配合TCRT5000紅外循跡模塊和HC-SR04超聲波傳感器。這兩個模塊是整個項目的靈魂也是難點所在。我先說個結論這個項目不難但坑很多尤其是供電和傳感器閾值調節(jié)能折磨人兩三天。下文我會把整個項目的硬件搭建、模塊原理、代碼核心邏輯以及我調試過程中踩過的坑全部拆開講清楚。1. 項目整體設計與硬件架構拆解1.1 為什么選51單片機而不是STM32或Arduino很多人一上來就糾結主控選型覺得51單片機性能太弱不如直接用STM32或者Arduino。我個人的看法是這個項目用51恰恰是最合適的選擇。STM32雖然性能強但對于一個循跡加避障的小車來說絕大部分算力都是浪費的。Arduino確實開發(fā)快但很多學校的課程設計和競賽默認平臺就是51而且51的寄存器操作方式能讓你把每一個外設的原理都吃透比如定時器怎么配置、中斷怎么觸發(fā)、PWM怎么模擬。你把這些搞明白了后面去學STM32其實就是換了個庫的事。還有一點51單片機的手冊和例程極其豐富網上隨便一搜就是幾百個版本。對于新手來說遇到問題容易找到參考。而且STC89C52這種芯片的成本只有幾塊錢即使燒錯了幾個IO口換一片也不心疼。1.2 小車硬件的選型思路與整體框架整個小車系統(tǒng)可以拆成五個部分主控板、電機驅動、傳感器、電源、車架。我這次用的配置如下也是網上流傳最廣的一套方案模塊型號說明主控STC89C52RC40腳DIP封裝12MHz晶振電機驅動L298N雙H橋驅動支持PWM調速直流電機4個TT馬達兩驅或四驅減速比1:48扭矩夠用循跡模塊TCRT5000三路紅外對管帶LM393比較器數(shù)字輸出超聲波模塊HC-SR04測距范圍2cm-400cm舵機可選SG90用于超聲波云臺左右掃描電源7.4V鋰電池組 LM2596降壓模塊電機直供邏輯部分降壓到5V整個系統(tǒng)的連接方式是這樣的電池正極經過一個總開關分成兩路一路直接給L298N電機驅動供電用于電機另一路經過LM2596降壓到5V后給單片機主板、循跡模塊、超聲波模塊供電。這里最關鍵的一點是共地即降壓模塊的負極與L298N的負極必須連在一起否則傳感器和單片機之間的電平參考不一致邏輯就會亂套。很多人的小車跑起來不受控十有八九就是地線沒接好。TCRT5000循跡模塊的輸出是數(shù)字信號直接接單片機的P1口。HC-SR04超聲波模塊的Trig和Echo分別接兩個IO口測量距離時只需要拉高Trig10微秒然后接收Echo高電平的持續(xù)時長即可。1.3 供電方案的確定與避坑供電是整個項目里最容易出問題的地方尤其是電機和單片機共用電源的情況。TT馬達啟動瞬間電流能到1A甚至更高電壓跌落非常明顯。如果單片機和傳感器跟電機共用一個電源且沒有做降壓隔離小車一啟動單片機就會復位表現(xiàn)就是車跑起來沒幾秒就重啟。我的做法是7.4V鋰電池直接給電機驅動供電LM2596降壓到5V給單片機。比較便宜的方案是用三端穩(wěn)壓芯片7805但7805壓差要求較高7.4V輸入輸出5V時效率不高而且發(fā)熱明顯功耗全浪費在發(fā)熱上了。LM2596是開關電源效率高發(fā)熱低實測下來電池續(xù)航比7805方案多20%以上。注意無論采用哪種方案共地都不能省。不共地信號線上測到的電壓永遠是不確定的系統(tǒng)必然隨機性失控。2. 紅外循跡模塊的原理與控制邏輯2.1 TCRT5000到底在檢測什么TCRT5000內部集成了一個紅外發(fā)射管和一個光電接收管。發(fā)射管持續(xù)發(fā)射紅外光紅外光照射到地面后由于黑色和白色的反射率不同接收管接收到的紅外光強度也不同。模塊上自帶一個LM393電壓比較器將接收管上的電壓與一個可調閾值電壓進行比較最終輸出高電平或低電平。當紅外光被黑色軌道吸收時接收管幾乎收不到反射光比較器輸出高電平通常標記為黑線檢測當小車行駛在白色地面上時反射光強接收管導通比較器輸出低電平白底。模塊上的藍色電位器就是用來調節(jié)這個比較閾值的。閾值調得太靈地面有一點污漬都會被當成黑線調得太鈍黑線又檢測不到。我的一般做法是把模塊放在白底上方約1.5cm處旋轉電位器直到輸出剛好跳變然后再往靈敏方向微調半圈。2.2 三路循跡的控制策略三路循跡模塊的安裝位置呈一字排開左、中、右各一個傳感器。中間的傳感器負責對準黑線左右兩個負責檢測偏離方向。邏輯控制其實就是一個非常簡單的查表判斷左傳感器中傳感器右傳感器判斷結果動作111全在黑線上十字路口直行000全在白底上丟線停止100左偏左轉001右偏右轉101中間在黑線上直行010中間對準黑線直行011車頭偏左左轉110車頭偏右右轉表中的1代表檢測到黑線0代表白底。這里有一個細節(jié)左右傳感器的安裝間距要和實際軌道的線寬匹配一般黑線寬度在1.5cm到2.5cm之間時傳感器間距15mm左右比較合適。間距太寬小車會在黑線上走“之”字形間距太窄轉向反應又太遲鈍。2.3 為什么不用PID也能跑直線很多新手一看到循跡小車就問是不是要用PID控制。我坦率地說對于51單片機配合TCRT5000這種簡單數(shù)字量的應用場景PID反而有點殺雞用牛刀的感覺。數(shù)字傳感器的輸出只有0和1沒有連續(xù)的偏差量你看到的就是左邊偏了就往右打右邊偏了就往左打本質上是bang-bang控制開關控制。這種控制方式在小車速度不高PWM占空比40%左右的情況下完全夠用。如果后續(xù)想讓小車的循跡更絲滑可以在代碼邏輯上做一些變通比如檢測到輕微偏轉時先給一個較小的轉向PWM檢測到重度偏轉時再給全力轉向。這種做法相當于一個模糊的P控制雖然沒有標準PID那么精細但比單純的開關控制好很多而且不增加代碼復雜度。2.4 循跡模塊的物理安裝方向TCRT5000的安裝高度建議在距離地面1.5cm到2.5cm之間。離得太近小車稍微顛簸一下模塊就貼地了信號被遮擋離得太遠紅外光發(fā)散嚴重黑白反射差異被稀釋比較器不好判斷。安裝方向要保證發(fā)射管和接收管垂直于地面歪斜會導致接收光強度下降閾值判斷不穩(wěn)定。模塊固定我推薦用銅柱加螺絲鎖死在底盤上不要用熱熔膠。熱熔膠在電機振動下容易脫落而且一脫落就是三路全歪排查起來非常痛苦。3. 超聲波避障模塊的原理與數(shù)據(jù)采集3.1 HC-SR04的測距時序細節(jié)HC-SR04的測距原理用一句話概括就是記錄超聲波從發(fā)射出去到反射回來的時間然后乘以聲速再除以2。但工程上實現(xiàn)的細節(jié)比這句話要具體得多很多人的代碼看似邏輯正確實測卻不行問題就出在時序控制上。正確的時序如下先將Trig引腳拉低至少2微秒確保模塊處于復位狀態(tài)。將Trig引腳拉高至少10微秒模塊內部會自動發(fā)射8個40kHz的方波脈沖并等待接收回波。發(fā)送完方波之后模塊會主動將Echo引腳拉高Echo拉高的時間就是聲波往返的時間。單片機需要精確測量Echo引腳高電平的持續(xù)時間。在代碼中這套時序寫成void Ultrasonic_Trigger(void) { TRIG 0; // Trig拉低 delay_us(5); // 保持低電平 TRIG 1; // Trig拉高 delay_us(15); // 保持高電平至少10us TRIG 0; // 拉低結束觸發(fā) } float Ultrasonic_GetDistance(void) { unsigned int time_us 0; Ultrasonic_Trigger(); // 發(fā)送觸發(fā)信號 while (ECHO 0); // 等待Echo拉高 TR0 0; TH0 0; TL0 0; // 清零定時器 TR0 1; // 啟動定時器開始計時 while (ECHO 1); // 等待Echo拉低即等待回波 TR0 0; // 停止計時 time_us (TH0 8) | TL0; // 讀取定時器值 return time_us * 0.017; // 距離(cm)詳見下節(jié)推導 }3.2 距離計算公式的推導聲速在空氣約340m/s也就是0.034cm/微秒。Echo高電平的持續(xù)時間是聲波從發(fā)出到接收的往返時間所以距離的計算公式是距離(cm) 時間(微秒) * 0.034 / 2 時間(微秒) * 0.017很多例程里面直接寫distance time * 0.017這個0.017就是這么來的。對于12MHz晶振的單片機定時器每微秒計數(shù)一次12個時鐘周期所以用TH0和TL0拼出來的數(shù)值本身就是微秒數(shù)不需要再除以12。但如果你用的是11.0592MHz晶振就必須做頻率修正否則距離始終會偏大或偏小。3.3 超聲波測距的避坑經驗HC-SR04的精度在實際使用中受很多因素影響。第一是環(huán)境溫度聲速跟溫度正相關溫度每上升1度聲速大約增加0.6m/s。在室內恒定溫度下影響不大但在室外環(huán)境溫差明顯的地方測距值會出現(xiàn)漂移。第二是反射面形狀超聲波打到斜面或圓柱面上很大一部分能量會被反射到其他方向導致測不到回波Echo會超時拉高程序就會卡死在while等待循環(huán)里。所以代碼里必須加超時判斷。我的處理方式是在等待Echo拉高和等待Echo拉低的兩個while循環(huán)中都加一個超時計數(shù)變量如果超過30ms對應約5米距離還沒有反應直接退出循環(huán)并返回一個固定的最大值比如99cm避免單片機卡死在等待中。unsigned int timeout 0; while (ECHO 1) { if (timeout 30000) { // 約30ms超時 TR0 0; return 99; // 超時返回大值 } }3.4 避障策略左轉優(yōu)先還是右轉優(yōu)先有了距離數(shù)據(jù)之后避障策略就變成邏輯判斷了。我常用的策略是先讓超聲波測前方距離如果大于設定閾值比如25cm小車直行如果小于閾值停車然后原地向左轉90度或者右轉再次測距如果新方向安全則前進否則向反方向轉。這個策略有一個隱藏的問題就是超聲波模塊是固定的還是帶舵機云臺的。固定安裝時超聲波只能檢測正前方一個方向小車在遇到斜側面的障礙物時如果正前方的檢測窗口剛好漏過了障礙物邊緣就會撞上去。我的做法是給超聲波模塊加一個SG90舵機左右各轉60度形成三個方向的掃描。雖然51單片機驅動舵機需要寫PWM但用定時器加IO翻轉就能實現(xiàn)不算復雜。實測下來三向掃描的避障成功率比單方向檢測高很多尤其面對墻角這類場景效果明顯。4. 程序源代碼的結構分析與核心代碼講解4.1 模塊化編程的文件規(guī)劃拿到源碼包后先看文件結構這是快速理解代碼脈絡的捷徑。一套健康的51工程項目文件應該包含以下幾個層次main.c主循環(huán)負責初始化和調度delay.c/delay.h延時函數(shù)微秒和毫秒級motor.c/motor.h電機控制包括前進、后退、左轉、右轉、PWM調速tracking.c/tracking.h循跡傳感器的讀取和策略判斷ultrasonic.c/ultrasonic.h超聲波測距的觸發(fā)和距離計算uart.c/uart.h串口調試可選文件分模塊帶來的最大好處是你可以單獨測試某一部分功能。比如先用一個簡單的測試程序確認電機正反轉方向再單獨用串口打印出超聲波的距離數(shù)據(jù)最后再合并整體邏輯。如果不分模塊所有代碼堆在main.c里一旦出現(xiàn)問題排查難度翻倍。4.2 主循環(huán)的執(zhí)行調度主循環(huán)的邏輯應該保持簡單盡量不讓阻塞性函數(shù)占住整個CPU。我寫的main.c核心如下void main(void) { unsigned int distance; Timer0_Init(); // 初始化定時器0 PWM_Init(); // 初始化PWM輸出 Motor_Init(); // 初始化電機引腳 while (1) { Tracking_Handle(); // 循跡邏輯判斷 distance Ultrasonic_GetDistance(); // 測距 if (distance 25) // 前方障礙物過近 { Motor_Stop(); Car_Turn_Left(300); // 左轉300ms delay_ms(100); } else { Motor_Go_Straight(); // 直線前進 } } }這里有一個常見的架構陷阱如果把循跡和避障放在同一個主循環(huán)里并且超聲波測距的while等待循環(huán)時間太長單片機就做不了其他事情邏輯會顯得非?!翱D”。一個比較好的優(yōu)化方式是給整個循環(huán)加一個固定節(jié)拍比如每50ms執(zhí)行一次循跡判斷每200ms執(zhí)行一次超聲波測距。這樣兩個功能的干擾會降到最低。4.3 定時器與中斷的正確配置51單片機產生PWM的方式比較原始最常見的就是用定時器中斷每隔一個固定時間翻轉IO口來模擬PWM波。舉個例子如果想讓一個電機引腳輸出50%占空比、周期為10ms的PWM信號那就用定時器產生5ms中斷中斷服務函數(shù)里翻轉一次IO。void Timer0_ISR(void) interrupt 1 { TH0 0xEE; // 重新裝載初值 TL0 0x00; // 對應約5ms中斷 pwm_counter; if (pwm_counter 100) { pwm_counter 0; } if (pwm_counter motor_speed_left) { IN1 1; // 左電機正轉控制 } else { IN1 0; } }motor_speed_left變量就是占空比的等效表達0到100之間的數(shù)值越大平均電壓越高轉速越快。用這種軟件PWM的好處是占空比隨時可調缺點是比較占中斷資源所以中斷服務函數(shù)里面不要放復雜邏輯。4.4 源碼中需要注意的易錯點位我見過特別多的例程在起始處沒有先設置IO口的初始電平導致上電瞬間電機亂轉一下。這是很多新手忽略的細節(jié)單片機在復位瞬間IO口默認是高電平如果電機驅動芯片的使能端和輸入引腳是高電平有效那么電機上電就會瞬間轉一下再停下來。編程時要在主函數(shù)的最開始先把所有電機控制引腳拉低使能引腳也拉低然后再初始化其他外設。第二個容易出錯的點是舵機PWM的周期。SG90舵機需要20ms周期的PWM信號1.5ms的高電平對應90度。用定時器中斷模擬舵機PWM時定時器初值的計算要精確到微秒如果脈沖寬度偏差超過0.3ms舵機角度就會嚴重偏移。5. 常見故障、調試技巧與經驗總結5.1 小車走不出直線的排查小車走不直是最常見的問題但原因可能有很多我提供一個從簡到繁的排查順序左右電機的接線極性是否一致。L298N的OUT1/OUT2接左電機OUT3/OUT4接右電機接反了左右轉向就會鏡像表現(xiàn)出來就是走不直甚至原地繞圈。左右電機轉速差異。TT馬達雖然是同一個型號但個體差異導致轉速不可能完全一致即使用相同的PWM占空比小車也會向慢的一側偏移。解決方式是實測左右輪在不同占空比下的實際速度在代碼里補償。電池電量下降帶來的電壓跌落。實測下來電池從滿電7.4V降到6.5V左右時同樣的PWM占空比下電機轉速會有明顯下降如果代碼里的補償值是固定寫死的電量變化后又會偏。5.2 紅外循跡模塊輸出一直不變模塊輸出一直為高或者一直為低先檢查模塊上的電源指示燈和信號指示燈有沒有正常亮起。如果沒有亮大概率是3.3V供電還是5V供電接錯了。TCRT5000模塊有的版本支持3.3V到5V寬電壓有的只支持5V電壓不足時發(fā)射管亮度不夠接收管完全收不到反射信號。如果供電正常但輸出一直不對把模塊拿到黑線正上方邊旋轉電位器邊看信號指示燈有沒有變化。如果不管怎么調都沒反應大概率是模塊本身壞了換一個模塊測試。TCRT5000這個器件本身很皮實但模塊上焊接的貼片電阻和電容器件比較脆弱焊接溫度過高導致虛焊的情況不在少數(shù)。5.3 超聲波測距數(shù)據(jù)跳變的處理超聲波測距數(shù)據(jù)偶爾跳變是正?,F(xiàn)象因為有環(huán)境雜波干擾或反射面不規(guī)則。處理辦法有兩種一是連續(xù)測5次去掉最大值和最小值然后取剩下3次的平均值二是加一個簡單的濾波判斷如果當前值和上一次值的差超過某個閾值比如20cm丟棄繼續(xù)用舊值。我個人推薦第二種因為計算量小而且對實時避障來說更有用不會因為一個瞬時的大跳變而做出過激的轉向反應。5.4 電量消耗過快的建議小車用的是18650鋰電池組標稱容量2000mAh左右實測滿電連續(xù)跑大概在40分鐘左右。如果你發(fā)現(xiàn)續(xù)航遠低于這個數(shù)先檢查L298N的散熱片溫度燙手的話說明電機堵轉或者PWM頻率太低導致開關損耗大??梢园裀WM頻率提高到10kHz以上降低功率管的開關損耗同時對電池也是一個保護。5.5 調試時最好加一個串口打印我在開發(fā)時必開串口打印30塊錢的USB轉TTL模塊就能干這個活。把循跡三路傳感器的實時狀態(tài)、超聲波的距離數(shù)據(jù)、當前電機的PWM占空比全部通過串口發(fā)到電腦串口助手一眼就能看出問題是出在傳感器還是執(zhí)行器還是邏輯。不要用矩陣燈或者示波器去猜調試效率太低。void UART_Send_Byte(unsigned char dat) { SBUF dat; while (!TI); TI 0; } void UART_Send_String(char *str) { while (*str) { UART_Send_Byte(*str); } }一段簡單的發(fā)送函數(shù)在循環(huán)里格式化出一串字符發(fā)出去sprintf(buffer, L:%d M:%d R:%d Dis:%dcm\r\n, sensor_left, sensor_mid, sensor_right, distance); UART_Send_String(buffer);這樣調起來心里就有底了小車跑偏了你能看到是哪路傳感器誤觸發(fā)避障撞車了你能看到是距離閾值設大了還是轉向時間不合適。6. 源碼該從哪里開始改拿到源碼包第一件事不是直接燒錄而是先打開motor.c文件把左右電機的PWM占空比或者轉速補償寫好。因為后續(xù)所有調試都建立在“小車能沿著直線走”的基礎上。走不直的話循跡和避障的邏輯再完美都白搭。然后再調整循跡模塊的電位器傳感器的安裝高度和間距。最后再調試避障的距離閾值我常用的參數(shù)是前方30cm內開始減速25cm內停車轉向轉向后測到新方向大于40cm才繼續(xù)直行。不同車架和電機性能略有差異這個參數(shù)需要實測微調。提示在實際調車之前把車輪拆了架空測試是很好的習慣。架空時電機空轉負載小電流小能避免很多供電和電流沖擊引起的問題。等邏輯調通后再裝上輪子地面跑。7. 一點個人體會這個項目其實就代表了一條完整的學習路線從單片機外設驅動到傳感器原理從執(zhí)行機構控制到閉環(huán)策略設計全部經歷一遍。很多過來人說“51單片機過時了”但如果你真的把一個51小車從零做到能穩(wěn)定循跡、靈活避障你再回頭看STM32、ESP32這些平臺會發(fā)現(xiàn)難點根本不在主控性能而在你對硬件原理和軟件架構的理解夠不夠深。我在做完這個小車之后最大的收獲是學會了怎么把一個大問題拆成一個個能單獨驗證的小模塊。先點亮LED再驅動電機輪子轉起來再讀到傳感器數(shù)值再把它們串起來。每一步都驗證過了最終合在一起的每一分鐘就都是心理有數(shù)的。這套方法才是這個源碼包里最值錢的東西。本文還有配套的精品資源點擊獲取