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