測報警系統(tǒng)設計與實現(xiàn))
簡介面向單片機課程設計、畢業(yè)設計與電子入門實踐的51單片機汽車胎壓監(jiān)測報警系統(tǒng)資料包圍繞輪胎壓力實時采集、閾值判斷與聲光報警展開幫助讀者搭建從傳感器信號處理、51單片機程序控制到顯示更新和報警聯(lián)動的完整方案。標題信息顯示包內以程序源碼、仿真工程、電路原理圖和元件清單為主可用于對照理解系統(tǒng)組成、接口設計與仿真驗證流程文件總數(shù)暫未標注類型明細上游未提供壓縮包約9.87MB?,F(xiàn)有319人學習適合需要快速獲取胎壓監(jiān)測參考實現(xiàn)、梳理硬件連接與程序邏輯的讀者。資料覆蓋初始化配置、數(shù)據(jù)采集濾波、壓力計算、報警判斷及顯示刷新等關鍵環(huán)節(jié)也便于在仿真環(huán)境中提前排查問題、優(yōu)化硬件配置與代碼作為課程設計或項目原型的起步材料較為實用。1. 四個輪子的壓力同時盯這套 51 單片機胎壓監(jiān)測報警系統(tǒng)解決的是什么問題四個輪胎的壓力值同時掛在一塊 1602 屏上任意一路掉到 1.8 bar 以下或者竄到 3.2 bar 以上蜂鳴器就開始叫對應那一路的 LED 閃起來。這套基于 STC89C52 的汽車胎壓監(jiān)測報警系統(tǒng)本質上就是把「采樣 → 換算 → 比較 → 報警」這條鏈路在一個 8 位單片機上完整跑通壓縮包里把程序、Proteus 仿真、原理圖和元件清單一次性給全了屬于拿到手能編譯、能仿真、能照著焊的那一類資源。它適合三類人課程設計要交活的、想練手 51 外設驅動ADC0832、LCD1602、獨立按鍵、蜂鳴器的初學者以及要給老設備加個簡易胎壓巡檢的工程開發(fā)人員。它不追求 CAN 總線和無線組網勝在鏈路完整——每一行代碼都能對上原理圖上的一個網絡標號這對剛開始啃單片機的人來說比一個功能花哨但看不透的工程有價值得多。2. 系統(tǒng)架構與元件清單從壓力傳感器到四路輪詢的完整鏈路2.1 硬件分層主控、采樣、人機、報警四塊怎么分工把原理圖攤開整個系統(tǒng)其實就四塊分工特別清楚。第一塊是主控最小系統(tǒng)STC89C52RC 加晶體振蕩電路、上電復位電路和電源去耦。這塊沒任何花樣DIP40 的芯片、兩個 22pF 瓷片電容配一個晶振、10uF 電解加 10k 電阻做復位再加一個復位按鍵齊活。第二塊是壓力采樣鏈。四路模擬輸出的壓力傳感器各自經一級 RC 低通進多路開關再進 ADC0832 轉成數(shù)字量最后從 P3 口的幾個引腳進單片機。這條鏈是整個系統(tǒng)里唯一需要認真對待的模擬通路后面精度崩不崩八成看這一段。第三塊是人機接口LCD1602 負責顯示四個輪子的實時壓力值和當前閾值四個獨立按鍵分別對應「設置」「加」「減」「切輪位」。第四塊是報警輸出一個有源或無源蜂鳴器加四路 LED每路 LED 對應一個輪胎。哪路異常哪路亮。Proteus 仿真里真實的壓力傳感器是用四個電位器替代的——仿真模型庫里沒有 MPX 系列的 spice 模型用可調電阻分壓模擬 05V 的傳感器輸出調一下旋鈕就相當于給輪胎放氣或打氣調試起來反而比實物方便。原理圖上留的是標準三針接口VCC / GND / OUT實物上換成真?zhèn)鞲衅髦苯硬寰托羞@也是這套圖紙能同時兼顧仿真和實物的關鍵設計。2.2 元件清單逐項過每個器件在電路里干什么活壓縮包里的元件清單我按電路功能重新梳理了一遍把關鍵項和選型理由放在下面器件型號/參數(shù)數(shù)量在電路里的作用選型注意主控STC89C52RCDIP401全部邏輯運算與外設調度兼容 AT89C52Keil 里可直接選 Atmel 器件晶振11.0592MHz1系統(tǒng)時鐘要跑串口就選它12MHz 除不盡波特率復位電容10uF / 16V 電解1上電復位延時配 10k 下拉電阻模數(shù)轉換ADC083218 位串行 A/D只有 2 個單端通道四路要擴展多路開關CD4051 / CD40521四路傳感器切換4051 是 8 選 14052 是雙 4 選 1壓力傳感器模擬電壓輸出型4感受氣壓并輸出電壓量程要覆蓋 0250kPa液晶LCD16021顯示四路壓力與狀態(tài)帶背光版更好調對比度電位器10k1調 LCD 的 V0少了它大概率白屏獨立按鍵6×6 輕觸4設置閾值、切換輪位一端接地靠內部上拉蜂鳴器5V 有源/無源1聲音報警無源的要方波驅動驅動三極管S8550PNP1放大蜂鳴器電流單片機 IO 直接驅動帶不動指示 LED5mm 紅/綠4分輪位指示串 1k 限流電阻上拉排阻10k9 腳1P0 口上拉51 的 P0 是開漏不加驅動不了濾波電容100uF 0.1uF各若干電源去耦蜂鳴器電源腳必須加有兩點值得單獨說一下。晶振為什么用 11.0592MHz 而不是 12MHz12MHz 看著整數(shù)好看但 51 的定時器算波特率時除不盡一旦后面要加串口打印調試信息就會翻車11.0592 是 9600 波特率在 51 上的整數(shù)解即便當前這套程序不用串口留著也是給自己留退路。P0 口的排阻容易被忽略。很多人焊完發(fā)現(xiàn) LCD 有背光但一個字符都不顯示折騰半天代碼最后發(fā)現(xiàn)是 P0 沒上拉——51 的 P0 口內部沒有上拉電阻輸出不了高電平數(shù)據(jù)線一直是低LCD 收到的全是 0x00。2.3 信號鏈與供電為什么參考電壓決定了一半的精度ADC0832 是 8 位分辨率參考電壓直接取單片機 VCC5V 滿量程對應 255 個碼值一個 LSB 就是 5000mV / 255 ≈ 19.6mV。按 45mV/kPa 的傳感器靈敏度折算一個碼值大約對應 0.44kPa也就是 0.0044 bar。這個分辨率用來判斷「低于 1.8 bar 就報警」綽綽有余顯示到小數(shù)點后一位完全沒有問題。真正的問題不在分辨率在穩(wěn)定性。蜂鳴器吸合瞬間電流能到 30mA 以上如果它和 ADC 共用同一路 5V 走線VCC 會被拉低幾十毫伏換算下來所有通道的讀數(shù)一起往下掉 23 個碼值。這個現(xiàn)象在仿真里看不出來因為 Proteus 的電源是理想的只有焊出實物才會暴露。我一般的處理是三件事蜂鳴器電源腳并一個 100uF 電解加一個 0.1uF 瓷片走線從電源入口單獨拉一路模擬地和數(shù)字地在電源入口處單點匯合別在板子上到處亂連每路傳感器輸出串一個 1k 電阻加一個 0.1uF 電容到地做成截止頻率約 1.6kHz 的低通把開關電源和電機帶來的高頻噪聲擋在 ADC 外面。還有一件事容易漏多路開關切換通道之后ADC 內部的采樣電容需要建立時間。CD4051 的導通電阻在 5V 供電下大概一兩百歐配上后面的采樣電容穩(wěn)定下來要幾十微秒。代碼里切完地址立刻啟動轉換讀回來的值會帶著上一路的殘留四路讀數(shù)看起來「互相串」。仿真里因為模型理想不明顯實物上就是災難。提示這套圖紙的傳感器接口是通用模擬電壓輸入量程系數(shù)寫在代碼的宏里。換上不同型號的傳感器只需要改SENSOR_K和SENSOR_B兩個宏不用動采樣流程。3. 環(huán)境搭起來Keil C51 編譯、Proteus 聯(lián)調、實物上電的三段式跑通流程3.1 Keil C51 工程配置與一次成功編譯壓縮包里的程序是標準 C51 工程用 Keil uVision4 或 uVision5 加 C51 編譯器打開都能編。打開之后先做三件事缺一件就編不出能跑的 hex。第一確認器件選的是 Atmel 的 AT89C52STC89C52 跟它指令兼容Keil 的器件庫里沒有 STC。第二在 Target Options 的 Output 頁把「Create HEX File」勾上否則編譯只生成 obj 不生成 hexProteus 那邊就會一直加載不到新程序。第三Target 頁的 Xtal 填 11.0592跟實際晶振對上不然所有基于delay_ms()的時序都會偏。工程目錄整理之后大概是這么個結構TPMS_51/ ├── Project/ │ └── TPMS.uvproj # Keil 工程文件 ├── User/ │ ├── main.c # 主循環(huán)與定時調度 │ ├── adc0832.c # 采樣時序與壓力換算 │ ├── lcd1602.c # 顯示驅動 │ └── key.c # 按鍵掃描 ├── Inc/ │ └── tpms.h # 閾值宏、結構體、引腳定義 ├── Sim/ │ └── TPMS.pdsprj # Proteus 仿真工程 ├── Doc/ │ ├── 原理圖.pdf │ └── 元件清單.xlsx └── Output/ └── TPMS.hex # 編譯產物仿真和燒錄都用它編譯完看 Build Output 窗口必須是0 Error(s), 0 Warning(s)。帶 Warning 也能生成 hex但那種工程往往埋著隱式類型轉換的雷比如把unsigned int的壓力值塞進unsigned char變量超過 255 直接截斷屏幕上的數(shù)會莫名其妙歸零。3.2 Proteus 仿真加載hex、時鐘頻率與虛擬終端仿真工程打開后雙擊那顆單片機彈出屬性框里有兩個值必須改Program File 指向Output/TPMS.hexClock Frequency 填11.0592M。Program File 這一項最容易出錯。Keil 每次編譯生成的 hex 會覆蓋舊文件但 Proteus 如果已經加載過一次雙擊進去看到的路徑是對的實際跑的卻可能是內存里的舊副本。穩(wěn)妥做法是每次重新編譯后在 Proteus 里把 Program File 那一欄重新選一遍或者直接關掉仿真工程重開。Clock Frequency 這一項更隱蔽。Proteus 默認是 12MHz跟 11.0592 差 8%。LCD1602 的初始化時序對時間敏感差這 8% 有時能跑有時不能跑表現(xiàn)出來就是「昨天還好好的今天一打開就白屏」屬于典型的玄學問題。跑起來之后四個電位器就是四個輪胎。往上擰增加電壓相當于打氣往下擰相當于放氣。把某一路擰到底對應的 LED 應該亮、蜂鳴器應該響、屏幕上那一格應該出現(xiàn)異常提示。這一步能全過說明邏輯沒問題可以進實物階段了。3.3 實物焊接與首次上電檢查順序實物階段最忌諱一次焊完再上電。我的習慣是分四步每步都有一個可驗證的輸出。第一步只焊最小系統(tǒng)單片機座、晶振、兩個 22pF、復位電路、電源座和去耦電容。燒一個最簡單的閃燈程序進去P1.0 接個 LED 加 1k 電阻看到燈一閃一閃說明供電、時鐘、復位、下載四件事全對了。這一步不過后面全是白費。第二步焊 LCD1602 和對比度電位器。先不接數(shù)據(jù)線只接電源和背光確認背光亮。再接上數(shù)據(jù)線和控制線燒一個只顯示固定字符串的程序一邊顯示一邊擰那個 10k 電位器直到字符最清晰。很多人卡在「背光亮但沒字」九成是這個電位器擰在極端位置上。第三步焊 ADC0832 和傳感器接口。這時候先不接傳感器把每個通道的輸入腳用杜邦線接到 GND 和 VCC看讀數(shù)是不是穩(wěn)定在 0 和滿量程附近。這一步是在驗證采樣鏈路本身把傳感器排除在外。第四步才焊蜂鳴器和 LED。蜂鳴器一定要接三極管單片機 IO 的灌電流能力有限直推要么聲音小得像蚊子要么把 IO 口燒了。S8550 是 PNP做高邊驅動時 IO 輸出低電平導通這個極性搞反了蜂鳴器永遠不響。4. 核心代碼拆解ADC0832 采樣、壓力換算、閾值狀態(tài)機四段主邏輯4.1 定時調度與主循環(huán)骨架整套程序不用 RTOS也不需要。一個定時器中斷做時基主循環(huán)按標志位干活對 51 這種資源來說是最省的做法。#include reg52.h unsigned int g_tick 0; /* 1ms 時基計數(shù) */ unsigned char g_scan_flag 0; /* 200ms 采樣標志 */ void Timer0_Init(void) { TMOD 0xF0; TMOD | 0x01; /* 定時器0工作方式116位 */ TH0 (65536 - 922) / 256; /* 11.0592MHz/12 921.6kHz1ms 約 922 個計數(shù) */ TL0 (65536 - 922) % 256; ET0 1; EA 1; TR0 1; } void Timer0_ISR(void) interrupt 1 { TH0 (65536 - 922) / 256; TL0 (65536 - 922) % 256; g_tick; if (g_tick % 200 0) { g_scan_flag 1; /* 每 200ms 置一次采樣請求 */ } } void main(void) { System_Init(); /* IO 方向、LCD 初始化、閾值賦默認值 */ Timer0_Init(); while (1) { Key_Scan(); /* 按鍵隨時響應不放在中斷里 */ if (g_scan_flag) { g_scan_flag 0; Wheel_Sample_All(); /* 四路采樣 換算 判斷 */ Alarm_Update(); /* 蜂鳴器與 LED 輸出 */ LCD_Refresh(); /* 刷屏 */ } } }為什么是 200ms輪胎壓力是個慢變量機械系統(tǒng)的響應時間在秒級采樣頻率再高也只是把噪聲一起采進來。200ms 一輪人眼看著數(shù)字是連續(xù)變化的又給了每路傳感器足夠的建立時間和軟件平均的樣本量。按鍵為什么不放中斷按鍵要處理的是「短按」「長按」「連按」這些語義放在中斷里會讓中斷服務函數(shù)變得又長又難維護而且按鍵抖動會在中斷里被放大成多次觸發(fā)。放主循環(huán)里靠狀態(tài)機消抖邏輯清晰得多。4.2 ADC0832 讀時序與壓力換算ADC0832 是串行接口DI 和 DO 共用一根線靠分時切換方向。這段時序寫錯了讀出來就是一堆隨機數(shù)。sbit ADC_CS P3^5; sbit ADC_CLK P3^6; sbit ADC_DIO P3^7; /* ch: 0 或 1對應 ADC0832 的兩個單端通道 */ static unsigned char Adc0832_Read(unsigned char ch) { unsigned char i, dat 0; ADC_CS 0; /* 片選拉低一次轉換開始 */ ADC_CLK 0; ADC_DIO 1; /* 第1個時鐘起始位 */ ADC_CLK 1; ADC_CLK 0; ADC_DIO 1; /* 第2個時鐘SGL1單端輸入模式 */ ADC_CLK 1; ADC_CLK 0; ADC_DIO ch; /* 第3個時鐘ODD 位選擇通道 */ ADC_CLK 1; ADC_CLK 0; ADC_DIO 1; /* 釋放數(shù)據(jù)線交給 ADC 驅動 */ for (i 0; i 8; i) { /* 高位在前讀 8 位 */ ADC_CLK 1; ADC_CLK 0; dat (dat 1) | (unsigned char)ADC_DIO; } ADC_CS 1; /* 片選拉高本次轉換結束 */ return dat; }這段代碼最容易翻車的點是ADC_DIO 1;這一句。51 的 IO 口做輸入之前必須先寫 1準雙向口的特性否則內部的下拉會把線拉死在低電平讀回來永遠是 0。CS 拉高之后也要記得給 DIO 寫 1否則下一輪起始位發(fā)不出去。另一個點是時鐘速度。實物上 ADC0832 最快能跑到 400kHz51 用軟件翻轉 IO 差不多就是幾百 kHz剛好卡在邊緣。如果讀出來的值不穩(wěn)定在兩個ADC_CLK 1; ADC_CLK 0;之間加一兩個空操作_nop_();把時鐘壓到 200kHz 以下穩(wěn)定性立刻上來。原始碼值要變成壓力值中間隔著一個線性換算/* 傳感器手冊給的線性關系Vout Vcc * (K * P B)P 單位 kPa 這里把 K 折算成 mV/kPaB 折算成 mV全部用整數(shù)算避開浮點 */ #define VREF_MV 5000L /* ADC 參考電壓直接取 VCC */ #define ADC_FULL 255L /* 8 位滿量程 */ #define SENSOR_K 45L /* 45 mV/kPa */ #define SENSOR_B (-475L) /* 零點偏移 -475 mV */ unsigned int Adc_To_Press10(unsigned char adc) { long vout_mv; long p10; vout_mv (long)adc * VREF_MV / ADC_FULL; /* ADC 碼值 → 毫伏 */ p10 (vout_mv - SENSOR_B) * 10L / SENSOR_K; /* 反解壓力放大 10 倍 */ if (p10 0) p10 0; if (p10 9999) p10 9999; return (unsigned int)p10; /* 單位 0.1 kPa */ }參數(shù)怎么改SENSOR_K和SENSOR_B完全來自器件手冊的傳輸函數(shù)。上面這組系數(shù)對應的是 15115kPa 量程的氣壓傳感器用在實驗室演示沒問題真要裝車測胎壓必須換成量程覆蓋 0250kPa 的型號然后把手冊里的斜率和截距重新折算進去。這一步不做屏幕上的數(shù)字永遠是六十幾 kPa怎么調都上不去。p10用long而不是int是有原因的vout_mv - SENSOR_B最大能到 5000 475 5475乘 10 就是 54750超過 16 位有符號整數(shù)的 32767用int直接溢出成負數(shù)。采樣還要做平均。單次采樣的抖動在 ±2 個 LSB 左右不處理的話屏幕末位一直在跳看著像壞了#define SAMPLE_NUM 8 static unsigned int Wheel_ReadRaw(unsigned char idx) { unsigned char i; unsigned long sum 0; for (i 0; i SAMPLE_NUM; i) { sum Adc0832_Read(Channel_Map[idx]); /* 通道映射表兼容 4051 擴展 */ } return (unsigned int)(sum / SAMPLE_NUM); /* 8 次算術平均 */ }8 次平均能把隨機噪聲壓到原來的 1/2.8 左右代價是每路多花幾百微秒。四路加起來不到 5ms相對于 200ms 的采樣周期完全可以接受。要更狠一點可以改成去掉最大最小值再平均抗脈沖干擾更好但代碼量翻倍這個場景沒必要。4.3 閾值判斷、消抖與報警狀態(tài)機判斷邏輯本身很簡單難的是讓它別亂叫。車過個減速帶壓力瞬間波動十幾個 kPa如果直接用瞬時值比較蜂鳴器會跟著路面節(jié)奏響。#define ST_OK 0 #define ST_LOW 1 #define ST_HIGH 2 #define ST_SENSOR_ERR 3 typedef struct { unsigned int press; /* 當前壓力單位 0.1kPa */ unsigned int hi; /* 上限 */ unsigned int lo; /* 下限 */ unsigned char raw_st; /* 本次瞬時判斷結果 */ unsigned char st; /* 消抖后的穩(wěn)定狀態(tài) */ unsigned char cnt; /* 連續(xù)一致計數(shù) */ } Wheel_t; Wheel_t g_wheel[4]; static unsigned char Wheel_RawJudge(Wheel_t *w) { if (w-press 50 || w-press 9000) return ST_SENSOR_ERR; /* 斷線或短路 */ if (w-press w-lo) return ST_LOW; if (w-press w-hi) return ST_HIGH; return ST_OK; } void Wheel_Judge_All(void) { unsigned char i, now; for (i 0; i 4; i) { now Wheel_RawJudge(g_wheel[i]); if (now g_wheel[i].raw_st) { if (g_wheel[i].cnt 250) g_wheel[i].cnt; } else { g_wheel[i].cnt 0; g_wheel[i].raw_st now; } /* 連續(xù) 3 輪約 600ms結論一致才認濾掉過坎瞬間的毛刺 */ if (g_wheel[i].cnt 3) { g_wheel[i].st now; } } }600ms 這個數(shù)不是拍腦袋來的。輪胎壓力變化的物理時間常數(shù)在秒級而路面沖擊引起的壓力尖峰通常在 100300ms 內衰減完。3 輪確認剛好卡在兩者中間真的漏氣一定撐得過 600ms假的尖峰一定撐不過。報警輸出單獨抽成一個函數(shù)把「判斷」和「執(zhí)行」解耦后面要加分級報警或者換報警方式只動這一個函數(shù)void Alarm_Update(void) { unsigned char i, any_alarm 0; static unsigned char beep_cnt 0; for (i 0; i 4; i) { if (g_wheel[i].st ! ST_OK) any_alarm 1; } if (any_alarm) { /* 無源蜂鳴器靠方波驅動這里每 1ms 翻轉一次 ≈ 500Hz */ if (beep_cnt 1) { beep_cnt 0; BEEP !BEEP; } } else { BEEP 0; beep_cnt 0; } for (i 0; i 4; i) { LED[i] (g_wheel[i].st ! ST_OK) ? 0 : 1; /* 低電平點亮 */ } }這段里BEEP !BEEP放在主循環(huán)里其實不夠準主循環(huán)一圈的時間不固定實際頻率會飄。嚴謹做法是把翻轉動作挪到 1ms 定時器中斷里主循環(huán)只負責置一個beep_enable標志。仿真里聽不出差別實物上聲音會明顯更干凈。4.4 LCD1602 顯示與按鍵設置顯示部分沒什么技術含量關鍵是排版。四個輪子分兩行每行兩個左邊前輪右邊后輪中間留個空格。/* press 單位 0.1kPa1 bar 100kPa 1000 個計數(shù) 所以 bar 的整數(shù)位是 press/1000小數(shù)位是 press/100%10 */ void LCD_ShowPress(unsigned char pos, unsigned int press) { LCD_ShowChar(pos 0, 0 press / 1000 % 10); LCD_ShowChar(pos 1, .); LCD_ShowChar(pos 2, 0 press / 100 % 10); LCD_ShowChar(pos 3, B); }單位換算這里有個小陷阱0.1kPa 的計數(shù)單位下1 bar 正好是 1000 個計數(shù)所以取整數(shù)位是除以 1000取第一位小數(shù)是除以 100 再模 10。搞混了顯示出來就是「2.25B」這種一看就不對的數(shù)。按鍵用狀態(tài)機消抖不用延時/* K1 設置 K2 加 K3 減 K4 切換輪位低電平有效 */ void Key_Scan(void) { static unsigned char last 0xFF, cnt 0; unsigned char cur (K1 0) | (K2 1) | (K3 2) | (K4 3); if (cur last) { if (cnt 10) cnt; } else { cnt 0; last cur; } if (cnt 3) { /* 連續(xù) 3 次讀到同一狀態(tài)才認 */ if (!(cur 0x01)) Key_SetMode(); if (!(cur 0x02)) Key_Inc(); if (!(cur 0x04)) Key_Dec(); if (!(cur 0x08)) Key_NextWheel(); } }cnt 3這個寫法保證了「一次按下只觸發(fā)一次」。cnt 會繼續(xù)漲到 10 3這個條件之后就再不成立直到松開按鍵把 cnt 清零。用 3就會變成按住不放瘋狂連加設個閾值一路飆到底。5. 避坑與排查從仿真到實物最容易翻車的五條記錄5.1 編譯與仿真階段的三條現(xiàn)象一Keil 編譯 0 錯誤Proteus 里跑起來 LCD 只有第一行黑塊程序像沒跑。原因是 hex 沒更新。Proteus 的 Program File 指向的路徑是對的但它在啟動仿真時已經把這個 hex 讀進內存了Keil 重新編譯覆蓋本文還有配套的精品資源點擊獲取