護(hù)系統(tǒng)開(kāi)發(fā)實(shí)戰(zhàn):心率血氧、跌倒檢測(cè)與遠(yuǎn)程告警)
簡(jiǎn)介面向嵌入式、物聯(lián)網(wǎng)及智能硬件學(xué)習(xí)者的一份完整老人監(jiān)護(hù)系統(tǒng)設(shè)計(jì)文檔以STM32F103RCT6為主控整合SIM800C、GPS、MPU6050、MAX30102等模塊覆蓋脈搏心率、體溫監(jiān)測(cè)跌倒檢測(cè)、定位跟蹤與OneNet云平臺(tái)遠(yuǎn)程監(jiān)護(hù)等典型場(chǎng)景。資源為單個(gè)PDF文檔壓縮包約29.6MB內(nèi)容按設(shè)計(jì)需求、硬件選型、云平臺(tái)部署、STM32代碼設(shè)計(jì)四大部分展開(kāi)詳細(xì)分析了三軸加速度傳感器檢測(cè)摔倒的原理以及SIM800C、GPS、MAX30102等芯片的工作機(jī)制與調(diào)試要點(diǎn)并給出完整Keil工程實(shí)現(xiàn)思路。目前已有174人學(xué)習(xí)適合正在開(kāi)展嵌入式課程設(shè)計(jì)、畢業(yè)設(shè)計(jì)或智能穿戴產(chǎn)品預(yù)研的讀者參考。通過(guò)這份材料可以快速建立老人監(jiān)護(hù)系統(tǒng)的整體框架認(rèn)知減少硬件選型和代碼調(diào)試階段的時(shí)間成本。1. 為什么想給獨(dú)居老人做一套監(jiān)護(hù)系統(tǒng)需求倒推設(shè)計(jì)目標(biāo)說(shuō)實(shí)話做這個(gè)項(xiàng)目的起因很現(xiàn)實(shí)。家里老人獨(dú)居白天我們?nèi)ド习嗤砩喜呕丶?。以前總覺(jué)得老人身體還行但有一次鄰居打電話說(shuō)老人在樓道里坐了半天起不來(lái)問(wèn)怎么回事老人說(shuō)就是覺(jué)得暈想坐一會(huì)兒。話是輕飄飄的可那段時(shí)間我心里一直在想如果當(dāng)時(shí)他直接倒地了誰(shuí)會(huì)知道手機(jī)在口袋里他自己能拿得出來(lái)嗎基于STM32做一套老人監(jiān)護(hù)系統(tǒng)本質(zhì)上就是把這個(gè)沒(méi)人知道的空窗期用嵌入式方案填上。站在畢業(yè)設(shè)計(jì)或者個(gè)人項(xiàng)目的角度來(lái)看這類題目的技術(shù)覆蓋度也非常友好底層要配置GPIO、定時(shí)器、I2C、ADC、串口上層要處理傳感器協(xié)議、數(shù)據(jù)濾波、狀態(tài)判斷再加上WiFi通信、實(shí)時(shí)操作系統(tǒng)一條鏈捋下來(lái)STM32開(kāi)發(fā)里最重要的知識(shí)點(diǎn)幾乎全過(guò)了一遍。這也是為什么STM32相關(guān)的畢業(yè)設(shè)計(jì)題目里老人監(jiān)護(hù)、智能穿戴、健康監(jiān)測(cè)總是排在前列——不是題目沒(méi)新意是它確確實(shí)實(shí)能鍛煉完整的產(chǎn)品思維。在設(shè)計(jì)目標(biāo)上我沒(méi)有一上來(lái)就追求全功能、高端化。跟幾個(gè)做過(guò)類似方案的朋友聊過(guò)之后結(jié)合自己踩坑的感受我把需求收斂成了下面幾條能實(shí)時(shí)測(cè)量心率、血氧飽和度這是最基本的人體體征參數(shù)能檢測(cè)跌倒動(dòng)作并且在識(shí)別到疑似跌倒時(shí)主動(dòng)詢問(wèn)/告警提供一鍵SOS緊急求救用戶主動(dòng)觸發(fā)要最可靠異常情況下能遠(yuǎn)程通知家人不能只在本地響鈴設(shè)備要便攜、可充電數(shù)據(jù)至少能夠脫離手機(jī)獨(dú)立工作。這幾條需求看著簡(jiǎn)單真正做起來(lái)每條都不省心。心率血氧牽扯到模擬前端和信號(hào)處理跌倒檢測(cè)牽扯到姿態(tài)解算和誤報(bào)抑制遠(yuǎn)程通知又牽扯到網(wǎng)絡(luò)模塊的功耗和穩(wěn)定性。后面我會(huì)按模塊拆開(kāi)講把從選型到調(diào)試的完整鏈路都過(guò)一遍包括那些文檔里查不到、只有上手跑過(guò)才會(huì)遇到的坑。2. 硬件選型里的思路主控、傳感器和通信模塊怎么搭才合理2.1 主控為什么選STM32F103C8T6而不是更小的芯片主控選的是STM32F103C8T6。這顆芯片在如今看來(lái)不算新但作為監(jiān)護(hù)系統(tǒng)的主控它的平衡性非常好72MHz主頻足夠跑傳感器數(shù)據(jù)解算和通信協(xié)議棧64KB Flash在Keil5工程里不用精打細(xì)算地?cái)D空間20KB SRAM跑FreeRTOS任務(wù)也不會(huì)太緊張1個(gè)I2C、1個(gè)USART、幾個(gè)定時(shí)器管腳位分布合理能把外設(shè)很舒服地分配開(kāi)。有人可能會(huì)問(wèn)用STM32L431之類更低功耗的芯片不更好包括我自己最初也糾結(jié)過(guò)。但實(shí)際對(duì)比后會(huì)發(fā)現(xiàn)L系列的低功耗優(yōu)勢(shì)要靠仔細(xì)配置多個(gè)低功耗模式才能發(fā)揮而F103在正常運(yùn)行時(shí)功耗也沒(méi)到不可接受的程度。對(duì)初版功能驗(yàn)證來(lái)說(shuō)F103的資料密度、例程數(shù)量、調(diào)試工具兼容性都有明顯優(yōu)勢(shì)。項(xiàng)目重點(diǎn)是先把邏輯跑通功耗優(yōu)化我放在了第二版迭代里后面會(huì)專門講。2.2 傳感器選型MAX30102和MPU6050是性價(jià)比組合體征采集部分用了MAX30102。這顆芯片把紅光LED、紅外光LED和光電二極管集成在一起通過(guò)I2C直接輸出經(jīng)過(guò)AD轉(zhuǎn)換的PPG光電容積脈搏波原始數(shù)據(jù)省去了前端模擬電路的設(shè)計(jì)。如果用分立元件搭光電容積脈搏波采集電路光是運(yùn)放選型、濾波電路調(diào)試就夠折騰好幾周MAX30102的存在讓項(xiàng)目的重心回到算法和系統(tǒng)上這是用芯片換時(shí)間的一個(gè)典型做法。姿態(tài)檢測(cè)用的是MPU6050內(nèi)置三軸加速度計(jì)和三軸陀螺儀。跌倒檢測(cè)主要靠加速度計(jì)的數(shù)據(jù)來(lái)判斷沖擊和姿態(tài)變化陀螺儀則用于計(jì)算姿態(tài)角變化兩個(gè)配合能判斷出人是直立摔倒還是彎腰下蹲之類的非跌倒動(dòng)作。MPU6050在平衡小車、手勢(shì)識(shí)別這些項(xiàng)目里被用爛了資料極多驅(qū)動(dòng)代碼也好改對(duì)開(kāi)發(fā)效率很友好。通信模塊我留了兩個(gè)方案位。室內(nèi)場(chǎng)景下首選ESP8266因?yàn)樗阋?、SDK成熟、串口透?jìng)鞣奖闳绻罄m(xù)要做室外場(chǎng)景可以把通信換成4G Cat.1模塊比如Air724UG或者EC200S代碼層面抽象出統(tǒng)一的發(fā)送接口即可。初版先用WiFi打通遠(yuǎn)程告警鏈路這點(diǎn)很關(guān)鍵。2.3 人機(jī)交互與電源方案人機(jī)交互部分包括一塊0.96寸OLED屏、兩個(gè)物理按鍵、一顆有源蜂鳴器和一顆震動(dòng)馬達(dá)。OLED用來(lái)顯示當(dāng)前心率、血氧值、告警狀態(tài)物理按鍵一個(gè)是SOS急按鍵一個(gè)是復(fù)位/確認(rèn)鍵蜂鳴器和震動(dòng)馬達(dá)負(fù)責(zé)本地告警實(shí)測(cè)下來(lái)震動(dòng)馬達(dá)在嘈雜環(huán)境下的提醒效果比蜂鳴器好老人放在口袋里也能感覺(jué)明顯。電源采用3.7V鋰電池方案電池經(jīng)過(guò)TP4056充電模塊充電再通過(guò)RT9193-3.3V穩(wěn)壓芯片給整個(gè)系統(tǒng)供電。剛開(kāi)始我用AMS1117做穩(wěn)壓但空載電流偏大對(duì)電池供電的設(shè)備不友好。RT9193的靜態(tài)電流只有微安級(jí)別而且輸出電壓紋波小給MAX30102這種模擬測(cè)量芯片供電更合適。系統(tǒng)休眠的時(shí)候整板電流能壓到幾百微安日常佩戴充電一次可以撐一天以上。3. 心率血氧采集從MAX30102的寄存器到濾波和數(shù)值解算3.1 理解PPG信號(hào)才能讀懂MAX30102的輸出先講一點(diǎn)背景原理。MAX30102內(nèi)部的兩個(gè)LED交替點(diǎn)亮光打到皮膚上后一部分被血液吸收一部分反射回來(lái)被光電二極管接收。心臟收縮時(shí)血管內(nèi)血容量增加吸收的光變多反射光就變?nèi)跣呐K舒張時(shí)相反。光電二極管輸出的光強(qiáng)信號(hào)經(jīng)過(guò)跨阻放大和ADC采樣就得到了隨時(shí)間波動(dòng)的PPG波形。血氧飽和度則是利用含氧血紅蛋白和脫氧血紅蛋白對(duì)紅光660nm與紅外光940nm吸收率的差異來(lái)計(jì)算的這是脈搏血氧儀的基本原理。MAX30102的驅(qū)動(dòng)配置看起來(lái)簡(jiǎn)單實(shí)際上有幾個(gè)關(guān)鍵參數(shù)直接影響數(shù)據(jù)質(zhì)量。LED電流我調(diào)到了20mA左右脈沖寬度設(shè)成最寬的4096微秒ADC量程選2048。這個(gè)組合的好處是寬脈沖讓每個(gè)采樣點(diǎn)積累更多光信號(hào)信噪比更高20mA電流在指夾或腕戴場(chǎng)景下既不會(huì)太暗導(dǎo)致信號(hào)幅度過(guò)小也不會(huì)因?yàn)殡娏魈蟀压暮桶l(fā)熱帶上去。采樣率設(shè)置在100Hz對(duì)人體的脈搏頻帶來(lái)說(shuō)足夠了平均心率、呼吸干擾都不需要太高的采樣率。3.2 I2C讀取和FIFO的使用方式MAX30102內(nèi)部有FIFO緩沖可以把多個(gè)采樣點(diǎn)暫存在芯片里再一次性讀走。代碼上非常適合用定時(shí)器主循環(huán)配合DMA或者輪詢I2C讀取。我實(shí)際的讀取流程是一個(gè)定時(shí)器以10ms為周期觸發(fā)軟件標(biāo)志位主循環(huán)檢測(cè)到標(biāo)志位后通過(guò)I2C從FIFO寄存器讀出兩個(gè)通道的數(shù)據(jù)紅光和紅外光各占3個(gè)字節(jié)轉(zhuǎn)換成32位整數(shù)存在數(shù)組里。這里有個(gè)細(xì)節(jié)值得提一下MAX30102的FIFO深度是32個(gè)樣本如果讀得不及時(shí)數(shù)據(jù)會(huì)被新數(shù)據(jù)覆蓋。所以讀取FIFO的周期不要比采樣周期大太多。我直接用100Hz的采樣率配10ms的讀取周期每次讀出來(lái)正好是一到兩個(gè)新樣本。實(shí)測(cè)在標(biāo)準(zhǔn)庫(kù)和HAL庫(kù)兩種環(huán)境下都跑過(guò)HAL庫(kù)的I2C因?yàn)橹虚g有多層抽象如果中間打斷次數(shù)多偶爾會(huì)丟樣本后來(lái)改成用DMA方式讀FIFO基本沒(méi)有再出現(xiàn)掉數(shù)的情況。3.3 心率解算滑動(dòng)平均加閾值檢測(cè)比FFT更好用從原始PPG信號(hào)到心率值大多數(shù)人第一反應(yīng)是上FFT做頻譜分析。但FFT在資源受限的MCU上做需要維護(hù)較大的計(jì)算緩沖而且窗口長(zhǎng)度短了頻率分辨率不夠。我實(shí)際試過(guò)之后最終采用的是時(shí)域方案先做滑動(dòng)平均濾波去掉高頻噪聲再用一個(gè)簡(jiǎn)單的一階高通濾波截止頻率約0.5Hz去除基線漂移然后通過(guò)自適應(yīng)閾值法檢測(cè)脈搏波的峰值計(jì)算相鄰兩個(gè)峰的時(shí)間間隔換算得到心率值。自適應(yīng)閾值的思路是維護(hù)一個(gè)滑動(dòng)窗口取窗口中信號(hào)最大值的70%作為當(dāng)前檢測(cè)閾值。當(dāng)信號(hào)從下向上穿過(guò)閾值時(shí)記錄一個(gè)峰值點(diǎn)。兩次峰值之間的時(shí)間差取倒數(shù)再乘以60就得到當(dāng)前的心率。血氧飽和度采用的是經(jīng)驗(yàn)查表法計(jì)算紅光和紅外光交流分量與直流分量的比值R再代入血氧芯片數(shù)據(jù)手冊(cè)中的標(biāo)準(zhǔn)曲線用分段線性插值擬合出SpO2值。這套算法在靜止?fàn)顟B(tài)下誤差可以控制在±2%以內(nèi)已經(jīng)能滿足日常監(jiān)護(hù)的精度要求。3.4 實(shí)操里最容易忽視的信號(hào)質(zhì)量問(wèn)題調(diào)試階段最讓我頭疼的不是算法而是信號(hào)質(zhì)量。MAX30102的數(shù)據(jù)手冊(cè)會(huì)告訴你芯片支持紅血氧檢測(cè)但它不會(huì)告訴你戴得不緊、環(huán)境光太強(qiáng)、手指晃動(dòng)時(shí)數(shù)據(jù)會(huì)直接變成一堆毛刺。后來(lái)我總結(jié)了幾條實(shí)用的排查方法用手指肚按壓傳感器指尖變白的瞬間如果看不到清晰的脈搏波說(shuō)明光路沒(méi)有對(duì)準(zhǔn)或LED電流太小用黑色海綿遮光袋包住手指和傳感器對(duì)比遮光前后波形如果遮光后波形幅度變化很大說(shuō)明環(huán)境光干擾嚴(yán)重需要加強(qiáng)結(jié)構(gòu)遮光手指保持靜止時(shí)波形幅度明顯增大稍有晃動(dòng)幅度就掉一半這種情況下算出的心率值會(huì)忽高忽低算法上必須識(shí)別信號(hào)質(zhì)量差值信號(hào)質(zhì)量差的時(shí)候?qū)幙刹簧蠄?bào)數(shù)據(jù)也不能報(bào)錯(cuò)數(shù)據(jù)。關(guān)于上不上報(bào)錯(cuò)誤數(shù)據(jù)這件事我后來(lái)專門做了邏輯保護(hù)實(shí)時(shí)計(jì)算兩個(gè)相鄰峰值的間隔如果間隔差異超過(guò)30%就判定當(dāng)前信號(hào)不可信心率值顯示--直到連續(xù)出現(xiàn)5個(gè)以上穩(wěn)定的峰值間隔才恢復(fù)顯示。這個(gè)機(jī)制在用戶翻身、活動(dòng)肢體時(shí)能有效避免心率值亂跳實(shí)際使用體驗(yàn)好很多。4. 跌倒檢測(cè)的邏輯加速度波形分析加姿態(tài)判斷4.1 跌倒的波形特征和檢測(cè)難點(diǎn)跌倒檢測(cè)是這類監(jiān)護(hù)系統(tǒng)里誤報(bào)率最高、最容易被用戶吐槽的模塊。我一開(kāi)始以為只要檢測(cè)到合加速度突變就能判定跌倒后來(lái)用MPU6050做了一組真實(shí)模擬實(shí)驗(yàn)包括從站著倒地、從椅子上滑落、快速坐下、彎腰撿東西、原地跳了跳才發(fā)現(xiàn)單純用閾值判斷完全不可靠。快速坐下和跳一下同樣會(huì)產(chǎn)生超過(guò)3g的加速度尖峰如果按閾值直接報(bào)警系統(tǒng)基本上會(huì)一天誤報(bào)幾十次。所以我把跌倒檢測(cè)拆成了三個(gè)階段來(lái)識(shí)別第一個(gè)是失重階段人體從站立到失重倒地合加速度會(huì)先下降低于0.6g并且持續(xù)至少100ms第二個(gè)是撞擊階段身體接觸地面瞬間合加速度產(chǎn)生一個(gè)至少2.5g到3g的尖峰持續(xù)時(shí)間在30ms到200ms之間第三個(gè)是靜止階段倒地后身體保持不動(dòng)加速度回到1g附近姿態(tài)角從接近垂直變成接近水平。4.2 合加速度計(jì)算與姿態(tài)角獲取合加速度的計(jì)算很簡(jiǎn)單三個(gè)軸加速度的平方和開(kāi)根號(hào)。由于MPU6050輸出的是數(shù)字加速度值單位被配置成±2g量程取出的原始值除以16384就得到以g為單位的加速度。姿態(tài)角方面我用的是加速度計(jì)靜態(tài)傾角公式pitch atan2(accel_y, sqrt(accel_x^2 accel_z^2)) * 180 / PI roll atan2(-accel_x, sqrt(accel_y^2 accel_z^2)) * 180 / PI為什么要用這個(gè)公式而不是直接取某個(gè)軸的數(shù)值因?yàn)檠b在手環(huán)或者口袋里的設(shè)備穿戴方向不固定直接用單軸判斷是否水平非常不靠譜。用雙軸反正切算出的姿態(tài)角在設(shè)備任意旋轉(zhuǎn)90度的情況下都能穩(wěn)定反映出人體軀干相對(duì)地面的角度。設(shè)計(jì)上要求可穿戴設(shè)備在任意角度佩戴都能使用這是必須過(guò)的坎。4.3 三階段判定算法和誤報(bào)抑制三階段判定在FreeRTOS里是作為一個(gè)獨(dú)立任務(wù)跑的每50ms讀一次MPU6050并更新?tīng)顟B(tài)機(jī)。還有一個(gè)好用的小技巧每1分鐘滑動(dòng)存儲(chǔ)最近50秒的姿態(tài)角均值當(dāng)發(fā)生疑似跌倒時(shí)把當(dāng)前姿態(tài)角與均值對(duì)比如果角度差小于30度說(shuō)明這人本來(lái)就躺著大概率是翻身或者起身動(dòng)作不是跌倒。確認(rèn)疑似跌倒后系統(tǒng)不會(huì)立刻發(fā)告警而是進(jìn)入一段10秒的預(yù)告警模式蜂鳴器鳴叫、屏幕上顯示檢測(cè)到跌倒按鍵取消報(bào)警。如果用戶在這10秒內(nèi)按下確認(rèn)鍵說(shuō)明自己沒(méi)事告警取消如果10秒沒(méi)有取消系統(tǒng)自動(dòng)判定為嚴(yán)重跌倒觸發(fā)遠(yuǎn)程告警并附帶當(dāng)前GPS位置如果接了定位模塊。這個(gè)10秒倒計(jì)時(shí)可取消機(jī)制比我最初設(shè)計(jì)的檢測(cè)即報(bào)警好太多誤報(bào)率在實(shí)際測(cè)試?yán)飶脑瓉?lái)的每周十幾次降到了差不多兩周一次。4.4 實(shí)際測(cè)試數(shù)據(jù)說(shuō)明問(wèn)題我整理了一組室內(nèi)模擬測(cè)試的記錄每次動(dòng)作重復(fù)20遍統(tǒng)計(jì)檢出結(jié)果測(cè)試動(dòng)作檢出次數(shù)未檢出次數(shù)誤報(bào)情況從站立位直接倒地191無(wú)從椅子上滑落200無(wú)快速坐下1檢出191次彎腰撿東西020無(wú)原地跳躍2檢出182次躺下翻身1檢出191次從數(shù)據(jù)里能明顯看到漫游心跳、下蹲、跳躍這類動(dòng)作依然會(huì)有一定比例的誤報(bào)這是當(dāng)前算法方案的天花板。如果想進(jìn)一步壓低誤報(bào)率可以往里面加陀螺儀的姿態(tài)角變化率判斷或者在撞擊階段之后加入一個(gè)波形恢復(fù)時(shí)間的特征。但從可靠報(bào)警的角度看現(xiàn)在這個(gè)方案已經(jīng)能解決大多數(shù)真實(shí)跌倒場(chǎng)景后續(xù)算法迭代可以作為獨(dú)立優(yōu)化點(diǎn)繼續(xù)深化。5. 遠(yuǎn)程告警鏈路ESP8266加MQTT把消息送到手機(jī)上5.1 通信方案為什么選MQTT而不是TCP長(zhǎng)連接遠(yuǎn)程告警是整個(gè)系統(tǒng)離用戶最近、也是價(jià)值最直觀的部分。告警消息必須推送到家人的手機(jī)上而且延遲要盡量低。這里我選了MQTT協(xié)議通過(guò)ESP8266 Wi-Fi模塊連到云服務(wù)器。有人會(huì)不解STM32直接通過(guò)AT指令發(fā)個(gè)TCP連接不也能把數(shù)據(jù)傳出去嗎為什么要繞一層MQTT原因就在于TCP長(zhǎng)連接需要自己維護(hù)?;?、重連、消息確認(rèn)這些機(jī)制而MQTT發(fā)布訂閱模型天然支持一對(duì)多推送家里人用微信小程序訂閱設(shè)備的告警主題設(shè)備只需要發(fā)布一條消息所有訂閱者都能收到。阿里云物聯(lián)網(wǎng)平臺(tái)、EMQX這類公共MQTT Broker都提供免費(fèi)的開(kāi)發(fā)檔位注冊(cè)一個(gè)設(shè)備三元組就能用省去自己搭服務(wù)器的麻煩。我用的云平臺(tái)是阿里云IoT的公共實(shí)例免費(fèi)額度對(duì)個(gè)人項(xiàng)目綽綽有余。5.2 數(shù)據(jù)幀結(jié)構(gòu)與發(fā)送流程設(shè)備發(fā)送給云端的數(shù)據(jù)格式用JSON字段包括設(shè)備ID、當(dāng)前心率、血氧、電池電量、告警類型sos/fall、時(shí)間戳。因?yàn)镸CU內(nèi)存有限JSON字符串不能做得太大我裁掉了不必要的空格整個(gè)包的字符串長(zhǎng)度控制在220字節(jié)以內(nèi)放在一個(gè)256字節(jié)的緩沖數(shù)組里發(fā)送完立即清空。// 告警消息JSON結(jié)構(gòu)示例 {dev:A001,hr:85,spo2:97,bat:78,alarm:fall,ts:1720000000}發(fā)送流程設(shè)計(jì)成三級(jí)防抖第一級(jí)是本地蜂鳴器和屏幕提示第二級(jí)是MQTT發(fā)布告警主題第三級(jí)是把告警記錄存在STM32內(nèi)部Flash里下次設(shè)備收發(fā)電時(shí)重新上報(bào)。這個(gè)三級(jí)備份設(shè)計(jì)是我從一次WiFi斷開(kāi)導(dǎo)致的丟事件中總結(jié)出來(lái)的。當(dāng)時(shí) ESP8266斷網(wǎng)重連花了差不多一分多鐘恰好那一次真正的SOS請(qǐng)求沒(méi)有發(fā)出去我意識(shí)到所有數(shù)據(jù)只走網(wǎng)絡(luò)鏈路是不可靠的必須在設(shè)備端留一份底。5.3 斷線重連與電量?jī)?yōu)化ESP8266作為WiFi客戶端最大問(wèn)題在于斷線后不會(huì)主動(dòng)恢復(fù)需要通過(guò)AT固件的事件上報(bào)機(jī)制來(lái)感知連接斷開(kāi)然后重新發(fā)起TCP連接、重新訂閱主題。這個(gè)邏輯名叫自動(dòng)恢復(fù)?;顧C(jī)制我實(shí)現(xiàn)的時(shí)候用一個(gè)軟件定時(shí)器每30秒檢查一次連接狀態(tài)不通就自動(dòng)走重連流程。只要WiFi信號(hào)不是弱到-85dBm以下實(shí)測(cè)重連成功概率很高。WiFi全時(shí)在線是這類設(shè)備功耗的最大殺手。我實(shí)測(cè)過(guò)MAX30102加MPU6050全速運(yùn)行加上ESP8266保持常連電池電流平均在120mA到150mA之間一塊500mAh電池不到4小時(shí)就空了這顯然沒(méi)法用。后來(lái)優(yōu)化的策略是ESP8266平時(shí)Power Down心跳數(shù)據(jù)每30秒通過(guò)串口發(fā)送完就休眠只有檢測(cè)到異常告警時(shí)才喚醒并建立連接。這樣日常模式平均電流降到30mA左右異常模式下才走全功率鏈路。犧牲了一點(diǎn)數(shù)據(jù)實(shí)時(shí)上傳的平滑度換來(lái)了接近十倍的續(xù)航提升這筆買賣非常劃算。6. 軟件架構(gòu)FreeRTOS任務(wù)劃分與模塊解耦6.1 多任務(wù)劃分的依據(jù)系統(tǒng)軟件采用FreeRTOS管理原因很直接這套方案里同時(shí)存在傳感器讀取、算法判斷、顯示刷新、按鍵掃描、WiFi通信、Flash存儲(chǔ)多個(gè)需要并行處理的環(huán)節(jié)。如果用裸機(jī)輪詢邏輯寫主循環(huán)會(huì)被I2C讀取的阻塞延時(shí)占滿按鍵響應(yīng)和顯示刷新都會(huì)變得卡頓。任務(wù)劃分成這樣傳感器采集任務(wù)100Hz讀取MAX30102和MPU6050原始數(shù)據(jù)算法處理任務(wù)20Hz運(yùn)行PPG濾波、心率血氧計(jì)算、跌倒檢測(cè)狀態(tài)機(jī)顯示任務(wù)5Hz刷新OLED屏幕更新心率血氧值和圖標(biāo)狀態(tài)網(wǎng)絡(luò)任務(wù)1Hz處理MQTT收發(fā)、心跳保活、斷線重連按鍵與告警任務(wù)10Hz掃描SOS和取消鍵控制蜂鳴器與震動(dòng)馬達(dá)。6.2 任務(wù)間通信用消息隊(duì)列代替全局變量任務(wù)之間傳數(shù)據(jù)用的是FreeRTOS消息隊(duì)列而不是到處定義全局變量。全局變量的問(wèn)題在調(diào)試多任務(wù)代碼時(shí)特別明顯循環(huán)緩沖被多個(gè)任務(wù)同時(shí)寫容易產(chǎn)生數(shù)據(jù)錯(cuò)位和難復(fù)現(xiàn)的隱性BUG。消息隊(duì)列的方式相當(dāng)于把數(shù)據(jù)流按管道隔離生產(chǎn)者只管往隊(duì)列里丟消費(fèi)者只在隊(duì)列非空時(shí)才取不會(huì)出現(xiàn)兩個(gè)任務(wù)同時(shí)訪問(wèn)同一塊內(nèi)存的情況。傳感采集任務(wù)和算法處理任務(wù)之間用的是兩個(gè)隊(duì)列一條通道傳PPG原始波形數(shù)據(jù)一條通道傳MPU6050的姿態(tài)數(shù)據(jù)。隊(duì)列深度的設(shè)置也需要考慮我最后定的是每個(gè)隊(duì)列長(zhǎng)度64每項(xiàng)4字節(jié)FIFO讀出的樣本如果短時(shí)間沒(méi)被取走隊(duì)列會(huì)暫存而不是立刻丟降低了高優(yōu)先級(jí)任務(wù)被打斷時(shí)數(shù)據(jù)缺失的概率。實(shí)測(cè)在系統(tǒng)負(fù)載最高的異常處理場(chǎng)景下隊(duì)列也不會(huì)滿沒(méi)有出現(xiàn)因?yàn)殛?duì)列滿導(dǎo)致丟失關(guān)鍵傳感器數(shù)據(jù)的現(xiàn)象。6.3 按鍵消抖與中斷處理的細(xì)節(jié)SOS按鍵的可靠性直接關(guān)乎生命安全這里不能簡(jiǎn)單用延遲消抖的常規(guī)做法。我的方案是按鍵觸發(fā)外部中斷中斷里只做一件事把一個(gè)軟件定時(shí)器的啟動(dòng)時(shí)間重置。定時(shí)器超時(shí)0.15秒后在任務(wù)上下文中做一次完整的電平確認(rèn)多重確認(rèn)通過(guò)后才判定為一次有效按鍵。這么做既避免了延遲消抖帶來(lái)的誤觸發(fā)又不會(huì)在中斷里做太多事影響其他實(shí)時(shí)邏輯。7. 實(shí)測(cè)表現(xiàn)與調(diào)試經(jīng)驗(yàn)?zāi)男┛又档糜浵聛?lái)7.1 整體運(yùn)行效果整套系統(tǒng)做完后我戴著原型機(jī)進(jìn)行了大約三天的日常測(cè)試包括做飯、打掃、快走、午睡這幾個(gè)場(chǎng)景。心率顯示在靜息狀態(tài)下與小米手環(huán)對(duì)比差值在3次/分以內(nèi)血氧在靜止?fàn)顟B(tài)下與醫(yī)用指夾式血氧儀對(duì)比誤差在1%以內(nèi)SOS按鍵響應(yīng)時(shí)間小于1秒跌倒檢測(cè)模擬測(cè)試的識(shí)別率約95%誤報(bào)集中在快速坐下的場(chǎng)景。這個(gè)成績(jī)作為畢業(yè)設(shè)計(jì)或者個(gè)人項(xiàng)目交差完全夠用但離商用產(chǎn)品的準(zhǔn)確度和魯棒性還有差距。7.2 MAX30102數(shù)據(jù)異常排查的三板斧很多卡在起步階段的人會(huì)問(wèn)為什么我的MAX30102讀數(shù)全是0xFFFFFFFF這類問(wèn)題九成出在I2C通信和芯片配置上。我按經(jīng)驗(yàn)總結(jié)了一個(gè)排查順序確認(rèn)I2C地址是否沖突。MAX30102的7位地址是0x57如果總線上掛了其他I2C器件需要通過(guò)地址區(qū)分避免地址沖突導(dǎo)致讀寫錯(cuò)亂檢查I2C上拉電阻一般4.7k到10k都可以。如果上拉電阻太大或太小波形邊沿會(huì)變差數(shù)據(jù)就讀不回來(lái)尤其當(dāng)I2C線路稍微長(zhǎng)一點(diǎn)的時(shí)候更明顯初始化順序很重要。要嚴(yán)格按照數(shù)據(jù)手冊(cè)的流程先寫模式配置寄存器再配置LED電流最后使能FIFO寫入。順序錯(cuò)了會(huì)出現(xiàn)在某個(gè)狀態(tài)一直讀不到有效數(shù)據(jù)的情況。7.3 電池電量顯示的水分還有一個(gè)容易被忽略的坑是電量顯示。原計(jì)劃用ADC直接讀電池電壓再換算成電量百分比后來(lái)發(fā)現(xiàn)鋰電池的電壓和剩余電量并不是線性關(guān)系。3.7V到4.2V之間前半段電壓掉得慢后半段掉得很快直接線性映射的電量會(huì)有將近20%的誤差。后來(lái)我在網(wǎng)上找了一張比較通用的鋰電池電壓-容量曲線表用分段線性查表替代了一刀切的線性換算實(shí)測(cè)誤差縮小到5%以內(nèi)。這個(gè)小改動(dòng)人不多但對(duì)每天看電量顯示的老年用戶來(lái)說(shuō)準(zhǔn)確的電量顯示就是安全感。7.4 結(jié)構(gòu)設(shè)計(jì)上的一點(diǎn)建議軟件和硬件都完成之后剩下的問(wèn)題就是怎么裝進(jìn)殼子。我建議用3D打印機(jī)打印一個(gè)帶卡扣的手環(huán)外殼傳感器部分用透明窗口讓光路盡量直射皮膚。感應(yīng)面不要緊貼外殼表面內(nèi)部留大概1到2毫米的懸空間隙否則手指按壓時(shí)受力不均會(huì)造成局部透光影響信號(hào)采集質(zhì)量。OLED屏直接嵌在殼體表面朝外方便用戶查看。把排線全部用熱熔膠固定在殼體內(nèi)壁避免晃動(dòng)引起排線虛接這一條對(duì)于裸露測(cè)試板狀態(tài)的階段特別有用。8. 從畢業(yè)設(shè)計(jì)到可用產(chǎn)品還要補(bǔ)哪些功課做到現(xiàn)在這個(gè)程度原型機(jī)已經(jīng)能穩(wěn)定監(jiān)體征、識(shí)別跌倒、發(fā)遠(yuǎn)程告警自己也真實(shí)用小半個(gè)月。不過(guò)如果想讓它真正適合給老人長(zhǎng)期使用還有幾層功課要補(bǔ)。功耗方面當(dāng)前WiFi方案只適合在有電源環(huán)境的家里使用出門就沒(méi)網(wǎng)。要真正做成戶外可用通信方案必須換成4G Cat.1另外STM32的睡眠模式要細(xì)分平時(shí)除了傳感器輪詢主控盡量進(jìn)Stop模式把平均電流壓到10mA級(jí)別配上1000mAh以上電池才能實(shí)現(xiàn)一周一充。算法方面心率血氧在用戶轉(zhuǎn)身、說(shuō)話、抬手時(shí)依然會(huì)有短時(shí)抖動(dòng)跌倒檢測(cè)也還沒(méi)做到老人真實(shí)的摔倒數(shù)據(jù)學(xué)習(xí)。這些場(chǎng)景的邊界性能要進(jìn)一步提升靠的是大量真實(shí)佩戴數(shù)據(jù)來(lái)調(diào)試模型參數(shù)已經(jīng)屬于數(shù)據(jù)驅(qū)動(dòng)的范疇了。硬件層面鋰電池充電保護(hù)電路、電源和傳感器鋪地隔離、EMC測(cè)試這些量產(chǎn)必須考慮的事情原型機(jī)上還沒(méi)有系統(tǒng)做。如果要將這套方案繼續(xù)推進(jìn)到實(shí)際產(chǎn)品階段硬件的可靠性是第一優(yōu)先級(jí)這可能比算法迭代更花時(shí)間。不過(guò)從學(xué)習(xí)、畢業(yè)設(shè)計(jì)、個(gè)人項(xiàng)目的角度衡量基于STM32的這套老人監(jiān)護(hù)系統(tǒng)已經(jīng)把從硬件到云端全鏈路打通了。對(duì)我來(lái)說(shuō)最大的收獲不是那一堆能跑的代碼而是習(xí)慣了從真實(shí)用戶場(chǎng)景出發(fā)倒推技術(shù)方案這個(gè)思考方式。在把心率血氧的調(diào)試閾值一個(gè)個(gè)試、把跌倒誤報(bào)率一點(diǎn)點(diǎn)降下來(lái)的過(guò)程里你真的能感受到工程實(shí)踐和課本例題之間的差別——課本告訴你什么是對(duì)的踩坑會(huì)告訴你什么是足夠好。這套系統(tǒng)的調(diào)優(yōu)空間還有很多但作為一個(gè)起點(diǎn)它已經(jīng)給了我繼續(xù)往深做的理由。本文還有配套的精品資源點(diǎn)擊獲取