戰(zhàn):智能輸液監(jiān)護(hù)調(diào)控系統(tǒng),PID控制與Proteus仿真全解析)
STM32項(xiàng)目實(shí)戰(zhàn)智能輸液監(jiān)護(hù)調(diào)控系統(tǒng)升級(jí)版代碼、原理圖與仿真全解析做嵌入式這些年經(jīng)手過(guò)不少醫(yī)療相關(guān)的項(xiàng)目但要說(shuō)最能體現(xiàn)“軟硬結(jié)合”魅力的還得是輸液監(jiān)護(hù)這塊。每次在醫(yī)院看到護(hù)士忙著調(diào)滴速、盯液位我就琢磨這活兒要是能交給單片機(jī)去干能省多少人力又能減少多少醫(yī)患糾紛。所以當(dāng)朋友把“智能輸液監(jiān)護(hù)調(diào)控系統(tǒng)升級(jí)版”這個(gè)項(xiàng)目丟給我的時(shí)候我挺興奮的這正好是STM32的典型應(yīng)用場(chǎng)景實(shí)時(shí)采集、閉環(huán)控制、異常報(bào)警一樣不缺非常適合拿來(lái)練手也具備共享給同行參考的價(jià)值。這篇文章就圍繞這個(gè)開(kāi)源項(xiàng)目把代碼、原理圖和仿真從頭到尾捋一遍。不是單純地貼代碼而是講清楚每一部分為什么這么設(shè)計(jì)實(shí)操的時(shí)候有哪些坑要注意以及怎么在Proteus里把整個(gè)系統(tǒng)跑起來(lái)。如果你是電子相關(guān)專業(yè)的學(xué)生或者剛轉(zhuǎn)嵌入式的工程師手里正好有一塊STM32F103C8T6的最小系統(tǒng)板那這篇文章就是給你準(zhǔn)備的。1. 內(nèi)容整體設(shè)計(jì)與思路拆解1.1 需求背后的核心矛盾輸液監(jiān)護(hù)到底要解決什么問(wèn)題醫(yī)院里輸液最怕兩件事一是滴速不準(zhǔn)藥液流得太快或太慢直接影響治療效果二是輸液完成時(shí)沒(méi)人發(fā)現(xiàn)導(dǎo)致回血甚至空氣進(jìn)入血管。傳統(tǒng)做法是護(hù)士手動(dòng)調(diào)夾子、估速度然后定時(shí)巡查效率低高峰期難免顧此失彼。智能輸液監(jiān)護(hù)系統(tǒng)要解決的就是這兩個(gè)痛點(diǎn)說(shuō)到底就是三個(gè)字測(cè)得準(zhǔn)、控得住、報(bào)得靈。升級(jí)版相比基礎(chǔ)版?zhèn)戎攸c(diǎn)在于“調(diào)控”和“聯(lián)動(dòng)”?;A(chǔ)版往往只做到監(jiān)測(cè)比如滴速異常就蜂鳴報(bào)警但最終調(diào)速度還得人工來(lái)。升級(jí)版引入了執(zhí)行機(jī)構(gòu)比如步進(jìn)電機(jī)或者比例閥通過(guò)PID或模糊控制算法自動(dòng)調(diào)節(jié)滴速再結(jié)合液位傳感器實(shí)現(xiàn)輸液結(jié)束時(shí)的自動(dòng)切斷和遠(yuǎn)程報(bào)警。這一套閉環(huán)下來(lái)才算真正配得上“智能”兩個(gè)字。從系統(tǒng)架構(gòu)上看整個(gè)工程可以拆成幾個(gè)邏輯模塊傳感采集滴速傳感器、液位傳感器、核心處理STM32微控制器、人機(jī)交互按鍵、OLED/LCD顯示、蜂鳴器、執(zhí)行機(jī)構(gòu)電機(jī)驅(qū)動(dòng)或電磁閥、通信模塊可選比如WiFi或藍(lán)牙用于上報(bào)給護(hù)士站。做原理圖和代碼時(shí)心里先要有這個(gè)模塊劃分否則很容易亂。1.2 芯片選型和方案取舍為什么是STM32F103C8T6先說(shuō)結(jié)論這個(gè)項(xiàng)目選STM32F103C8T6是性價(jià)比最高的方案沒(méi)有之一。為什么因?yàn)檫@塊芯片的定位就是“皮實(shí)耐用、外設(shè)豐富”。F103C8T6采用ARM Cortex-M3內(nèi)核主頻72MHz對(duì)于輸液監(jiān)控這種中低速實(shí)時(shí)系統(tǒng)來(lái)說(shuō)性能完全夠用。關(guān)鍵是它內(nèi)置了多個(gè)定時(shí)器、ADC模數(shù)轉(zhuǎn)換器、USART、I2C、SPI接口這意味著你不需要外掛一堆亂七八糟的芯片去實(shí)現(xiàn)PWM輸出、模擬量采集、串口通信一塊MCU全包了。具體到輸液系統(tǒng)里各個(gè)外設(shè)的用處如下定時(shí)器TIM產(chǎn)生PWM波形用來(lái)控制電機(jī)轉(zhuǎn)速或比例閥開(kāi)度。這是調(diào)速的核心。ADC采集壓力傳感器或光電傳感器的模擬信號(hào)用于檢測(cè)液滴或液位。外部中斷EXTI接滴速傳感器的脈沖輸出每次滴一滴藥觸發(fā)一次中斷程序在中斷里計(jì)數(shù)并計(jì)算滴速。I2C/SPI驅(qū)動(dòng)OLED顯示屏顯示實(shí)時(shí)滴速、輸液總量、報(bào)警狀態(tài)等信息。USART連接ESP8266等WiFi模塊把數(shù)據(jù)上傳到護(hù)士站的上位機(jī)或者云平臺(tái)。再說(shuō)Flash和RAM64KB的Flash、20KB的RAM對(duì)于這個(gè)項(xiàng)目來(lái)說(shuō)都是夠用的。如果選用更小容量的芯片比如F103C6代碼稍微復(fù)雜一點(diǎn)就可能爆F(xiàn)lash;如果直接上F407性能和成本都有富余但對(duì)這個(gè)場(chǎng)景有點(diǎn)殺雞用牛刀了。所以C8T6就是那個(gè)“剛剛好”的選擇。市面上的最小系統(tǒng)板也便宜十幾塊錢一片燒錄用ST-Link或者串口ISP都行DIY門檻非常低。如果不小心燒壞了引腳換一片也不心疼非常適合反復(fù)折騰。2. 核心細(xì)節(jié)解析與實(shí)操要點(diǎn)2.1 滴速檢測(cè)光電對(duì)管和外部中斷的配合滴速檢測(cè)是整個(gè)系統(tǒng)最核心的輸入信號(hào)。常用的方案有兩種一種是讓液滴自由滴落穿過(guò)紅外對(duì)管的檢測(cè)區(qū)液滴遮擋光線產(chǎn)生脈沖另一種是滴管外側(cè)貼電容式傳感器液滴經(jīng)過(guò)時(shí)電容值變化。后者精度高但成本也高DIY項(xiàng)目普遍用紅外對(duì)管方案。紅外對(duì)管的原理并不復(fù)雜發(fā)射管持續(xù)發(fā)射紅外光接收管根據(jù)接收到的光強(qiáng)輸出高低電平。當(dāng)沒(méi)有液滴經(jīng)過(guò)時(shí)接收管能接收到較強(qiáng)的紅外光輸出低電平當(dāng)液滴落下時(shí)光線被折射和遮擋接收管輸出高電平。把這個(gè)信號(hào)接到STM32的外部中斷引腳上設(shè)置上升沿或下降沿觸發(fā)每來(lái)一個(gè)脈沖就計(jì)一次數(shù)同時(shí)記錄時(shí)間戳。程序?qū)崿F(xiàn)上不建議在中斷服務(wù)函數(shù)里做太多算術(shù)運(yùn)算因?yàn)橹袛嗬锖臅r(shí)過(guò)長(zhǎng)會(huì)影響實(shí)時(shí)性。我的做法是在中斷里只做兩件事滴數(shù)加一記錄當(dāng)前時(shí)間戳。然后設(shè)置一個(gè)定時(shí)器中斷比如每500毫秒或1秒觸發(fā)一次在主循環(huán)或定時(shí)器中斷里去計(jì)算“最近1分鐘的平均滴速”。計(jì)算滴速有兩種常見(jiàn)算法簡(jiǎn)單計(jì)數(shù)法統(tǒng)計(jì)1秒內(nèi)中斷的次數(shù)乘以60得到“滴/分鐘”。這種方法實(shí)時(shí)性強(qiáng)但波動(dòng)大抗干擾能力差?;瑒?dòng)窗口法維護(hù)一個(gè)數(shù)組記錄最近若干滴的時(shí)間戳然后計(jì)算平均間隔。比如記錄最近10滴的時(shí)間戳用第10滴的時(shí)間減去第1滴的時(shí)間再除以9得到平均每滴間隔然后換算成滴速。這種方法更平滑也更接近真實(shí)值。我用的是第二種代碼大概長(zhǎng)這樣#define DROP_WINDOW_SIZE 10 volatile uint32_t drop_timestamps[DROP_WINDOW_SIZE]; volatile uint8_t drop_index 0; volatile uint8_t drop_count 0; void EXTI_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) ! RESET) { uint32_t now HAL_GetTick(); drop_timestamps[drop_index] now; drop_index (drop_index 1) % DROP_WINDOW_SIZE; if (drop_count DROP_WINDOW_SIZE) drop_count; EXTI_ClearITPendingBit(EXTI_Line0); } } float GetDropRate(void) { if (drop_count 2) return 0.0f; uint8_t last_index (drop_index DROP_WINDOW_SIZE - 1) % DROP_WINDOW_SIZE; uint8_t first_index (last_index DROP_WINDOW_SIZE - drop_count) % DROP_WINDOW_SIZE; uint32_t time_diff drop_timestamps[last_index] - drop_timestamps[first_index]; uint32_t drops_in_window drop_count - 1; float interval_ms (float)time_diff / drops_in_window; return 60000.0f / interval_ms; }注意判斷滴速傳感器是否正常工作可以看中斷頻率是否在一個(gè)合理范圍內(nèi)。正常輸液一般是每分鐘20到60滴如果中斷頻率為0說(shuō)明液滴沒(méi)在滴可能輸液管堵塞或者液體輸完了如果中斷頻率突然飆升可能是傳感器被干擾也可能輸液袋被意外抬高導(dǎo)致壓力突變。這些異常情況都應(yīng)該納入報(bào)警邏輯。2.2 調(diào)速執(zhí)行機(jī)構(gòu)從繼電器到步進(jìn)電機(jī)如果說(shuō)滴速傳感器是系統(tǒng)的“眼睛”那執(zhí)行機(jī)構(gòu)就是“手”。怎么根據(jù)偏差自動(dòng)調(diào)節(jié)滴速目前的方案大致有三個(gè)層級(jí)。最低端的是用電磁閥或夾管閥只能實(shí)現(xiàn)“開(kāi)/關(guān)”二態(tài)控制。滴速偏快就夾緊一點(diǎn)滴速偏慢就松開(kāi)一點(diǎn)但無(wú)法精確調(diào)節(jié)容易振蕩。中間檔次是比例閥通過(guò)PWM波控制閥門開(kāi)度占空比越高開(kāi)口越大流速越快。這種方案控制平滑但閥體成本偏高而且對(duì)PWM頻率有要求頻率太低閥門會(huì)抖動(dòng)頻率太高驅(qū)動(dòng)電路容易發(fā)熱。最高端的是步進(jìn)電機(jī)驅(qū)動(dòng)的蠕動(dòng)泵通過(guò)步進(jìn)電機(jī)轉(zhuǎn)子壓迫硅膠管用轉(zhuǎn)子的轉(zhuǎn)速來(lái)控制液流速度。這種方案控制精度最高對(duì)管路的壓迫是漸進(jìn)的也不容易造成管路損傷適合應(yīng)用在需要精準(zhǔn)輸液的患者身上。缺點(diǎn)是成本高電機(jī)驅(qū)動(dòng)要額外加步進(jìn)電機(jī)驅(qū)動(dòng)芯片如A4988或DRV8825控制邏輯也更復(fù)雜需要處理脈沖/方向信號(hào)??紤]到升級(jí)版的定位和DIY可復(fù)現(xiàn)性我推薦“直流電機(jī)光電編碼器”或“步進(jìn)電機(jī)定時(shí)器PWM”的組合。如果你剛上手直接從“PWM控制直流電機(jī)”開(kāi)始代碼最簡(jiǎn)單先把閉環(huán)跑通后面再換執(zhí)行器整個(gè)系統(tǒng)的控制邏輯框架是不用大改的。如果需要精確調(diào)節(jié)滴速那么控制周期不能太短否則電機(jī)會(huì)反復(fù)啟停。我在項(xiàng)目里把控制周期設(shè)為2秒每2秒讀一次滴速計(jì)算偏差然后輸出一次PWM調(diào)整。這樣電機(jī)動(dòng)作平緩系統(tǒng)穩(wěn)定性好很多。這里有一個(gè)非常容易踩的坑執(zhí)行機(jī)構(gòu)的響應(yīng)速度比傳感器慢得多。傳感器檢測(cè)到滴速變化幾乎是瞬時(shí)的但電機(jī)從當(dāng)前轉(zhuǎn)速變到目標(biāo)轉(zhuǎn)速需要幾百毫秒甚至更久。如果PID參數(shù)調(diào)得太大系統(tǒng)會(huì)振蕩得很厲害。我一開(kāi)始就吃了這個(gè)虧滴速在20到80滴/分鐘之間來(lái)回震蕩后來(lái)把比例系數(shù)調(diào)小、積分時(shí)間拉長(zhǎng)才穩(wěn)定下來(lái)。2.3 液位檢測(cè)與斷液報(bào)警紅外對(duì)管的一個(gè)巧妙應(yīng)用輸液結(jié)束的檢測(cè)原理上也不復(fù)雜。輸液管末端有一個(gè)莫非氏滴管當(dāng)液面低于一定位置時(shí)滴管內(nèi)部的液體會(huì)變少空間里只剩下空氣。利用一個(gè)紅外對(duì)管發(fā)射管朝滴管內(nèi)部發(fā)射紅外光接收管在另一側(cè)接收。有液體時(shí)液體對(duì)紅外光的散射和吸收比較明顯接收管接收到的光強(qiáng)變?nèi)鯖](méi)有液體時(shí)光線直接穿過(guò)空氣接收管收到的光強(qiáng)變大。于是通過(guò)判斷接收管輸出電壓的大小就能推斷液面是否過(guò)低。實(shí)際電路連接上可以將接收管輸出電壓經(jīng)過(guò)一個(gè)比較器比如LM393整形成數(shù)字信號(hào)送入STM32的GPIO也可以用ADC直接采集模擬電壓設(shè)置一個(gè)閾值來(lái)判斷。前者抗干擾能力強(qiáng)后者閾值可調(diào)各有優(yōu)點(diǎn)。我在項(xiàng)目里采用了ADC方式因?yàn)樵赑roteus里仿真時(shí)調(diào)節(jié)閾值比較方便可以很直觀地看到液位變化對(duì)應(yīng)的電壓變化。判斷邏輯如下連續(xù)讀取10次ADC值取平均值避免單次抖動(dòng)誤判。如果平均電壓超過(guò)“空管閾值”持續(xù)2秒以上就認(rèn)為輸液即將完成進(jìn)入報(bào)警狀態(tài)。報(bào)警狀態(tài)包括LCD顯示“輸液完成”蜂鳴器鳴叫同時(shí)可以通過(guò)通信模塊發(fā)送報(bào)警消息。注意一個(gè)細(xì)節(jié)不要用單次ADC結(jié)果直接判斷。實(shí)際環(huán)境中液滴下落時(shí)滴管內(nèi)的液面會(huì)有微小波動(dòng)單次采樣很容易誤判。而且液滴經(jīng)過(guò)紅外對(duì)管時(shí)也會(huì)造成一個(gè)瞬時(shí)遮擋如果那個(gè)時(shí)刻正好采樣數(shù)據(jù)會(huì)非常離譜。所以我在代碼里做了一個(gè)“連續(xù)N次采樣取平均”的濾波函數(shù)實(shí)測(cè)效果很好。2.4 人機(jī)交互OLED顯示與按鍵處理人機(jī)交互這一塊我用了一塊0.96寸的OLED屏幕分辨率128x64I2C接口。顯示內(nèi)容分為三個(gè)頁(yè)面主頁(yè)面顯示當(dāng)前滴速滴/分鐘、輸液總量毫升、剩余時(shí)間估計(jì)小時(shí):分鐘。設(shè)置頁(yè)面可以選擇目標(biāo)滴速范圍比如30~40滴/分鐘、報(bào)警開(kāi)關(guān)、輸液總量上限。狀態(tài)頁(yè)面顯示傳感器狀態(tài)滴速傳感器正常/異常、液位正常/低液位、通信模塊連接狀態(tài)。按鍵用了三個(gè)獨(dú)立按鍵模式切換鍵、加鍵、減鍵。按鍵處理時(shí)要注意抖動(dòng)問(wèn)題。最簡(jiǎn)單的辦法是按鍵中斷觸發(fā)后啟動(dòng)一個(gè)20ms的延遲再讀一次電平確認(rèn)這叫“軟件消抖”。不要在硬件上省略RC濾波電路的情況下直接跳過(guò)這一步否則你會(huì)被按鍵抖動(dòng)折磨到懷疑人生。另外I2C的OLED驅(qū)動(dòng)代碼很容易受時(shí)序影響如果屏幕不亮先檢查引腳是否定義正確再看初始化時(shí)序是否符合SSD1306的要求。很多網(wǎng)上代碼的初始化序列都是照抄的如果你的屏幕不是同一家模塊廠可能會(huì)因?yàn)樯想娧訒r(shí)不夠而初始化失敗。解決方法是上電后加一個(gè)50ms的延時(shí)再執(zhí)行初始化序列。3. 實(shí)操過(guò)程與核心環(huán)節(jié)實(shí)現(xiàn)3.1 工程搭建從Keil到HAL庫(kù)的配置細(xì)節(jié)開(kāi)發(fā)環(huán)境方面我用的還是Keil MDK版本V5以上。芯片支持包要裝好否則編譯直接報(bào)錯(cuò)“Device not found”。我比較推薦使用STM32CubeMX先初始化工程生成HAL庫(kù)代碼再手動(dòng)添加業(yè)務(wù)邏輯。好處是引腳分配和時(shí)鐘樹(shù)配置可視化不容易出錯(cuò)。在CubeMX里需要配置的項(xiàng)包括RCC因?yàn)橛昧送獠烤д?MHz在RCC設(shè)置里選擇“Crystal/Ceramic Resonator”。SYSDebug選擇“Serial Wire”否則燒錄后第一次可能把SWD引腳復(fù)用掉導(dǎo)致無(wú)法再次燒錄。GPIO滴速傳感器接PA0EXTI0液位ADC接PA1ADC1_IN1OLED接PB6I2C1_SCL、PB7I2C1_SDAPWM輸出接PA8TIM1_CH1蜂鳴器接PB10按鍵接PB12/PB13/PB14。TIM1配置為PWM輸出模式頻率設(shè)20kHz初始占空比50%。I2C1默認(rèn)配置速度為100kHz。ADC1開(kāi)啟通道1采樣時(shí)間為55.5周期連續(xù)轉(zhuǎn)換模式關(guān)閉。生成代碼后工程里會(huì)自動(dòng)生成main.c、stm32f1xx_hal_msp.c等文件。我習(xí)慣把傳感器處理、控制算法、顯示驅(qū)動(dòng)分別放到不同的源文件里比如drop_sensor.c、pump_control.c、oled_display.c。這樣后續(xù)維護(hù)、調(diào)試方便很多不會(huì)在一個(gè)幾百行的main.c里翻來(lái)翻去。3.2 Proteus仿真環(huán)境搭建元件選型和接線避坑指南Proteus仿真是這個(gè)項(xiàng)目的一大亮點(diǎn)。很多教程里只教畫(huà)原理圖不教你跑仿真結(jié)果初學(xué)者一上電代碼燒進(jìn)去沒(méi)反應(yīng)也不知道是不是原理圖有問(wèn)題。其實(shí)在Proteus里跑仿真只要選對(duì)元件、接對(duì)線效果和實(shí)物幾乎一致。需要的元件清單如下STM32F103C8T6Proteus庫(kù)里的型號(hào)名一般是STM32F103C8T6在“Microprocessor ICs”分類下。紅外對(duì)管用LED-RED和PHOTOTRANSISTOR替代實(shí)際效果近似。LM393比較器或者直接用ADC采樣。LM016L液晶可選或者直接用OLED的Proteus模型如果庫(kù)里有的話沒(méi)有就用LCD1602替代。直流電機(jī)MOTOR-DC配一個(gè)MOSFET或三極管驅(qū)動(dòng)必要時(shí)加續(xù)流二極管。電位器POT-HG模擬液位傳感器的電壓變化。接線時(shí)有幾個(gè)容易出問(wèn)題的地方共地所有模塊的地必須連到同一個(gè)網(wǎng)絡(luò)否則信號(hào)參考點(diǎn)不同數(shù)據(jù)完全亂掉。電機(jī)驅(qū)動(dòng)STM32 GPIO輸出能力有限不能直接驅(qū)動(dòng)直流電機(jī)必須經(jīng)過(guò)三極管如S8050或MOSFET如IRF5xx系列進(jìn)行電流放大。還要在電機(jī)兩端反向并聯(lián)一個(gè)二極管泄放關(guān)斷時(shí)的反向電動(dòng)勢(shì)否則很容易燒驅(qū)動(dòng)管。傳感器電源紅外對(duì)管的工作電流一般不超過(guò)20mA串聯(lián)一個(gè)1k電阻限流即可。Proteus里STM32的晶振雙擊芯片在屬性里設(shè)置外部晶振頻率為8MHz否則HAL庫(kù)的延時(shí)函數(shù)會(huì)嚴(yán)重不準(zhǔn)整個(gè)時(shí)序全亂。這個(gè)很多人栽過(guò)跟頭。仿真配置文件里還有一個(gè)啟動(dòng)選項(xiàng)要勾選“Enable COF file”并把編譯生成的.axf或.hex文件路徑填進(jìn)去。在Keil里輸出選項(xiàng)勾選“Create HEX File”編譯后就能在工程目錄下找到hex文件。然后把hex文件加載到STM32芯片中就能仿真了。3.3 控制算法落地的代碼示例位置式PID與限幅當(dāng)?shù)嗡倨畲嬖跁r(shí)怎么算PWM占空比我直接用了位置式PIDu(k) Kp * e(k) Ki * sum(e(i)) Kd * (e(k) - e(k-1))其中e(k)是目標(biāo)滴速與當(dāng)前滴速的偏差。u(k)是輸出的PWM占空比0~100%。但因?yàn)榈嗡俸蚉WM占空比之間并不是嚴(yán)格的線性關(guān)系所以PID參數(shù)的整定比較關(guān)鍵。我調(diào)出來(lái)的一組參數(shù)參考Kp 0.8Ki 0.1Kd 0.05控制周期 2000ms這組參數(shù)在仿真和實(shí)物上表現(xiàn)都還可以。但要注意不同輸液管、不同電機(jī)的特性差異挺大溫度也會(huì)影響藥液粘度所以最好在設(shè)置界面里開(kāi)放PID參數(shù)調(diào)節(jié)而不是寫(xiě)死在代碼里。或者干脆用模糊控制規(guī)則表根據(jù)滴速偏差和偏差變化率調(diào)整整PWM增量不依賴精確模型適應(yīng)性強(qiáng)一些。代碼實(shí)現(xiàn)簡(jiǎn)化版typedef struct { float Kp; float Ki; float Kd; float integral; float last_error; } Pid_t; float Pid_Update(Pid_t *pid, float target, float current) { float error target - current; pid-integral error; // 積分限幅防止積分飽和 if (pid-integral 100) pid-integral 100; if (pid-integral -100) pid-integral -100; float derivative error - pid-last_error; pid-last_error error; return pid-Kp * error pid-Ki * pid-integral pid-Kd * derivative; }使用的時(shí)候把返回值疊加到當(dāng)前的PWM占空比上并做0~100%的限幅。延時(shí)2秒后再進(jìn)行一次新的PID計(jì)算。這樣電機(jī)動(dòng)作很平滑不會(huì)出現(xiàn)突然滿轉(zhuǎn)的情況。3.4 主循環(huán)狀態(tài)機(jī)的設(shè)計(jì)別把所有邏輯塞進(jìn)中斷很多新手寫(xiě)這個(gè)項(xiàng)目時(shí)習(xí)慣把滴速檢測(cè)、液位檢測(cè)、顯示刷新全部塞進(jìn)定時(shí)器中斷里結(jié)果發(fā)現(xiàn)中斷一多程序就卡死串口也亂。正確的做法是設(shè)計(jì)一個(gè)狀態(tài)機(jī)主循環(huán)不停地按狀態(tài)順序處理任務(wù)中斷只負(fù)責(zé)標(biāo)記事件。我定義的狀態(tài)大致如下SYS_INIT初始化所有外設(shè)清空滴速計(jì)數(shù)。SYS_MONITOR正常監(jiān)控狀態(tài)滴速正常時(shí)只刷新顯示。SYS_ADJUST滴速偏差超過(guò)閾值時(shí)啟動(dòng)PID調(diào)節(jié)控制電機(jī)。SYS_ALARM液位過(guò)低、滴速異?;蛲ㄐ攀r(shí)進(jìn)入報(bào)警。SYS_PAUSE報(bào)警確認(rèn)后聽(tīng)到蜂鳴器停止等待手動(dòng)恢復(fù)。主循環(huán)的偽代碼while (1) { switch (state) { case SYS_MONITOR: current_rate GetDropRate(); if (current_rate min_rate || current_rate max_rate) { state SYS_ADJUST; } if (low_level_flag) { state SYS_ALARM; } break; case SYS_ADJUST: pwm_output Pid_Update(pid, target_rate, current_rate); Set_Motor_Duty(pwm_output); state SYS_MONITOR; break; case SYS_ALARM: Buzzer_On(); if (user_confirm) { Buzzer_Off(); state SYS_MONITOR; } break; } Oled_Refresh(); }這種狀態(tài)機(jī)的寫(xiě)法邏輯清晰后續(xù)要加“暫?!薄白o(hù)士呼叫”等功能只需要在狀態(tài)枚舉和switch里加分支就行維護(hù)成本很低。4. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄4.1 代碼燒錄失敗或調(diào)試器無(wú)法識(shí)別芯片這個(gè)應(yīng)該是ST-Link或者J-Link用戶最常碰到的問(wèn)題了。報(bào)錯(cuò)信息一般是Error: Flash Download failed - Cortex-M3或者是No STM32 target found!。排查順序檢查ST-Link與板子的接線SWDIO接PA13、SWCLK接PA14、GND接GND、3.3V接3.3V。接線錯(cuò)了什么都白搭。檢查CubeMX里是否把PA13/PA14配置成普通GPIO了如果配置成普通GPIO調(diào)試口就被禁用了芯片變成“磚頭”。解決方法是把BOOT0引腳拉高通過(guò)串口ISP模式擦除Flash再把Boot0拉低復(fù)位。檢查Keil的Debug設(shè)置Utilities選項(xiàng)卡下要選擇正確的Flash算法STM32F1系列的算法是STM32F10x Med-density Flash。檢查供電很多最小系統(tǒng)板用ST-Link的3.3V供電但ST-Link有些版本輸出的電流不夠板子帶OLED和傳感器后電壓跌落導(dǎo)致芯片工作異常。最好外接穩(wěn)定的3.3V電源。4.2 仿真時(shí)滴速數(shù)據(jù)跳變劇烈或始終為零這種情況十有八九是傳感器模型和信號(hào)調(diào)理電路的問(wèn)題。在Proteus里用LED和光敏三極管模擬滴速傳感器時(shí)光線變化可能不夠陡峭導(dǎo)致輸出信號(hào)毛刺很多。解決辦法是在光敏三極管的輸出端加一個(gè)RC濾波器比如10k電阻串聯(lián)100nF電容到地再進(jìn)比較器或ADC。還有Proteus仿真速度默認(rèn)是實(shí)時(shí)速度如果你的電腦性能一般動(dòng)態(tài)交互電機(jī)轉(zhuǎn)動(dòng)、液滴下落會(huì)拖慢整個(gè)仿真反饋到滴速計(jì)算上就會(huì)出現(xiàn)卡頓甚至歸零。這時(shí)候可以在“Debug”菜單里把仿真時(shí)間比例改成“10us per sec”這類更快的速度或者把滴速檢測(cè)窗口縮短到5滴而不是10滴減少計(jì)算時(shí)所需的樣本數(shù)。4.3 OLED長(zhǎng)時(shí)間運(yùn)行后閃爍或花屏這種現(xiàn)象在實(shí)物中更常見(jiàn)通常原因是I2C通信被中斷干擾。前面我把滴速傳感器信號(hào)接在EXTI外部中斷上滴速快的時(shí)候中斷頻率高主循環(huán)中被中斷打斷的I2C通信時(shí)序就可能錯(cuò)亂。解決思路有幾個(gè)降低滴速中斷的優(yōu)先級(jí)讓I2C中斷優(yōu)先級(jí)更高。在OLED數(shù)據(jù)傳輸過(guò)程中關(guān)閉滴速中斷傳輸完再開(kāi)啟。但要注意關(guān)閉時(shí)間不能太長(zhǎng)否則會(huì)漏掉滴速脈沖。OLED刷新頻率不要太高每500ms刷新一次就夠了頻繁刷寫(xiě)I2C總線壓力很大。我實(shí)際用的是第二種方案但只在發(fā)送整屏數(shù)據(jù)時(shí)關(guān)閉幾次中斷用__disable_irq()和__enable_irq()包住關(guān)鍵傳輸段實(shí)測(cè)穩(wěn)定多了。4.4 液位傳感器誤報(bào)頻繁液位誤報(bào)多數(shù)是閾值設(shè)置不合理導(dǎo)致。建議做兩件事藥液管里空管和滿管時(shí)的ADC原始值在調(diào)試階段用串口打印出來(lái)記錄基線和閾值。加入“連續(xù)N次判定”和“持續(xù)時(shí)間判定”。比如連續(xù)3次采樣都超過(guò)閾值并且持續(xù)1秒以上才確認(rèn)為低液位。另外紅外對(duì)管的位置也很關(guān)鍵要和滴管中心對(duì)齊太高容易受空氣濕度影響太低液體還沒(méi)流完就誤報(bào)了。安裝時(shí)用熱縮管或黑色橡膠套做遮光罩能有效減少環(huán)境光干擾。5. 項(xiàng)目擴(kuò)展與個(gè)人經(jīng)驗(yàn)總結(jié)這個(gè)系統(tǒng)做完基礎(chǔ)版之后我個(gè)人覺(jué)得還可以往這幾個(gè)方向延伸加一個(gè)WiFi模塊ESP8266或ESP32把滴速、液位、報(bào)警狀態(tài)傳到護(hù)士站的上位機(jī)用簡(jiǎn)單的TCP或者M(jìn)QTT協(xié)議就行。這樣一來(lái)就從一個(gè)單機(jī)設(shè)備變成了一個(gè)物聯(lián)網(wǎng)節(jié)點(diǎn)。把數(shù)據(jù)存到SD卡里用文件系統(tǒng)記錄每天的輸液日志方便事后追溯。把按鍵和顯示升級(jí)成觸摸屏人機(jī)交互體驗(yàn)會(huì)好很多。如果追求控制精度的極致可以換成步進(jìn)電機(jī)蠕動(dòng)泵然后引入更復(fù)雜的控制策略比如基于模型預(yù)測(cè)控制在高速滴速場(chǎng)景下的表現(xiàn)。前前后后琢磨這個(gè)項(xiàng)目最大的感觸是嵌入式系統(tǒng)里軟件和硬件從來(lái)沒(méi)法分開(kāi)談。你寫(xiě)個(gè)PID控制覺(jué)得參數(shù)調(diào)不好結(jié)果一查發(fā)現(xiàn)是電源紋波太大傳感器信號(hào)被干擾你解決了一點(diǎn)干擾又發(fā)現(xiàn)電機(jī)驅(qū)動(dòng)的反電動(dòng)勢(shì)把單片機(jī)復(fù)位了。每一個(gè)問(wèn)題的背后都是軟硬件協(xié)同設(shè)計(jì)的問(wèn)題。做這類項(xiàng)目別急于一口氣把功能全懟上去。先把滴速檢測(cè)調(diào)通再寫(xiě)顯示然后加報(bào)警最后才做閉環(huán)控制。每加一個(gè)模塊就驗(yàn)證一遍之前的功能這樣就算出了問(wèn)題也知道是剛加的哪部分引起的。聽(tīng)起來(lái)慢實(shí)際上是最快的方式。