設(shè)計(jì)與調(diào)試全流程)
簡(jiǎn)介基于STM32的大棚溫濕度控制系統(tǒng)完整設(shè)計(jì)方案面向嵌入式單片機(jī)開(kāi)發(fā)者及農(nóng)業(yè)自動(dòng)化相關(guān)專業(yè)學(xué)生針對(duì)溫室環(huán)境溫濕度自動(dòng)監(jiān)測(cè)與控制需求提供從硬件選型、電路構(gòu)成、系統(tǒng)流程到模塊實(shí)現(xiàn)的整體思路。文檔共1個(gè)docx文件壓縮包14KB雖體量精簡(jiǎn)但內(nèi)容覆蓋DHT11傳感器數(shù)據(jù)采集、STM32主控處理、LCD1602顯示、蜂鳴器報(bào)警及風(fēng)扇/加熱片聯(lián)動(dòng)調(diào)節(jié)等關(guān)鍵環(huán)節(jié)并附有芯片選型對(duì)比、分模塊原理分析和系統(tǒng)調(diào)試方法。已有146人學(xué)習(xí)適合作為課程設(shè)計(jì)、畢業(yè)設(shè)計(jì)或大棚智控項(xiàng)目的前期方案參考能幫助讀者快速理解完整的控制邏輯與實(shí)現(xiàn)要點(diǎn)。 做這個(gè)項(xiàng)目之前說(shuō)實(shí)話我已經(jīng)被各種“智能大棚”方案看花了眼。有的用 Arduino有的直接上樹(shù)莓派但真正落到實(shí)際上還是 STM32 最穩(wěn)。原因很簡(jiǎn)單成本低、資料全、能干的活多而且作為畢業(yè)設(shè)計(jì)或者課程設(shè)計(jì)答辯時(shí)候的可講性也強(qiáng)。這個(gè)基于 STM32 的大棚溫濕度控制系統(tǒng)核心就是一塊 STM32 主控配合溫濕度傳感器采集大棚環(huán)境數(shù)據(jù)再根據(jù)設(shè)定的閾值去控制風(fēng)扇、加熱器、水泵這些執(zhí)行機(jī)構(gòu)順帶把數(shù)據(jù)實(shí)時(shí)顯示在屏幕上。整個(gè)過(guò)程看起來(lái)不復(fù)雜但真正做起來(lái)從選型到調(diào)試坑其實(shí)不少。這篇文章我就把我實(shí)際做下來(lái)的完整流程、踩過(guò)的坑、以及排查問(wèn)題的方法全部寫出來(lái)給準(zhǔn)備做類似項(xiàng)目的朋友一個(gè)可以直接參考的路線。1. 系統(tǒng)整體架構(gòu)與控制思路1.1 項(xiàng)目定位與需求拆解先把這個(gè)項(xiàng)目到底要做什么說(shuō)清楚。大棚溫濕度控制本質(zhì)上就是一個(gè)“數(shù)據(jù)采集 數(shù)據(jù)處理 輸出控制”的閉環(huán)系統(tǒng)。放在大棚這個(gè)場(chǎng)景下核心需求有幾個(gè)第一要能實(shí)時(shí)采集空氣溫度和濕度第二要能根據(jù)作物的適宜生長(zhǎng)環(huán)境自動(dòng)開(kāi)啟或者關(guān)閉環(huán)境調(diào)節(jié)設(shè)備第三最好能讓人在本地直觀看到數(shù)據(jù)不用每次掏手機(jī)第四也是容易被忽略的一點(diǎn)系統(tǒng)要有足夠的穩(wěn)定性不能因?yàn)閭鞲衅饕淮握`讀就亂動(dòng)作。拆解下來(lái)功能模塊就很清晰了。采集端是溫濕度傳感器處理端是 STM32 單片機(jī)輸出端是繼電器驅(qū)動(dòng)的各類設(shè)備交互端是顯示屏幕和按鍵再加上一個(gè)用于狀態(tài)提示的蜂鳴器。這就是整套系統(tǒng)的全部構(gòu)成。做這個(gè)項(xiàng)目的時(shí)候我一開(kāi)始也想加 WiFi 遠(yuǎn)程監(jiān)控后來(lái)先把本地功能跑通了再逐步擴(kuò)展的。建議你也按這個(gè)順序來(lái)別一上來(lái)就想著把所有功能堆上去那樣出了問(wèn)題根本定位不到原因。1.2 技術(shù)方案選型原因主控選擇上我用的 STM32F103C8T6也就是大家常說(shuō)的“藍(lán)丸”核心板。選它的理由很實(shí)在價(jià)格便宜十幾塊錢一片主頻 72MHz跑這種溫濕度控制邏輯綽綽有余Flash 64KB、RAM 20KB資源不緊張后續(xù)加功能也夠用。最關(guān)鍵的是這個(gè)型號(hào)的資料多到泛濫不管卡在哪一步搜索引擎基本都能找到答案。做項(xiàng)目最怕的不是技術(shù)難而是遇到問(wèn)題連參考都找不到選 STM32F103 就不會(huì)有這個(gè)煩惱。傳感器方面方案上有 DHT11、DHT22、SHT30 這幾個(gè)常見(jiàn)選擇。我做展示和實(shí)驗(yàn)時(shí)用了 DHT11成本和時(shí)序邏輯都有代表性如果你要做精度更高的實(shí)際應(yīng)用建議直接換 SHT30走 I2C 接口可靠性好很多。這個(gè)取舍后面專門講。輸出端的執(zhí)行設(shè)備最常見(jiàn)的就是繼電器模塊。繼電器相當(dāng)于一個(gè)“電子開(kāi)關(guān)”STM32 的 GPIO 輸出高電平或者低電平通過(guò)三極管驅(qū)動(dòng)繼電器線圈從而控制 220V 或 12V 設(shè)備的通斷。這里有個(gè)很多新手容易忽略的細(xì)節(jié)繼電器的驅(qū)動(dòng)電流在幾十毫安級(jí)別STM32 的 GPIO 直接驅(qū)動(dòng)能力有限必須加三極管或者 ULN2003 這樣的驅(qū)動(dòng)芯片不能直接把繼電器線圈接到單片機(jī)上。1.3 控制策略為什么用滯回控制控制邏輯是整個(gè)系統(tǒng)的靈魂。最簡(jiǎn)單的做法是設(shè)一個(gè)閾值比如溫度高于 35 度就開(kāi)風(fēng)扇低于 35 度就關(guān)風(fēng)扇。但實(shí)際跑起來(lái)你會(huì)發(fā)現(xiàn)傳感器數(shù)據(jù)在閾值附近上下波動(dòng)時(shí)繼電器會(huì)瘋狂吸合釋放那個(gè)“咔噠咔噠”的響聲聽(tīng)著都心疼繼電器壽命也受不了。所以我在實(shí)際項(xiàng)目中用的是滯回控制也就是“兩個(gè)閾值”的思路。舉個(gè)例子降溫控制我這樣設(shè)定溫度超過(guò) 35 度時(shí)打開(kāi)風(fēng)扇但溫度要降到 32 度以下才關(guān)閉風(fēng)扇。中間這個(gè) 35 到 32 的區(qū)間就是滯回區(qū)間。這樣做的好處非常明顯數(shù)據(jù)在小范圍波動(dòng)時(shí)繼電器輸出狀態(tài)不會(huì)頻繁翻轉(zhuǎn)系統(tǒng)的穩(wěn)定性和設(shè)備壽命都大大提升。加濕、加熱的控制邏輯同理。后續(xù)如果想做更精確的控制可以在這個(gè)基礎(chǔ)上上 PID但作為大棚環(huán)境控制滯回控制已經(jīng)完全夠用重點(diǎn)是簡(jiǎn)單可靠出了問(wèn)題好排查。2. 硬件選型與電路設(shè)計(jì)要點(diǎn)2.1 主控核心STM32F103C8T6 夠用嗎這里明確回答夠而且有富余。STM32F103C8T6 屬于 STM32F1 系列ARM Cortex-M3 內(nèi)核72MHz 主頻片上資源包括 64KB Flash、20KB RAM以及豐富的定時(shí)器、串口、I2C、SPI 接口。做溫濕度采集、OLED 顯示、繼電器控制、按鍵輸入這些任務(wù)CPU 占用率其實(shí)很低大量時(shí)間都花在延時(shí)等待上。供電方面要注意STM32 核心板一般通過(guò) USB 供電或者外部 5V 供電板載穩(wěn)壓芯片降到 3.3V 給單片機(jī)。驅(qū)動(dòng) OLED 模塊一般用 3.3V繼電器模塊的供電則要單獨(dú)從 5V 取避免大電流通過(guò)單片機(jī)穩(wěn)壓電路造成壓降。我給系統(tǒng)布線的時(shí)候電源部分做了簡(jiǎn)單隔離STM32 和傳感器共用一組電源繼電器模塊單獨(dú)從 USB 或外部 5V 取電這樣能有效減小繼電器吸合瞬間對(duì)單片機(jī)的干擾。啟動(dòng)模式這個(gè)細(xì)節(jié)剛?cè)腴T的朋友容易忽略。STM32 有 BOOT0 和 BOOT1 引腳用于選擇啟動(dòng)方式。正常運(yùn)行時(shí) BOOT0 接低電平從主 Flash 啟動(dòng)如果 BOOT0 拉高會(huì)進(jìn)入系統(tǒng)存儲(chǔ)器啟動(dòng)模式可以配合串口下載程序。我用 ST-Link 下載程序所以 BOOT0 保持低電平就行這個(gè)問(wèn)題搞清楚能避免不少“下不進(jìn)程序”的困惑。2.2 溫濕度傳感器DHT11 和 SHT30 應(yīng)該怎么選我先把這幾款傳感器的關(guān)鍵參數(shù)列出來(lái)對(duì)比一下方便你選擇。傳感器接口溫度精度濕度精度采樣周期典型價(jià)格DHT11單總線±2℃±5%RH1 次/秒2~5 元DHT22/AM2302單總線±0.5℃±2%RH2 次/秒10~20 元SHT30I2C±0.3℃±2%RH可配置5~10 元如果你是做畢業(yè)設(shè)計(jì)想演示“數(shù)據(jù)采集 控制”這個(gè)完整流程DHT11 完全夠用畢竟一塊錢兩塊錢的成本資料又多。但我要提醒你DHT11 的濕度精度確實(shí)感人標(biāo)稱 ±5%RH 實(shí)際可能更差用來(lái)做定性判斷沒(méi)問(wèn)題做精確測(cè)量就吃力了。如果你打算把這個(gè)系統(tǒng)用到實(shí)際的大棚場(chǎng)景建議直接上 SHT30I2C 通信接口比單總線穩(wěn)定得多也支持 CRC 校驗(yàn)數(shù)據(jù)可靠性高一個(gè)檔次。DHT11 接線的時(shí)候有一個(gè)細(xì)節(jié)必須注意DATA 引腳需要外接一個(gè) 4.7k 到 10k 的上拉電阻到 3.3V。有些開(kāi)發(fā)板模塊上已經(jīng)集成上拉了但如果是裸傳感器或者自己做的 PCB這個(gè)電阻千萬(wàn)不能省。我一開(kāi)始就是忘了加上拉電阻DATA 引腳電平不定讀回來(lái)的數(shù)據(jù)全亂套。2.3 繼電器驅(qū)動(dòng)電路與設(shè)備接入繼電器的驅(qū)動(dòng)電路新手最容易出問(wèn)題。STM32 的 GPIO 是高電平還是低電平觸發(fā)取決于你買的繼電器模塊是“高電平觸發(fā)”還是“低電平觸發(fā)”版本。我用的是低電平觸發(fā)模塊也就是 GPIO 輸出低電平時(shí)繼電器吸合。為什么選低電平因?yàn)橛械膯纹瑱C(jī)在復(fù)位或者啟動(dòng)瞬間引腳狀態(tài)不確定如果是高電平觸發(fā)開(kāi)機(jī)那一刻設(shè)備容易被誤觸發(fā)。低電平觸發(fā)相對(duì)安全更常見(jiàn)的做法是 GPIO 通過(guò)三極管驅(qū)動(dòng)繼電器引腳在開(kāi)機(jī)時(shí)即使短暫懸空繼電器也不會(huì)亂動(dòng)。這塊用的時(shí)候要搞清楚。如果是自己畫板子接分立元件電路大概是GPIO 接一個(gè) 1k~4.7k 電阻電阻另一端接 NPN 三極管基極三極管發(fā)射極接地集電極接繼電器線圈一端線圈另一端接 VCC同時(shí)線圈兩端反向并聯(lián)一個(gè)續(xù)流二極管1N4007 或者 1N4148吸收斷電瞬間的反向電動(dòng)勢(shì)。這個(gè)二極管不能省不然繼電器斷開(kāi)瞬間產(chǎn)生的反向電壓很容易擊穿三極管甚至燒掉引腳。很多第一次做的人不知道這個(gè)結(jié)果繼電器一吸合單片機(jī)就復(fù)位查了半天才知道是干擾問(wèn)題。2.4 顯示與按鍵接口設(shè)計(jì)顯示方面我用的是 0.96 寸 OLEDSSD1306 驅(qū)動(dòng)芯片I2C 接口4 個(gè)引腳就能接好。OLED 功耗低、視角好、顯示內(nèi)容清晰比 1602 液晶屏省事多了不用管背光和對(duì)比度。接線就是 VCC、GND、SCL、SDASCL 接 STM32 的 I2C 時(shí)鐘引腳 PB6SDA 接數(shù)據(jù)引腳 PB7這是 I2C1 的默認(rèn)映射。按鍵接口設(shè)計(jì)上我做了 3 個(gè)獨(dú)立按鍵設(shè)置鍵、加鍵、減鍵。這里有個(gè)經(jīng)典坑按鍵輸入引腳必須配置為輸入上拉模式而且要在硬件上加上拉電阻。因?yàn)槟惆聪掳存I是把引腳拉低不按的時(shí)候引腳應(yīng)該是高電平。如果配置成浮空輸入引腳電平不確定按鍵檢測(cè)就會(huì)時(shí)靈時(shí)不靈。軟件層面上按鍵消抖也不能省。我用的是“檢測(cè)到低電平后延時(shí) 20ms 再讀一次仍然是低電平才認(rèn)為按鍵按下”的方式簡(jiǎn)單有效。3. 軟件設(shè)計(jì)與核心代碼實(shí)現(xiàn)3.1 開(kāi)發(fā)環(huán)境Keil5 CubeMX 還是 VSCodeSTM32 開(kāi)發(fā)環(huán)境的選擇這幾年變化挺大。傳統(tǒng)路線是 Keil MDK5 標(biāo)準(zhǔn)外設(shè)庫(kù)很多教材還在用這套。我的建議是如果剛?cè)腴T直接用 STM32CubeMX 生成初始化代碼加上 HAL 庫(kù)用 Keil5 編譯調(diào)試。CubeMX 的好處是圖形化配置時(shí)鐘樹(shù)、引腳復(fù)用、外設(shè)參數(shù)不用手寫一堆寄存器初始化的代碼出錯(cuò)概率大幅降低能把精力集中在業(yè)務(wù)邏輯上。也有不少人用 VSCode 搭配 EIDE 或者 PlatformIO 插件來(lái)開(kāi)發(fā) STM32體驗(yàn)確實(shí)比 Keil 舒服尤其是代碼補(bǔ)全和 Git 集成。但前提是你得有折騰環(huán)境的時(shí)間插件、編譯鏈、調(diào)試器的配合容易出問(wèn)題。項(xiàng)目時(shí)間緊的話我建議先求穩(wěn)用 Keil5 跑通全流程后續(xù)再嘗試 VSCode。調(diào)試器我用的是 ST-Link V2二十幾塊錢一個(gè)J-Link 的替代品完全夠用。這里有個(gè)很容易忽略的問(wèn)題如果用 ST-LinkProgrammer 界面有時(shí)會(huì)提示固件太舊需要升級(jí) ST-Link 的驅(qū)動(dòng)和固件可以在 ST 官網(wǎng)下載 ST-Link Utility 或者 CubeProgrammer 來(lái)刷。3.2 DHT11 單總線時(shí)序驅(qū)動(dòng)與延時(shí)問(wèn)題DHT11 用的是單總線協(xié)議也就是一根線既做數(shù)據(jù)輸入又做數(shù)據(jù)輸出時(shí)序要求比較嚴(yán)格。整個(gè)讀取過(guò)程分幾個(gè)階段主機(jī)發(fā)送起始信號(hào)GPIO 拉低至少 18ms然后拉高 20~40us釋放總線傳感器響應(yīng)DHT11 檢測(cè)到起始信號(hào)后拉低 80us 表示應(yīng)答再拉高 80us 準(zhǔn)備發(fā)送數(shù)據(jù)數(shù)據(jù)位傳輸每位數(shù)據(jù)由 50us 低電平加上高電平組成高電平持續(xù) 26~28us 代表數(shù)據(jù) 0高電平持續(xù) 70us 代表數(shù)據(jù) 1完整數(shù)據(jù)40 位數(shù)據(jù)包依次是濕度整數(shù)、濕度小數(shù)、溫度整數(shù)、溫度小數(shù)、校驗(yàn)和校驗(yàn)和等于前四個(gè)字節(jié)相加的低 8 位??雌饋?lái)簡(jiǎn)單實(shí)際寫代碼時(shí)最大的坑在延時(shí)函數(shù)的準(zhǔn)確性上。很多人的工程都是直接從正點(diǎn)原子或者野火的模板改的延時(shí)函數(shù)用的是 SysTick 中斷方式。如果你用了 HAL_Delay()它本身是基于 SysTick 的但如果在中斷服務(wù)函數(shù)里誤調(diào)用了延時(shí)或者 SysTick 優(yōu)先級(jí)設(shè)置不當(dāng)整個(gè)系統(tǒng)就卡死了這就是網(wǎng)上熱門的“stm32延時(shí)函數(shù)delay卡死”問(wèn)題。我給 DHT11 的讀寫單獨(dú)寫了一個(gè)微秒級(jí)延時(shí)函數(shù)用定時(shí)器 TIM 實(shí)現(xiàn)不讓它依賴 SysTick。具體是初始化 TIM2設(shè)置為 1us 計(jì)數(shù)一次每次延時(shí)就是把計(jì)數(shù)器清零然后等待到達(dá)目標(biāo)值。這樣做的好處是微秒延時(shí)不占用 CPU 中斷資源也不會(huì)和 HAL 庫(kù)的時(shí)鐘節(jié)拍沖突。還有一個(gè)細(xì)節(jié)很多人不注意DHT11 的讀取間隔必須大于 1 秒。因?yàn)?DHT11 本身的采樣周期是 1 秒一次你如果頻繁讀取它會(huì)一直返回上一次的數(shù)據(jù)或者直接不響應(yīng)。我是在主循環(huán)里用了一個(gè) 2 秒的軟件定時(shí)器每 2 秒采樣一次溫濕度這個(gè)頻率對(duì)大棚環(huán)境來(lái)說(shuō)完全足夠。3.3 滯回控制邏輯的代碼實(shí)現(xiàn)控制邏輯說(shuō)簡(jiǎn)單也簡(jiǎn)單說(shuō)復(fù)雜也復(fù)雜。我用的是一個(gè)有限狀態(tài)機(jī)每個(gè)執(zhí)行設(shè)備都有兩個(gè)狀態(tài)開(kāi)啟和關(guān)閉。以排風(fēng)扇為例設(shè)定兩個(gè)溫度閾值TEMP_ON 和 TEMP_OFFTEMP_ON TEMP_OFF。每次采樣后判斷如果當(dāng)前狀態(tài)是關(guān)閉且當(dāng)前溫度 TEMP_ON則開(kāi)啟風(fēng)扇如果當(dāng)前狀態(tài)是開(kāi)啟且當(dāng)前溫度 TEMP_OFF則關(guān)閉風(fēng)扇其他情況保持原狀態(tài)不變。C 語(yǔ)言核心部分大概長(zhǎng)這樣typedef enum {FAN_OFF, FAN_ON} FanState_t; FanState_t fan_state FAN_OFF; void Fan_Control(float temp) { switch(fan_state) { case FAN_OFF: if(temp TEMP_ON_THRESHOLD) // 上限 35.0 度 { Relay_Set(RELAY_FAN, LOW); // 低電平觸發(fā)LOW 表示吸合 fan_state FAN_ON; } break; case FAN_ON: if(temp TEMP_OFF_THRESHOLD) // 下限 32.0 度 { Relay_Set(RELAY_FAN, HIGH); // 釋放 fan_state FAN_OFF; } break; default: fan_state FAN_OFF; break; } }用狀態(tài)機(jī)的寫法比直接 if-else 清晰很多而且不會(huì)出現(xiàn)“狀態(tài)矛盾”的問(wèn)題。比如你在 if 里開(kāi)了風(fēng)扇else 里關(guān)了風(fēng)扇一旦溫度在閾值邊界跳動(dòng)程序容易在同一個(gè)循環(huán)里又開(kāi)又關(guān)。狀態(tài)機(jī)天然避免了這個(gè)問(wèn)題。類似的邏輯我復(fù)制到了加濕器、加熱器上只是閾值和傳感器類型不同。加熱和降溫的閾值需要冗余設(shè)計(jì)避免風(fēng)扇和加熱器同時(shí)動(dòng)作互相打架。濕度控制的邏輯同理。我設(shè)定的參數(shù)是濕度低于 40%RH 啟動(dòng)加濕器濕度升到 55%RH 以上關(guān)閉濕度高于 75%RH 啟動(dòng)排風(fēng)機(jī)排濕降到 65%RH 以下關(guān)閉。這里需要注意同一個(gè)風(fēng)扇可能同時(shí)承擔(dān)降溫和排濕兩個(gè)任務(wù)如果兩個(gè)條件都滿足那也沒(méi)問(wèn)題開(kāi)就好了但控制邏輯要把這兩個(gè)條件做“或”運(yùn)算處理。3.4 OLED 顯示與按鍵參數(shù)設(shè)置OLED 顯示我用的是開(kāi)源的 u8g2 庫(kù)對(duì) SSD1306 支持非常好中文顯示也能搞定只是需要取?;蛘咄鈷熳謳?kù)。如果你用的是 HAL 庫(kù)u8g2 有對(duì)應(yīng)的移植例程照著改一下 I2C 接口的底層讀寫函數(shù)就行。我在屏幕上分了三個(gè)區(qū)域當(dāng)前溫度、當(dāng)前濕度、設(shè)備狀態(tài)。設(shè)備狀態(tài)這一行會(huì)用文字顯示“風(fēng)扇:開(kāi) 加濕:關(guān) 加熱:開(kāi)”這樣人在現(xiàn)場(chǎng)不用看設(shè)備就知道系統(tǒng)在干什么。按鍵的使用上我做了長(zhǎng)按和短按區(qū)分短按設(shè)置鍵進(jìn)入設(shè)置模式加/減鍵調(diào)整閾值長(zhǎng)按設(shè)置鍵退出并保存參數(shù)到 Flash。用 STM32 內(nèi)部 Flash 保存參數(shù)不需要外接 EEPROM。保存的時(shí)候要注意STM32 內(nèi)部 Flash 擦寫有壽命限制F103 大約是 1 萬(wàn)次別在設(shè)置界面里反復(fù)保存。我的策略是只有在退出設(shè)置模式并且參數(shù)確實(shí)變化時(shí)才寫入 Flash。另外Flash 寫入期間 CPU 會(huì)暫停執(zhí)行所以寫入前要確保沒(méi)有正在進(jìn)行的傳感器讀取操作不然時(shí)序就亂了。4. 調(diào)試實(shí)錄與常見(jiàn)問(wèn)題排查技巧4.1 下載程序失敗no stm32 target found做這個(gè)項(xiàng)目遇到最多的問(wèn)題就是 Keil 點(diǎn)擊下載時(shí)彈出 “Error: No STM32 target found! If your product embeds debug authentication, ...” 這個(gè)報(bào)錯(cuò)。第一次遇到的人很容易慌其實(shí)排查思路很固定。按我踩坑的順序來(lái)先查接線ST-Link 的 SWDIO 接 PA13SWCLK 接 PA14GND 必須共地3.3V 可以不用接如果用 USB 供電。很多人只接了 SWDIO、SWCLK、GND 三根線這樣是對(duì)的但如果你沒(méi)共地SWD 通信一定失敗。再檢查 ST-Link 驅(qū)動(dòng)是否安裝好設(shè)備管理器里應(yīng)該能看到 “STLink dongle” 或者類似的設(shè)備。要是設(shè)備管理器里顯示感嘆號(hào)右鍵更新驅(qū)動(dòng)即可。第三檢查 BOOT0 的狀態(tài)。如果 BOOT0 被拉高芯片進(jìn)入系統(tǒng)存儲(chǔ)器啟動(dòng)模式雖然 SWD 仍然可以訪問(wèn)但有時(shí)會(huì)下載異常。把 BOOT0 恢復(fù)到低電平再試。第四在 Keil 的 Settings 里把 SWD 的 Max Clock 從 4MHz 降到 1MHz 甚至更低有些長(zhǎng)線連接或者芯片供電不穩(wěn)會(huì)導(dǎo)致 SWD 通信時(shí)序錯(cuò)誤。最后還有一個(gè)冷門原因如果芯片之前被設(shè)置了讀保護(hù)RDP也會(huì)出現(xiàn)連接失敗。這時(shí)需要先用 CubeProgrammer 或者 ST-Link Utility 連接并進(jìn)行全擦除解除讀保護(hù)。遇到這個(gè)報(bào)錯(cuò)別急著懷疑芯片燒了大多數(shù)情況就是接線和驅(qū)動(dòng)的鍋。4.2 傳感器數(shù)據(jù)亂跳或者一直顯示 0DHT11 讀取失敗表現(xiàn)就是數(shù)據(jù)全為 0 或者溫度濕度跳變離譜。我在調(diào)試階段專門遇到過(guò)這個(gè)問(wèn)題排查下來(lái)原因有三類。第一類就是上拉電阻沒(méi)加前面說(shuō)了DATA 引腳必須要有上拉否則電平浮空讀什么都是亂的。第二類是引腳配置問(wèn)題我用的 GPIO 模式是開(kāi)漏輸出加外部上拉GPIO_MODE_OUTPUT_OD如果你是推挽輸出讀數(shù)據(jù)的時(shí)候可能出現(xiàn)問(wèn)題。第三類是最隱蔽的延時(shí)時(shí)間不準(zhǔn)。DHT11 時(shí)序里數(shù)據(jù) 0 和數(shù)據(jù) 1 的差別就在高電平持續(xù)時(shí)間上26~28us 是 070us 是 1。如果你的微秒延時(shí)函數(shù)算得不準(zhǔn)或者用了 ADC 等外設(shè)搶占 CPU讀到的位就可能是錯(cuò)的。我用邏輯分析儀抓了 DHT11 的數(shù)據(jù)線波形對(duì)比標(biāo)準(zhǔn)時(shí)序后發(fā)現(xiàn)問(wèn)題出在我的延時(shí)函數(shù)太“毛糙”實(shí)際延時(shí)比理論值多了將近 20us。后來(lái)我把微秒級(jí)延時(shí)改成 TIM 定時(shí)器的阻塞方式精度對(duì)齊之后讀取成功率從 70% 提升到 99% 以上。建議你手頭沒(méi)有邏輯分析儀的話用示波器看波形先把傳感器的響應(yīng)時(shí)序抓出來(lái)對(duì)一遍再寫驅(qū)動(dòng)能省很多事。4.3 繼電器一動(dòng)作單片機(jī)就復(fù)位或者 ADC 數(shù)據(jù)突變這個(gè)現(xiàn)象做繼電器控制幾乎必遇到原因是繼電器線圈吸合和斷開(kāi)的瞬間會(huì)產(chǎn)生比較大的電流突變和反向電動(dòng)勢(shì)通過(guò)電源線路傳導(dǎo)到單片機(jī)造成電源電壓跌落單片機(jī)直接復(fù)位或者程序跑飛。我的處理辦法是電源和布線上做文章。物理隔離繼電器模塊供電跟單片機(jī)供電分開(kāi)。我是用外部 5V 適配器給繼電器模塊供電而且繼電器模塊的 GND 和單片機(jī)的 GND 要連在一起作共地但中間加了磁珠阻隔高頻干擾。電工方面繼電器線圈兩端加續(xù)流二極管反并聯(lián)。如果用的是成品繼電器模塊這個(gè)二極管模塊上已經(jīng)有了不用額外加。軟件上我在控制繼電器的 GPIO 操作前加了一個(gè) 50ms 的延時(shí)讓電源電壓穩(wěn)定后再繼續(xù)下一步操作。另外繼電器狀態(tài)切換的代碼不要放在定時(shí)器中斷里執(zhí)行因?yàn)橹袛嗬镪P(guān)不掉外部干擾最好放到主循環(huán)的統(tǒng)一狀態(tài)處理函數(shù)里。有人會(huì)遇到“繼電器不吸合但繼電器模塊上的指示燈亮了”的情況這種一般是繼電器驅(qū)動(dòng)能力不足或者模塊供電電流不夠。檢查一下 5V 電源的電流輸出能力我一開(kāi)始用 USB 口取電一個(gè)繼電器沒(méi)問(wèn)題兩個(gè)繼電器同時(shí)吸合就掛了換了大電流適配器就好了。4.4 看門狗、死機(jī)與參數(shù)恢復(fù)設(shè)計(jì)最后這個(gè)建議是我在做完整系統(tǒng)后加的就是啟用獨(dú)立看門狗IWDG。大棚環(huán)境設(shè)備可能一跑就是幾天不重啟程序偶發(fā)卡死要能自動(dòng)恢復(fù)。STM32 的 IWDG 用法很簡(jiǎn)單用 LSI 低速內(nèi)部時(shí)鐘作為計(jì)數(shù)時(shí)鐘設(shè)定一個(gè)超時(shí)時(shí)間比如 2 秒。主循環(huán)每隔一段時(shí)間就“喂狗”一次也就是重載計(jì)數(shù)器。如果程序卡死主循環(huán)跑不到喂狗的地方IWDG 超時(shí)后就會(huì)自動(dòng)復(fù)位單片機(jī)讓系統(tǒng)重新開(kāi)始運(yùn)行。需要注意的是喂狗的位置要放在主循環(huán)的關(guān)鍵路徑上別放在中斷里。放在中斷里的話就算主循環(huán)卡死了中斷還能一直觸發(fā)喂狗看門狗就形同虛設(shè)了。我的喂狗位置是在一輪完整的“采集—控制—顯示”完成之后這樣只要這一輪流程跑完喂狗就執(zhí)行任何一環(huán)卡住都會(huì)觸發(fā)復(fù)位。參數(shù)恢復(fù)方面我用了 STM32F103 內(nèi)部 Flash 存儲(chǔ)閾值參數(shù)。每次開(kāi)機(jī)啟動(dòng)后程序先從指定 Flash 地址讀取參數(shù)檢查標(biāo)識(shí)位是否正確。如果標(biāo)識(shí)位不對(duì)說(shuō)明 Flash 里沒(méi)有有效數(shù)據(jù)比如首次燒錄程序就加載默認(rèn)參數(shù)。這樣的設(shè)計(jì)保證系統(tǒng)首次上電和參數(shù)被寫壞時(shí)都能正常運(yùn)行不會(huì)變成磚頭。從傳感器時(shí)序到繼電器干擾再到看門狗兜底整套系統(tǒng)做下來(lái)我最大的體會(huì)是這種“小而全”的項(xiàng)目真正的難度不在單個(gè)功能實(shí)現(xiàn)而在把所有模塊穩(wěn)定地拼在一起。每次只改一個(gè)變量、只動(dòng)一個(gè)模塊測(cè)完了再改下一個(gè)是最笨也是最有效的方法。做完這個(gè)系統(tǒng)之后后續(xù)想升級(jí)可以考慮加個(gè) ESP8266 模塊把數(shù)據(jù)上傳到云平臺(tái)或者換成 SHT30 傳感器提高精度再進(jìn)一步還可以把滯回控制改成 PID 控制讓環(huán)境調(diào)節(jié)更順滑。核心的采集、控制、顯示框架不變擴(kuò)展就輕松多了。本文還有配套的精品資源點(diǎn)擊獲取