系統(tǒng):嵌入式開(kāi)發(fā)全流程實(shí)戰(zhàn))
身邊的人聽(tīng)說(shuō)我花了幾周時(shí)間做了一個(gè)“基于STM32的智能鴿子馴養(yǎng)系統(tǒng)”時(shí)大多數(shù)人的第一反應(yīng)都是“給鴿子搞智能化是不是太閑了”。但真正養(yǎng)過(guò)鴿子、或者做過(guò)農(nóng)禽馴養(yǎng)的朋友應(yīng)該明白定時(shí)喂食、飲水管理、環(huán)境監(jiān)控、回歸訓(xùn)練這幾件事日常消耗的時(shí)間精力遠(yuǎn)比想象中大得多。尤其是一個(gè)人同時(shí)養(yǎng)幾十只鴿子的時(shí)候每天幾點(diǎn)喂、喂多少、水夠不夠、鴿舍溫度濕度是否合適、晚上要不要補(bǔ)光這些瑣事全靠人工盯很容易出錯(cuò)。這個(gè)項(xiàng)目就是用STM32做主控把喂食、飲水、環(huán)境監(jiān)測(cè)、遠(yuǎn)程狀態(tài)查看、回歸訓(xùn)練輔助這些功能整合到一起做成一套可以實(shí)際運(yùn)行的實(shí)物系統(tǒng)同時(shí)配套完整的電路設(shè)計(jì)和設(shè)計(jì)手冊(cè)既能日常使用也適合作為嵌入式方向的課程設(shè)計(jì)或畢業(yè)設(shè)計(jì)項(xiàng)目。這個(gè)項(xiàng)目我采用的是STM32F103C8T6作為核心控制器配合溫濕度傳感器、光照傳感器、空氣質(zhì)量傳感器、步進(jìn)電機(jī)投喂機(jī)構(gòu)、繼電器風(fēng)扇/照明控制以及ESP8266模塊實(shí)現(xiàn)遠(yuǎn)程狀態(tài)上報(bào)。整套系統(tǒng)從需求拆解、硬件選型、電路搭建到軟件邏輯、實(shí)物調(diào)試踩了不少坑也沉淀了一些比較實(shí)用的經(jīng)驗(yàn)。這篇文章就把整個(gè)項(xiàng)目從0到1的關(guān)鍵環(huán)節(jié)完整梳理一遍包括硬件選型的邏輯、核心電路設(shè)計(jì)、狀態(tài)機(jī)思路、常見(jiàn)調(diào)試問(wèn)題以及最后設(shè)計(jì)手冊(cè)該怎樣整理希望能給正在做類似嵌入式項(xiàng)目的朋友一些參考。1. 先看這個(gè)系統(tǒng)到底解決了什么問(wèn)題1.1 鴿子馴養(yǎng)場(chǎng)景的真實(shí)痛點(diǎn)很多嵌入式項(xiàng)目的問(wèn)題在于“為了智能而智能”但鴿子馴養(yǎng)這個(gè)場(chǎng)景其實(shí)有非常明確的實(shí)際需求。鴿子有比較強(qiáng)的生物節(jié)律喂食時(shí)間、光照時(shí)長(zhǎng)、飲水清潔度都會(huì)直接影響鴿群的健康狀態(tài)和歸巢訓(xùn)練的反饋效果。我自己實(shí)測(cè)下來(lái)的體會(huì)是鴿子對(duì)“準(zhǔn)時(shí)”這件事很敏感固定的投喂時(shí)間點(diǎn)能讓鴿子形成條件反射這對(duì)后續(xù)的回歸訓(xùn)練很有幫助。人工喂養(yǎng)最大的麻煩在于不可控出差、加班、睡過(guò)頭都會(huì)導(dǎo)致喂食時(shí)間混亂飲水器如果長(zhǎng)期不清潔、水位不足鴿子容易出現(xiàn)消化問(wèn)題夏天鴿舍溫度過(guò)高、通風(fēng)不良鴿子狀態(tài)會(huì)明顯變差。這些問(wèn)題單獨(dú)看都不嚴(yán)重但疊加在一起馴養(yǎng)質(zhì)量就上不去。智能系統(tǒng)的價(jià)值就是把這些重復(fù)性、監(jiān)測(cè)類的工作自動(dòng)化讓馴養(yǎng)者把精力放在訓(xùn)練和觀察上。1.2 系統(tǒng)整體架構(gòu)與功能拆解這套系統(tǒng)的功能可以拆成四類定時(shí)喂食通過(guò)步進(jìn)電機(jī)驅(qū)動(dòng)投喂機(jī)構(gòu)按設(shè)定時(shí)間投放定量飼料支持多時(shí)段配置。飲水管理通過(guò)液位傳感器監(jiān)測(cè)飲水槽水位低于閾值時(shí)觸發(fā)電磁閥補(bǔ)水同時(shí)定時(shí)啟動(dòng)排水清潔。環(huán)境監(jiān)測(cè)與調(diào)節(jié)采集鴿舍溫濕度、光照強(qiáng)度、空氣質(zhì)量指標(biāo)根據(jù)閾值自動(dòng)控制風(fēng)扇、照明和加熱設(shè)備。狀態(tài)顯示與遠(yuǎn)程查看本地用OLED屏顯示實(shí)時(shí)數(shù)據(jù)遠(yuǎn)程通過(guò)ESP8266模塊把數(shù)據(jù)上報(bào)到手機(jī)或電腦端。主控選STM32F103C8T6的原因很直接性能足夠用、外設(shè)接口豐富、資料生態(tài)完善、成本低。Cortex-M3內(nèi)核72MHz主頻跑這些控制邏輯綽綽有余片上自帶ADC、定時(shí)器、USART、I2C、SPI基本不需要擴(kuò)展芯片。而且Keil MDK的開(kāi)發(fā)流程、標(biāo)準(zhǔn)庫(kù)和HAL庫(kù)的資料都比較成熟項(xiàng)目過(guò)程中遇到問(wèn)題很容易查到解決方案。對(duì)比Arduino方案STM32在定時(shí)器精度、多任務(wù)時(shí)間片管理、外設(shè)控制粒度上都要強(qiáng)不少對(duì)比純51單片機(jī)方案STM32的硬件資源又充足得多后面想擴(kuò)展功能也不用換平臺(tái)。模糊測(cè)距、回歸訓(xùn)練計(jì)時(shí)這類擴(kuò)展功能STM32的定時(shí)器捕獲模式也能直接支持后面想加紅外感應(yīng)器記錄鴿子歸巢時(shí)間點(diǎn)硬件基礎(chǔ)都是現(xiàn)成的。2. 硬件選型與電路設(shè)計(jì)細(xì)節(jié)2.1 核心板與電源方案的選擇主控板我用的是最常見(jiàn)的STM32F103C8T6最小系統(tǒng)板板載8MHz晶振和復(fù)位電路USB轉(zhuǎn)串口芯片用的是CH340方便下載程序和調(diào)試打印。這類小系統(tǒng)板在項(xiàng)目原型階段非常合適缺點(diǎn)是插針連接在震動(dòng)環(huán)境里可能接觸不良所以實(shí)物定型后最好自己畫PCB、把關(guān)鍵模塊焊接牢靠。電源是整個(gè)系統(tǒng)里最容易踩坑的部分。整套系統(tǒng)涉及多個(gè)電壓域STM32核心板需要3.3V傳感器模塊大多支持3.3V至5V步進(jìn)電機(jī)驅(qū)動(dòng)板和繼電器需要5V風(fēng)扇和加熱設(shè)備則需要12V。如果直接用一路電源硬扛全部負(fù)載電機(jī)啟動(dòng)瞬間的壓降會(huì)導(dǎo)致單片機(jī)復(fù)位這是我調(diào)試時(shí)遇到的第一個(gè)“靈異現(xiàn)象”。后來(lái)采用的方案是外部12V開(kāi)關(guān)電源作為總輸入12V直接給風(fēng)扇和加熱繼電器回路供電通過(guò)LM2596降壓模塊將12V降至5V給步進(jìn)電機(jī)驅(qū)動(dòng)、傳感器、ESP8266供電再?gòu)?V經(jīng)過(guò)AMS1117穩(wěn)壓到3.3V給STM32核心板供電。這樣做的核心思路是把大電流負(fù)載和小信號(hào)電路從電源層面做隔離避免電機(jī)和繼電器動(dòng)作時(shí)拉低邏輯電路電壓。有一點(diǎn)特別提醒LM2596的輸出紋波比較大如果直接給傳感器和單片機(jī)供電ADC采集的數(shù)值會(huì)跳得厲害。我實(shí)測(cè)在輸出端并聯(lián)一個(gè)470uF電解電容和0.1uF瓷片電容后紋波改善非常明顯。AMS1117前級(jí)的5V輸入也要保證至少4.75V以上否則輸出3.3V會(huì)跌到3.2V以下DHT11這類傳感器就開(kāi)始出現(xiàn)偶發(fā)讀不到數(shù)據(jù)的現(xiàn)象。2.2 傳感器模塊電路與接口設(shè)計(jì)溫濕度傳感器選DHT11雖然精度一般但勝在便宜、接口簡(jiǎn)單、單總線協(xié)議好寫。要注意DHT11的數(shù)據(jù)引腳需要接一個(gè)4.7kΩ上拉電阻到3.3V否則時(shí)序經(jīng)常不穩(wěn)定。我最初直接用核心板內(nèi)部上拉結(jié)果讀取成功率只有七成左右加上外部上拉后基本穩(wěn)定。讀取時(shí)序上起始信號(hào)拉低至少18ms然后釋放并等待DHT11響應(yīng)這個(gè)延遲很重要很多人讀失敗就是因?yàn)槠鹗夹盘?hào)低電平時(shí)間不夠。光照傳感器用BH1750I2C接口代碼量小數(shù)據(jù)直接是勒克斯值不用換算。這個(gè)傳感器供電范圍是3.3V到5V建議統(tǒng)一用3.3V否則I2C電平不匹配容易出現(xiàn)死鎖??諝赓|(zhì)量監(jiān)測(cè)選MQ-135輸出是模擬量直接接STM32的ADC引腳。這個(gè)傳感器有個(gè)需要注意的地方上電初期傳感器內(nèi)部加熱絲需要預(yù)熱幾分鐘輸出電壓才會(huì)穩(wěn)定如果上電馬上讀ADC數(shù)據(jù)會(huì)偏大不少。另外MQ-135的靈敏度可以通過(guò)電路板上的電位器調(diào)節(jié)我習(xí)慣把基準(zhǔn)電壓調(diào)低一點(diǎn)這樣對(duì)空氣變化的響應(yīng)更明顯。液位監(jiān)測(cè)這里我沒(méi)有用復(fù)雜的液位傳感器而是用了一個(gè)簡(jiǎn)易的浮球式液位開(kāi)關(guān)輸出數(shù)字量接GPIO讀高低電平即可。水位低于設(shè)定位置時(shí)開(kāi)關(guān)斷開(kāi)系統(tǒng)就觸發(fā)電磁閥補(bǔ)水。這個(gè)選型的考慮是可靠性優(yōu)先液位檢測(cè)不需要連續(xù)數(shù)據(jù)只需要一個(gè)“夠不夠”的狀態(tài)數(shù)字開(kāi)關(guān)比模擬傳感器穩(wěn)定得多。2.3 執(zhí)行器驅(qū)動(dòng)電路與保護(hù)細(xì)節(jié)步進(jìn)電機(jī)投喂機(jī)構(gòu)是這套系統(tǒng)最有“機(jī)械感”的部分。我用的28BYJ-48步進(jìn)電機(jī)配合ULN2003驅(qū)動(dòng)板轉(zhuǎn)速低、扭矩夠適合驅(qū)動(dòng)小尺寸的螺旋出料器。ULN2003內(nèi)部集成達(dá)林頓管和續(xù)流二極管直接接電機(jī)就能用但要注意驅(qū)動(dòng)板的GND一定要和STM32共地否則控制信號(hào)沒(méi)有參考電平電機(jī)不會(huì)動(dòng)。供電方面ULN2003板子標(biāo)稱5V輸入但實(shí)測(cè)在5V下扭矩偏小出料口堵住的時(shí)候容易丟步。后來(lái)我把電源提到5.5V左右扭矩明顯改善但不能再高了驅(qū)動(dòng)板會(huì)過(guò)熱。繼電器控制風(fēng)扇和加熱設(shè)備時(shí)不能直接把繼電器的線圈引腳接到GPIO上必須用三極管或ULN2003做驅(qū)動(dòng)同時(shí)在繼電器線圈兩端反向并聯(lián)一個(gè)續(xù)流二極管否則斷電瞬間產(chǎn)生的反向電動(dòng)勢(shì)會(huì)擊穿單片機(jī)引腳。我用的是NPN三極管S8050加1N4007二極管的經(jīng)典電路實(shí)測(cè)很穩(wěn)。還有一點(diǎn)繼電器觸點(diǎn)接入220V設(shè)備時(shí)一定要注意強(qiáng)弱電隔離和絕緣處理強(qiáng)電部分最好單獨(dú)用接線端子固定不能用杜邦線直接搭這是安全底線。2.4 電路設(shè)計(jì)中容易被忽略的細(xì)節(jié)按鍵輸入必須做軟件消抖我用的方法是在定時(shí)器中斷里每隔10ms掃描一次按鍵狀態(tài)連續(xù)兩次讀到同一狀態(tài)才確認(rèn)有效實(shí)測(cè)沒(méi)有誤觸發(fā)。STM32的BOOT0引腳要接10kΩ下拉電阻到GND保證正常從Flash啟動(dòng)否則偶爾上電會(huì)卡在Bootloader模式。傳感器和單片機(jī)的I2C引腳如果發(fā)生死鎖可以用一個(gè)GPIO模擬I2C方式讀取BH1750多十幾行代碼但穩(wěn)定性更好。PCB設(shè)計(jì)時(shí)晶振要盡量靠近主控芯片走線短且不要與其他信號(hào)線并行否則時(shí)鐘抖動(dòng)會(huì)導(dǎo)致串口波特率偏差。每個(gè)模塊的電源引腳旁邊都要放一個(gè)0.1uF去耦電容這個(gè)習(xí)慣非常重要?jiǎng)e只在主電源處加電容高頻噪聲往往是局部引入的。3. 軟件設(shè)計(jì)從裸機(jī)邏輯到系統(tǒng)狀態(tài)機(jī)3.1 為什么用狀態(tài)機(jī)而不是順序執(zhí)行如果只是定時(shí)喂食、讀取傳感器順序執(zhí)行也能跑但一旦加入按鍵配置、遠(yuǎn)程指令、多路傳感器采集和繼電器控制順序執(zhí)行就會(huì)出現(xiàn)一個(gè)問(wèn)題某個(gè)傳感器讀取卡住了整個(gè)系統(tǒng)都在等電機(jī)和繼電器控制也跟著卡殼。DHT11的時(shí)序讀取本來(lái)就有阻塞風(fēng)險(xiǎn)我在初期版本里就遇到過(guò)“DHT11偶爾不響應(yīng)導(dǎo)致喂食時(shí)間點(diǎn)錯(cuò)過(guò)”的尷尬情況。后來(lái)我把整個(gè)軟件架構(gòu)改成“時(shí)間片輪詢狀態(tài)機(jī)”的模型。用一個(gè)1ms的SysTick定時(shí)器作為基準(zhǔn)時(shí)鐘在中斷里只做計(jì)數(shù)和標(biāo)志位置位不處理具體業(yè)務(wù)。主循環(huán)里按照設(shè)定的時(shí)間片去輪詢各個(gè)任務(wù)模塊每個(gè)模塊內(nèi)部是一個(gè)狀態(tài)機(jī)比如喂食模塊有“待機(jī)→啟動(dòng)電機(jī)→出料等待→停止出料→復(fù)位等待→回到待機(jī)”這幾個(gè)狀態(tài)只在特定狀態(tài)下根據(jù)當(dāng)前時(shí)間來(lái)判斷是否需要切換。這樣做的好處是任何一個(gè)模塊卡住最壞只是影響自身狀態(tài)不會(huì)拖垮整個(gè)系統(tǒng)。跑下來(lái)這套架構(gòu)非常穩(wěn)。三個(gè)定時(shí)任務(wù)環(huán)境采集、喂食控制、通信上報(bào)加一個(gè)主循環(huán)調(diào)度CPU占用率還不到三成后面想擴(kuò)展功能、加歸巢計(jì)時(shí)任務(wù)都只需要新增一個(gè)模塊和對(duì)應(yīng)的時(shí)間片槽位。3.2 定時(shí)器配置與時(shí)間基準(zhǔn)設(shè)計(jì)STM32的定時(shí)器資源在鴿子馴養(yǎng)系統(tǒng)里用得很充分。SysTick提供1ms系統(tǒng)基準(zhǔn)時(shí)鐘TIM2做喂食計(jì)時(shí)的長(zhǎng)周期定時(shí)TIM3的PWM通道用來(lái)控制舵機(jī)如果后續(xù)做自動(dòng)門TIM4的編碼器接口模式可以用來(lái)采集外部計(jì)數(shù)信號(hào)。這里我想詳細(xì)說(shuō)一說(shuō)定時(shí)器配置的幾個(gè)關(guān)鍵點(diǎn)。時(shí)鐘樹(shù)是很多人第一次用STM32時(shí)最容易懵的地方。整個(gè)芯片的時(shí)鐘來(lái)自HSE外部8MHz晶振經(jīng)過(guò)PLL倍頻到72MHz作為系統(tǒng)主頻然后AHB分頻給各總線APB1外設(shè)時(shí)鐘最大36MHzAPB2外設(shè)時(shí)鐘最大72MHz。定時(shí)器掛在哪條總線上決定了它的時(shí)鐘源頻率。TIM2掛在APB1上如果APB1預(yù)分頻設(shè)為1定時(shí)器時(shí)鐘是72MHz但如果你把APB1預(yù)分頻設(shè)為2定時(shí)器時(shí)鐘會(huì)自動(dòng)變成72MHz定時(shí)器不是直接用APB1時(shí)鐘而是APB1的兩倍。這個(gè)細(xì)節(jié)搞不清楚定時(shí)時(shí)間就會(huì)偏差一倍。喂食計(jì)時(shí)的定時(shí)器配置我這樣設(shè)置TIM2的預(yù)分頻PSC設(shè)為7199自動(dòng)重裝值A(chǔ)RR設(shè)為10000這樣定時(shí)器時(shí)鐘72MHz經(jīng)過(guò)7200分頻后得到10kHz的計(jì)數(shù)頻率再計(jì)滿10000次就是1秒。為什么不用TIM2做1ms節(jié)拍因?yàn)镾ysTick已經(jīng)做了1ms基礎(chǔ)TIM2留給更長(zhǎng)的喂食計(jì)時(shí)避免高頻中斷擠占CPU。3.3 傳感器采集關(guān)鍵代碼和濾波思路DHT11的讀取是最容易翻車的部分。這里給出一個(gè)穩(wěn)定讀取的核心流程代碼里最關(guān)鍵的是精確延時(shí)我建議直接用DWT計(jì)數(shù)器做微秒延時(shí)不要用簡(jiǎn)單的循環(huán)延時(shí)因?yàn)榫幾g器優(yōu)化級(jí)別不同會(huì)導(dǎo)致循環(huán)延時(shí)偏差很大。// DHT11讀取函數(shù)返回溫濕度失敗返回-1 int8_t DHT11_Read(uint8_t *temp, uint8_t *humi) { uint8_t data[5] {0}; // 拉低起始信號(hào)必須大于18ms GPIO_ResetBits(DHT11_GPIO, DHT11_PIN); Delay_Ms(20); // 釋放總線拉高 GPIO_SetBits(DHT11_GPIO, DHT11_PIN); Delay_Us(30); // 延時(shí)30us后切換為輸入模式 GPIO_Mode_Input(); // 等待DHT11拉低響應(yīng)信號(hào)超時(shí)50us if (GPIO_ReadInputDataBit(DHT11_GPIO, DHT11_PIN) SET) { return -1; } while (GPIO_ReadInputDataBit(DHT11_GPIO, DHT11_PIN) RESET); while (GPIO_ReadInputDataBit(DHT11_GPIO, DHT11_PIN) SET); // 讀取40位數(shù)據(jù)每位數(shù)據(jù)由低電平高電平組成高電平長(zhǎng)度決定0/1 for (int i 0; i 40; i) { while (GPIO_ReadInputDataBit(DHT11_GPIO, DHT11_PIN) RESET); Delay_Us(40); if (GPIO_ReadInputDataBit(DHT11_GPIO, DHT11_PIN) SET) { data[i / 8] | (0x80 (i % 8)); } while (GPIO_ReadInputDataBit(DHT11_GPIO, DHT11_PIN) SET); } // 校驗(yàn)位驗(yàn)證 if (data[0] data[1] data[2] data[3] data[4]) { *humi data[0]; *temp data[2]; return 0; } return -1; }這里有幾個(gè)點(diǎn)展開(kāi)說(shuō)一下。DHT11的每一位數(shù)據(jù)都是先低電平50us然后高電平持續(xù)26us到28us表示邏輯0高電平持續(xù)70us表示邏輯1。代碼里用延時(shí)40us后讀引腳狀態(tài)來(lái)判斷0或1這個(gè)方法是網(wǎng)上最通用的方案但要注意不同批次的DHT11時(shí)序有輕微差異如果讀取不穩(wěn)定可以把延時(shí)微調(diào)到35us到45us之間試試。ADC采集MQ-135的空氣質(zhì)量時(shí)我用的是多次采樣加滑動(dòng)平均的濾波方式。連續(xù)采樣10次去掉最大值和最小值剩下的8次取平均。這樣能有效抑制傳感器輸出信號(hào)上的隨機(jī)噪聲和偶發(fā)尖峰。ADC的采樣時(shí)間也要注意STM32的ADC采樣時(shí)間可以配置為1.5到239.5個(gè)周期我實(shí)測(cè)采樣時(shí)間太短會(huì)導(dǎo)致數(shù)值波動(dòng)大建議配置為55.5個(gè)周期以上轉(zhuǎn)換結(jié)果更穩(wěn)定。3.4 電機(jī)控制和投喂機(jī)構(gòu)的機(jī)械邏輯投喂機(jī)構(gòu)用步進(jìn)電機(jī)控制這里有一個(gè)很多人容易忽略的點(diǎn)步進(jìn)電機(jī)要跑出穩(wěn)定的速度曲線最好做加減速控制否則從靜止直接跳到高速電機(jī)大概率丟步。28BYJ-48是減速步進(jìn)電機(jī)減速比1/64內(nèi)部步距角5.625度所以走一圈需要64 × 64 4096個(gè)半步脈沖。我用的是四相八拍驅(qū)動(dòng)方式每個(gè)脈沖轉(zhuǎn)動(dòng)5.625度的半個(gè)步距角。投喂的邏輯是這樣設(shè)計(jì)的設(shè)定一個(gè)“出料圈數(shù)”參數(shù)比如喂食時(shí)電機(jī)正向旋轉(zhuǎn)300個(gè)半步步進(jìn)螺旋出料器把飼料推出到食槽里投喂結(jié)束后電機(jī)反向旋轉(zhuǎn)20步把出料口殘存的飼料帶回去防止漏料。這個(gè)反向回帶的小細(xì)節(jié)是在實(shí)際使用過(guò)程中發(fā)現(xiàn)的不加的話每次喂完食槽附近都散落不少飼料。// 步進(jìn)電機(jī)四相八拍控制 static const uint8_t STEP_SEQUENCE[8] { 0x01, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x09 }; void Stepper_Control(int32_t steps, uint8_t direction, uint16_t delay_us) { uint8_t idx direction ? 0 : 7; uint8_t remain steps % 8; for (int32_t i 0; i steps; i) { GPIO_Write(GPIOA, STEP_SEQUENCE[idx], 0x0F); // 只寫低四位 Delay_Us(delay_us); idx direction ? (idx 1) % 8 : (idx 7) % 8; } // 如果步數(shù)不是8的倍數(shù)補(bǔ)一個(gè)停止?fàn)顟B(tài) if (remain ! 0) { GPIO_Write(GPIOA, 0x00, 0x0F); } }電機(jī)驅(qū)動(dòng)的節(jié)奏通過(guò)delay_us參數(shù)控制。啟動(dòng)階段從2000us開(kāi)始逐漸減小到800us完成階段再?gòu)?00us逐漸增大到2000us。這個(gè)加減速段可以提前計(jì)算好步數(shù)的比例比如總步數(shù)的20%做加速、60%勻速、20%減速實(shí)測(cè)出料量非常均勻。3.5 遠(yuǎn)程通信模塊的數(shù)據(jù)上報(bào)設(shè)計(jì)ESP8266做遠(yuǎn)程狀態(tài)上報(bào)是最繞的一段路。我用的方案是ESP8266作為WiFi模塊透?jìng)鲾?shù)據(jù)到MQTT服務(wù)器STM32通過(guò)串口發(fā)送AT指令和JSON格式的數(shù)據(jù)。這套方案成熟的點(diǎn)在于沒(méi)有太多私有協(xié)議要自己寫公共MQTT服務(wù)器的接入也方便手機(jī)上用現(xiàn)成的MQTT客戶端App就能訂閱查看。初始化流程大概是串口設(shè)置115200波特率→AT指令測(cè)試模塊應(yīng)答→配置WiFi模式→連接路由器→建立TCP連接到MQTT服務(wù)器→設(shè)備認(rèn)證→訂閱和發(fā)布消息。這里每一步都要等模塊返回OK或者響應(yīng)內(nèi)容再執(zhí)行下一步不能盲目往下發(fā)指令不然模塊響應(yīng)不過(guò)來(lái)狀態(tài)直接亂掉。數(shù)據(jù)上報(bào)的JSON格式我這樣定義{ device: pigeon_01, temp: 23.5, humi: 62, light: 320, air: 58, feed_count: 3, water_level: 1, timestamp: 1698600000 }有一個(gè)非常實(shí)用的經(jīng)驗(yàn)ESP8266模塊的供電要單獨(dú)用穩(wěn)壓后的5V轉(zhuǎn)3.3V電路最好加一個(gè)大一點(diǎn)的儲(chǔ)能電容。因?yàn)閃iFi模塊在發(fā)送數(shù)據(jù)瞬間電流會(huì)突然拉高到300mA左右如果和STM32共用同一個(gè)3.3V穩(wěn)壓源電壓跌落會(huì)導(dǎo)致單片機(jī)復(fù)位。我實(shí)際測(cè)試下來(lái)在ESP8266的3.3V電源引腳并聯(lián)一個(gè)470uF電解電容發(fā)送瞬間的電壓跌落能控制在0.1V以內(nèi)。4. 實(shí)操調(diào)試記錄與問(wèn)題排查4.1 常見(jiàn)問(wèn)題速查表以下是我在做這套系統(tǒng)過(guò)程中實(shí)際遇到并解決的問(wèn)題整理成表格方便大家對(duì)照排查?,F(xiàn)象可能原因排查與解決單片機(jī)反復(fù)復(fù)位電源壓降大電機(jī)或繼電器啟動(dòng)瞬間拉低電壓大電流負(fù)載獨(dú)立供電取消一二級(jí)電源之間的強(qiáng)藕合加儲(chǔ)能電容DHT11讀取失敗起始信號(hào)低電平時(shí)間不夠、上拉電阻缺失、時(shí)序延時(shí)不準(zhǔn)用示波器看數(shù)據(jù)引腳波形起始信號(hào)拉低超過(guò)18ms加4.7k上拉步進(jìn)電機(jī)丟步啟動(dòng)速度過(guò)快、電源電流不足、負(fù)載過(guò)重加減速控制用5.5V電源檢查ULN2003是否過(guò)熱OLED顯示花屏I2C引腳電平不匹配或接線過(guò)長(zhǎng)縮短杜邦線距離降低I2C時(shí)鐘頻率到100kHz以下確認(rèn)共地ESP8266連接不上路由器供電不足、串口波特率不匹配、AT指令順序錯(cuò)誤單獨(dú)供電加儲(chǔ)能電容用115200或9600逐檔匹配按模塊手冊(cè)逐條AT指令測(cè)試ADC數(shù)值跳變嚴(yán)重采樣時(shí)間短、電源噪聲、信號(hào)線受到干擾采樣時(shí)間設(shè)置55.5周期以上滑動(dòng)平均濾波信號(hào)線用雙絞線并遠(yuǎn)離電機(jī)電源線繼電器頻繁誤動(dòng)作控制信號(hào)受到電機(jī)換向瞬間的電磁干擾在繼電器控制端加RC濾波繼電器驅(qū)動(dòng)信號(hào)用屏蔽線遠(yuǎn)離電機(jī)走線上位機(jī)顯示時(shí)間不更新RTC芯片沒(méi)走時(shí)或代碼里時(shí)間戳更新邏輯錯(cuò)誤檢查RTC引腳焊接用串口打印時(shí)間戳定位更新位置4.2 調(diào)試過(guò)程中的幾個(gè)關(guān)鍵心得第一個(gè)心得是串口打印永遠(yuǎn)是最好的調(diào)試工具。我在代碼里預(yù)留了一個(gè)調(diào)試串口把所有關(guān)鍵狀態(tài)變量通過(guò)USART1打印到電腦串口助手包括當(dāng)前狀態(tài)機(jī)狀態(tài)、傳感器原始值、時(shí)間戳、電機(jī)執(zhí)行步數(shù)。很多問(wèn)題不需要猜打印一開(kāi)就清楚了。比如DHT11讀取不穩(wěn)定我用串口把每次讀取的時(shí)間和結(jié)果打出來(lái)很快就發(fā)現(xiàn)數(shù)據(jù)總線被外部干擾拉低解決思路一下就清晰了。第二個(gè)心得是模塊化通電千萬(wàn)不要把整套系統(tǒng)一次性全部接上電。我的調(diào)試步驟是先單獨(dú)測(cè)試STM32最小系統(tǒng)點(diǎn)亮LED然后接OLED顯示確認(rèn)I2C通信正常再逐個(gè)接傳感器每個(gè)傳感器都寫?yīng)毩⒌臏y(cè)試代碼驗(yàn)證讀數(shù)最后再接電機(jī)和繼電器。每接一個(gè)模塊就測(cè)試一次出了故障能立刻鎖定問(wèn)題范圍。如果一上來(lái)就全部模塊接電一旦出問(wèn)題排查范圍是全系統(tǒng)效率非常低。第三個(gè)心得是關(guān)于示波器的使用。很多人做嵌入式項(xiàng)目習(xí)慣全靠萬(wàn)用表量電壓但排查時(shí)序類問(wèn)題比如DHT11波形、ESP8266發(fā)送波形時(shí)示波器的價(jià)值無(wú)可替代。我遇到過(guò)ESP8266配置正確但無(wú)法連網(wǎng)的情況用示波器一看TX引腳波形發(fā)現(xiàn)波特率配置和模塊實(shí)際工作波特率差了3%導(dǎo)致數(shù)據(jù)全是亂碼。這種問(wèn)題靠萬(wàn)用表根本查不出來(lái)。4.3 設(shè)計(jì)手冊(cè)的整理思路與答辯要點(diǎn)這個(gè)項(xiàng)目既然配套了設(shè)計(jì)手冊(cè)就說(shuō)明它有課程設(shè)計(jì)或畢業(yè)設(shè)計(jì)的用途。設(shè)計(jì)手冊(cè)的整理和寫技術(shù)博客完全不一樣它的用戶是評(píng)審老師或者技術(shù)人員關(guān)注的是要點(diǎn)明確、邏輯完整、可復(fù)現(xiàn)。我建議手冊(cè)結(jié)構(gòu)按照“需求分析→總體方案→硬件設(shè)計(jì)→軟件設(shè)計(jì)→系統(tǒng)測(cè)試→總結(jié)”來(lái)編排。硬件設(shè)計(jì)部分一定要放完整的系統(tǒng)框圖、電源樹(shù)、每個(gè)模塊的接口定義表。軟件設(shè)計(jì)部分不要貼大段代碼而是放程序流程圖和關(guān)鍵邏輯偽代碼評(píng)審老師更看重設(shè)計(jì)思路而不是代碼堆砌。系統(tǒng)測(cè)試部分要有一條完整的測(cè)試記錄表包括測(cè)試時(shí)間、測(cè)試項(xiàng)目、測(cè)試環(huán)境、測(cè)試結(jié)果、結(jié)論這比在紙上畫一堆電路圖更有說(shuō)服力。實(shí)物部分要有清晰的照片內(nèi)部走線和模塊布局一目了然。還有答辯時(shí)最容易被追問(wèn)的問(wèn)題是“為什么選這個(gè)芯片”和“系統(tǒng)的創(chuàng)新點(diǎn)在哪里”。我的建議是不要只說(shuō)“資料多、便宜”而是要結(jié)合場(chǎng)景來(lái)說(shuō)比如STM32F103C8T6的定時(shí)器資源能同時(shí)滿足多路傳感器的時(shí)間基準(zhǔn)和電機(jī)控制這是很多低成本單片機(jī)做不到的系統(tǒng)擴(kuò)展性強(qiáng)后續(xù)可以增加GPS模塊做遠(yuǎn)距離回歸訓(xùn)練記錄這些就能體現(xiàn)出設(shè)計(jì)的余量意識(shí)。5. 實(shí)物制作與后續(xù)擴(kuò)展方向5.1 從面包板到實(shí)物的經(jīng)驗(yàn)原型階段在面包板上搭電路很方便但要連跑幾天甚至幾周面包板的接觸不良問(wèn)題就特別折磨人。我做過(guò)一個(gè)統(tǒng)計(jì)系統(tǒng)運(yùn)行三天里出現(xiàn)的所有“莫名其妙”故障八成都是面包板氧化導(dǎo)致的接觸問(wèn)題。所以驗(yàn)證完成后我建議直接把核心電路焊接到洞洞板或者自繪PCB上。自繪PCB這個(gè)環(huán)節(jié)有個(gè)小建議第一版不要追求體積小、布局密優(yōu)先保證走線清晰、模塊接口方便連接。鴿子馴養(yǎng)系統(tǒng)里面涉及220V強(qiáng)電繼電器控制強(qiáng)電部分和弱電部分要有明確的間距隔離這是安全要求。另外PCB上各個(gè)模塊的排針接口間距要與傳感器模塊的物理尺寸匹配我第一版改板就是因?yàn)榕裴橀g距排得太密模塊插上去互相打架。機(jī)械結(jié)構(gòu)方面投喂機(jī)構(gòu)使用的是3D打印的螺旋出料器配合飲料瓶作為料倉(cāng)。這里有一個(gè)實(shí)際經(jīng)驗(yàn)螺旋出料器的外徑和出料口之間要留0.5mm到1mm的間隙間隙太小會(huì)卡飼料間隙太大則出料不均勻還會(huì)漏料。如果不用3D打印用注射器筒和軟軸同樣能做但需要把出料口的直徑和螺旋螺距匹配好。5.2 更進(jìn)一步的擴(kuò)展方向這套系統(tǒng)跑穩(wěn)定之后可以沿著幾個(gè)方向擴(kuò)展。第一是回歸訓(xùn)練計(jì)時(shí)功能在鴿舍入口裝一對(duì)紅外對(duì)射傳感器用STM32的定時(shí)器捕獲輸入功能記錄鴿子歸巢的精確時(shí)間點(diǎn)數(shù)據(jù)通過(guò)WiFi上報(bào)用于分析不同訓(xùn)練周期下鴿子的歸巢速度變化。第二是加攝像頭模塊做圖像識(shí)別判斷食槽剩余飼料量這個(gè)對(duì)STM32F103來(lái)說(shuō)有點(diǎn)吃力建議換用帶硬件加速的芯片或者用ESP32-CAM分擔(dān)圖像任務(wù)。第三是用SD卡模塊做本地?cái)?shù)據(jù)存儲(chǔ)把一段時(shí)間的環(huán)境數(shù)據(jù)全部記錄下來(lái)后續(xù)在電腦上做數(shù)據(jù)分析對(duì)馴養(yǎng)策略的優(yōu)化很有幫助。5.3 分享一個(gè)實(shí)用小技巧最后分享一個(gè)我在調(diào)試過(guò)程中覺(jué)得特別實(shí)用的小技巧給每個(gè)模塊的電源正極都串一個(gè)小撥動(dòng)開(kāi)關(guān)模塊調(diào)試的時(shí)候可以單獨(dú)斷電。系統(tǒng)里某個(gè)傳感器出現(xiàn)問(wèn)題的時(shí)候不需要拔線直接斷開(kāi)對(duì)應(yīng)開(kāi)關(guān)復(fù)位模塊對(duì)比排查快得多。這個(gè)方法看似簡(jiǎn)單但很多人一開(kāi)始都沒(méi)有做等項(xiàng)目復(fù)雜到一定程度就會(huì)想念這個(gè)設(shè)計(jì)。另外一個(gè)建議是代碼版本管理不要用桌面文件夾項(xiàng)目大了之后幾十個(gè)版本的工程文件堆在一起完全分不清。我的習(xí)慣是每完成一個(gè)功能模塊就復(fù)制一份工程命名為“版本號(hào)日期功能說(shuō)明”比如“v1.2_20250118_add_feed_timer”。跑測(cè)試的時(shí)候如果出了問(wèn)題能快速回退到上一個(gè)正常版本這個(gè)習(xí)慣幫我節(jié)省了大量排錯(cuò)時(shí)間。個(gè)人操作體會(huì)整套系統(tǒng)從方案設(shè)計(jì)到實(shí)物完成我最大的體會(huì)是嵌入式項(xiàng)目要盡量把功能邊界劃清楚先跑通主干再完善細(xì)節(jié)。最開(kāi)始我計(jì)劃把喂食、飲水、環(huán)境監(jiān)測(cè)、遠(yuǎn)程監(jiān)控、回歸記錄一次全部做完結(jié)果發(fā)現(xiàn)模塊越多聯(lián)調(diào)越復(fù)雜有時(shí)候一個(gè)傳感器的問(wèn)題能排查一整天。后來(lái)我調(diào)整策略先只做“定時(shí)喂食溫濕度顯示”兩個(gè)功能跑通整個(gè)系統(tǒng)框架再逐步添加新模塊。每一步新增功能都有明確的驗(yàn)證標(biāo)準(zhǔn)系統(tǒng)穩(wěn)定性一直在可控范圍內(nèi)。如果你也在做類似的STM32項(xiàng)目建議從一開(kāi)始就用狀態(tài)機(jī)思路組織邏輯哪怕簡(jiǎn)單功能也用這個(gè)消息框架后面擴(kuò)展起來(lái)會(huì)輕松非常多。另外力矩和電源這兩個(gè)細(xì)節(jié)是最容易被低估的也是項(xiàng)目后期最容易找上門的坑前期方案階段留足余量不會(huì)錯(cuò)。