畢設(shè)實(shí)戰(zhàn):硬件選型到答辯全流程指南)
簡介本資源是一套完整的基于STM32單片機(jī)的智能手環(huán)高分畢業(yè)設(shè)計(jì)項(xiàng)目面向電子信息、自動(dòng)化、嵌入式等專業(yè)的本科生適用于畢業(yè)設(shè)計(jì)、課程設(shè)計(jì)及期末大作業(yè)實(shí)踐。項(xiàng)目涵蓋心率監(jiān)測、血壓提醒、計(jì)步、時(shí)間顯示等核心功能源碼經(jīng)本地實(shí)機(jī)編譯驗(yàn)證可穩(wěn)定運(yùn)行評(píng)審得分高達(dá)98分內(nèi)容已通過助教老師審定難度適中且工程規(guī)范性強(qiáng)。壓縮包共103個(gè)文件含11個(gè)C源文件與8個(gè)H頭文件構(gòu)成完整固件工程11個(gè)PDF文檔提供原理圖說明、算法解析與測試報(bào)告8個(gè)DOCX格式的設(shè)計(jì)說明書與答辯PPT框架以及大量BMP/PNG界面資源圖如開機(jī)廣告、心率/血壓測量界面等整體大小為17.75MB。目前已有210人學(xué)習(xí)下載配套資料齊全、模塊劃分清晰、注釋充分可直接用于移植開發(fā)或作為嵌入式系統(tǒng)綜合實(shí)踐的優(yōu)質(zhì)參考范例。 每年三月份總有一批人開始為畢業(yè)設(shè)計(jì)失眠。題目選了“基于STM32單片機(jī)的智能手環(huán)”的人表面上開心背地里也在打鼓網(wǎng)上源碼一大堆但到底哪份能跑通文檔怎么寫才能不像是抄的其實(shí)我一開始做這個(gè)項(xiàng)目時(shí)也經(jīng)歷過同樣的迷茫。今天這篇東西不曬成品圖也不吹噓功能就從一個(gè)過來人的角度把基于STM32單片機(jī)的智能手環(huán)從選題、硬件、軟件、文檔、調(diào)試到答辯的完整鏈路講清楚。手里這套源碼和全部資料我是按照“拿到手就能復(fù)現(xiàn)、改一改就能畢業(yè)”的標(biāo)準(zhǔn)整理的所以你會(huì)看到很多實(shí)際踩過坑之后才明白的細(xì)節(jié)。這不是一篇枯燥的技術(shù)說明書而是更適合當(dāng)畢設(shè)參考的實(shí)戰(zhàn)筆記。1. 為什么“STM32智能手環(huán)”是畢業(yè)設(shè)計(jì)里的穩(wěn)妥牌1.1 手環(huán)項(xiàng)目的天然優(yōu)勢畢業(yè)設(shè)計(jì)選題最怕什么怕題目太空、太抽象也怕太簡單沒有工作量更怕太復(fù)雜做不完。STM32智能手環(huán)恰好落在“中間剛剛好”的位置它有一個(gè)物理實(shí)體能戴在手腕上實(shí)時(shí)顯示數(shù)據(jù)視覺沖擊力強(qiáng)它內(nèi)部有傳感器采集、數(shù)據(jù)處理、顯示交互、電源管理這些模塊單獨(dú)拿出來都能講幾分鐘它又不需要做太深的人工智能或大數(shù)據(jù)大部分功能用單片機(jī)基礎(chǔ)外設(shè)就能實(shí)現(xiàn)。換句話說這個(gè)題目天然適合在答辯現(xiàn)場演示。常規(guī)的“溫度采集系統(tǒng)”只能看一個(gè)數(shù)而智能手環(huán)可以把時(shí)間、步數(shù)、心率、溫度、菜單切換全部展示出來老師一眼就能看出這是一個(gè)完整的小型嵌入式系統(tǒng)。再加上“智能穿戴”這個(gè)概念本身就有話題度就算你的算法只用了最簡單的閾值檢測也能用“低功耗場景下的輕量級(jí)計(jì)步實(shí)現(xiàn)”這種說法包裝聽起來并不掉價(jià)。1.2 項(xiàng)目訓(xùn)練的核心能力很多人在簡歷里寫“熟悉STM32”但實(shí)際只跑過流水燈。做一個(gè)智能手環(huán)至少能逼你接觸這些東西硬件最小系統(tǒng)晶振、復(fù)位、電源、啟動(dòng)模式、下載接口。常見通信協(xié)議I2C讀傳感器、SPI或I2C驅(qū)動(dòng)OLED、UART接藍(lán)牙模塊。ADC采樣電池電壓檢測、溫濕度模擬量讀取。傳感器數(shù)據(jù)處理加速度計(jì)數(shù)據(jù)的濾波、計(jì)步閾值判定、心率信號(hào)峰值檢測。狀態(tài)機(jī)編程OLED菜單切換、按鍵事件響應(yīng)、低功耗模式切換。這些能力不是背出來的是在反復(fù)調(diào)試中磨出來的。等到你真把計(jì)步從“亂跳”調(diào)到“走一步數(shù)一下”你對(duì)單片機(jī)中斷、定時(shí)器、DMA、線程調(diào)度這些概念的理解會(huì)遠(yuǎn)超那種只會(huì)調(diào)用庫函數(shù)的人。1.3 源碼、文檔、全部資料對(duì)拿高分的作用畢設(shè)成績從來不是只看功能而是看“資料完整性”。一份高分畢設(shè)通常要交付論文、可運(yùn)行源碼、演示視頻、硬件實(shí)物。很多人代碼能跑但論文寫不出來也有人論文寫得漂亮但代碼一運(yùn)行就崩。所以我在整理這套資料時(shí)特別強(qiáng)調(diào)“源碼文檔說明全部資料”三件套。源碼負(fù)責(zé)“能跑”文檔負(fù)責(zé)“能講”電路圖、原理圖、芯片手冊、調(diào)試記錄這類的全部資料則負(fù)責(zé)“可信度”。答辯時(shí)老師隨口問一句“你電源芯片為什么選這個(gè)”你如果能從資料里翻出數(shù)據(jù)手冊并說出“靜態(tài)功耗、壓降、輸出電流”這幾個(gè)關(guān)鍵詞印象分會(huì)立刻不一樣。資料齊全的意義就是讓你在壓力下也能快速找到依據(jù)而不是硬著頭皮編。2. 硬件搭建從主控到傳感器的完整選型思路2.1 主控最小系統(tǒng)不要一上來就畫PCB很多新手拿到原理圖就開始畫PCB結(jié)果焊接后板子不工作半天找不到原因。我第一次做手環(huán)時(shí)也犯過這個(gè)錯(cuò)。后來學(xué)乖了先買現(xiàn)成的核心板或最小系統(tǒng)板把功能全部調(diào)通再考慮畫集成板。STM32F103C8T6這種小容量芯片淘寶上一塊核心板十幾塊錢帶USB轉(zhuǎn)串口下載對(duì)畢設(shè)來說完全夠用。手環(huán)項(xiàng)目主控我推薦STM32F103C8T6原因有三個(gè)第一網(wǎng)上資料最多幾乎每個(gè)外設(shè)都能找到例程第二內(nèi)置64KB Flash和20KB RAMOLED、MPU6050、心率傳感器、RTC這些模塊堆在一起也不會(huì)爆第三封裝是LQFP48手工焊接比BGA容易太多。如果你的功能加了藍(lán)牙音頻或者需要跑FreeRTOS多任務(wù)可以考慮F103RCT6甚至F407但對(duì)普通計(jì)步手環(huán)來說C8T6已經(jīng)綽綽有余。最小系統(tǒng)需要注意的引腳分配NRST接上拉電阻和復(fù)位按鍵BOOT0接10K下拉到GNDBOOT1隨意VDDA/VSSA用磁珠和電容隔離OSC_IN/OSC_OUT接8MHz晶振和兩個(gè)20pF負(fù)載電容SWDIO/SWCLK引出來下載調(diào)試。另外VBAT引腳最好直接接3.3V這樣待機(jī)時(shí)RTC還能繼續(xù)走。2.2 傳感器模塊選型MPU6050、MAX30102、DHT11智能手環(huán)的核心功能是計(jì)步和心率所以傳感器選型很關(guān)鍵。計(jì)步我建議用MPU6050它內(nèi)部集成了三軸加速度計(jì)和三軸陀螺儀通過I2C接口輸出16位數(shù)據(jù)。雖然陀螺儀對(duì)計(jì)步用處不大但后續(xù)如果你想做姿態(tài)識(shí)別比如抬手亮屏、搖一搖切歌陀螺儀就能派上用場。心率模塊選MAX30102這是目前畢設(shè)中最常用的血氧心率傳感器。它用紅光和紅外光照射血管通過光電二極管檢測PPG信號(hào)。需要注意的是MAX30102的I2C地址可以通過AD引腳設(shè)置默認(rèn)0x57而且模塊工作電壓是1.8V所以很多成品模塊已經(jīng)帶了電平轉(zhuǎn)換芯片購買時(shí)一定要確認(rèn)是否兼容3.3V主控。溫濕度傳感器可以根據(jù)需求選DHT11或SHT30。DHT11便宜但精度低、采樣周期長1秒一次手環(huán)上顯示環(huán)境溫度夠用SHT30精度高且支持I2C代碼寫起來更舒服。如果不想增加復(fù)雜度DHT11完全沒問題但論文里需要提一句“傳感器性能滿足日常環(huán)境監(jiān)測需求”。除了傳感器還要預(yù)留一個(gè)無源蜂鳴器或振動(dòng)馬達(dá)。振動(dòng)馬達(dá)推薦扁平紐扣式PWM控制就能驅(qū)動(dòng)用來做鬧鐘或者來電提醒比蜂鳴器安靜且更像真實(shí)手環(huán)。2.3 顯示與交互OLED、按鍵、振動(dòng)馬達(dá)的設(shè)計(jì)顯示部分0.96寸OLED是最常見的選擇。I2C版本只需要四根線VCC、GND、SCL、SDA接線簡單但刷新速度一般SPI版本雖然多兩根線但刷新速度快很多如果你要在界面上畫動(dòng)態(tài)波形SPI是更好的選擇。我自己后來改用了SPI版本的OLED因?yàn)樾穆什ㄐ嗡⑿聲r(shí)I2C模式的撕裂感太明顯。OLED的引腳分配要盡量避免和傳感器沖突。比如MPU6050也走I2C你可以把OLED放在SPI總線上這樣兩條總線并行驅(qū)動(dòng)互不干擾。注意OLED模塊的邏輯電平和功耗3.3V供電即可背光電流大概20mA對(duì)鋰電池來說不算大。按鍵設(shè)計(jì)要防抖。手環(huán)上一般放兩個(gè)按鍵一個(gè)用于菜單切換一個(gè)用于確認(rèn)/返回。如果用的是外部中斷軟件里要加20ms消抖如果是掃描方式建議放到定時(shí)器中斷里每10ms掃描一次避免主循環(huán)卡頓。振動(dòng)馬達(dá)用NPN三極管或MOS管驅(qū)動(dòng)GPIO接基極馬達(dá)正極接電源負(fù)極接集電極續(xù)流二極管和RC吸收電路一定要加否則開關(guān)瞬間的尖峰電壓可能讓單片機(jī)復(fù)位。2.4 電源系統(tǒng)鋰電池充放電和電壓檢測手環(huán)是穿戴設(shè)備供電不能拉根電源線。我用的方案是3.7V鋰電池加TP4056充電模塊再加ME6211穩(wěn)壓芯片。TP4056負(fù)責(zé)給電池充電最大充電電流可以透過外部電阻設(shè)置我一般設(shè)500mA發(fā)熱小ME6211是一款低壓差LDO輸入2.5V到6V輸出3.3V靜態(tài)功耗幾十微安很適合電池供電。電池電壓檢測是很多新手容易忽略的地方。鋰電池電壓范圍3.0V到4.2V不能直接接ADC引腳需要用兩個(gè)電阻分壓后再采樣。選電阻時(shí)要算好分壓比比如用100K和100K把電壓減半最大只有2.1V安全落在STM32的3.3V ADC范圍內(nèi)。但要注意電阻分壓會(huì)持續(xù)消耗電池電流為了省電可以在分壓電阻和電池之間加一個(gè)MOS管平時(shí)關(guān)閉需要檢測電量時(shí)再打開每次只打開幾毫秒。低功耗設(shè)計(jì)上STM32可以進(jìn)入STOP模式配合RTC喚醒但畢設(shè)階段不一定要求這個(gè)老師更看重你有沒有這個(gè)意識(shí)。如果你能在論文里寫下“系統(tǒng)在休眠時(shí)關(guān)閉傳感器和OLED電流從35mA降到1.2mA”這已經(jīng)是一個(gè)不小的加分項(xiàng)。3. 軟件架構(gòu)和算法實(shí)現(xiàn)源碼不是堆出來的3.1 工程目錄怎么劃分讀代碼才不累很多畢設(shè)源碼是把所有代碼塞進(jìn)一個(gè)main.c幾千行看著頭皮發(fā)麻。這種代碼即使功能正常也很容易在答辯時(shí)被老師挑刺。我建議按模塊拆分目錄至少分成這樣main.c初始化外設(shè)進(jìn)入主循環(huán)。bsp.c / bsp.h板級(jí)支持包封裝LED、按鍵、OLED、傳感器初始化函數(shù)。app.c / app.h業(yè)務(wù)邏輯比如頁面切換、參數(shù)設(shè)置、報(bào)警處理。algorithm.c / algorithm.h計(jì)步算法、心率算法、濾波函數(shù)。driver存放廠商提供的驅(qū)動(dòng)比如MPU6050、MAX30102、SSD1306。這樣做的好處是答辯時(shí)你可以直接說“這里是驅(qū)動(dòng)層只負(fù)責(zé)寄存器讀寫這里是應(yīng)用層負(fù)責(zé)數(shù)據(jù)處理和界面跳轉(zhuǎn)”一句話就能體現(xiàn)架構(gòu)意識(shí)。源碼閱讀者也更容易快速定位問題顯示亂了去查app數(shù)據(jù)跳變?nèi)ゲ閍lgorithm沒反應(yīng)去查bsp。3.2 數(shù)據(jù)采集與濾波傳感器原始數(shù)據(jù)的“洗干凈”方法MPU6050讀出來的加速度數(shù)據(jù)不可能直接用。手腕擺動(dòng)時(shí)會(huì)有大量噪聲直接算步數(shù)會(huì)飄得離譜。我常用的辦法是滑動(dòng)平均濾波維護(hù)一個(gè)長度為N的環(huán)形緩沖區(qū)每來一個(gè)新數(shù)據(jù)剔除最老的數(shù)據(jù)求剩余數(shù)據(jù)的平均值。N通常取8到16太短濾不干凈太長會(huì)引入滯后讓計(jì)步反應(yīng)變慢。如果對(duì)實(shí)時(shí)性要求更高還可以用一階低通濾波公式是filtered alpha * new_sample (1 - alpha) * filtered;alpha值取0.2到0.4比較合適。這個(gè)公式簡單代碼量幾乎為零在MCU上跑非??臁?duì)于加速度計(jì)這種連續(xù)變化的信號(hào)一階低通配合滑動(dòng)平均已經(jīng)能應(yīng)對(duì)大部分噪聲。心率傳感器MAX30102的數(shù)據(jù)處理更麻煩。它需要先設(shè)置采樣率和LED電流然后讀取PPG數(shù)據(jù)。我建議把采樣率設(shè)在100Hz左右通過定時(shí)器中斷均勻采樣。原始PPG信號(hào)會(huì)有基線漂移可以用高速濾波去掉直流分量再用低通濾波濾除高頻噪聲。畢設(shè)階段不用做得很復(fù)雜能穩(wěn)定顯示波形、算出一個(gè)看得過去的心率值就夠了。3.3 計(jì)步和心率算法原理與簡化實(shí)現(xiàn)計(jì)步算法的核心思想是檢測加速度矢量的周期性波動(dòng)。每走一步加速度大小會(huì)先增大再減小出現(xiàn)一個(gè)波峰。所以算法步驟是先計(jì)算三軸加速度的合成幅值magnitude sqrt(ax*ax ay*ay az*az);然后對(duì)幅度做高通濾波去掉重力分量。設(shè)定一個(gè)閾值比如重力加速度的15%到20%當(dāng)濾波后的幅度超過閾值時(shí)記一個(gè)“候選步”再通過時(shí)間窗口判斷比如兩個(gè)波峰間隔必須在250ms到2000ms之間避免把抖動(dòng)當(dāng)步數(shù)。偽代碼如下static float threshold 0.15f; static uint32_t last_step_time 0; static uint32_t step_count 0; void step_detect(float magnitude) { float filtered highpass(magnitude); if (filtered threshold) { uint32_t now HAL_GetTick(); if (now - last_step_time 250 now - last_step_time 2000) { step_count; last_step_time now; } } }這個(gè)簡化算法在正常走路時(shí)準(zhǔn)確率能達(dá)到90%左右。如果要進(jìn)一步提升可以加入步頻檢測、動(dòng)態(tài)閾值、靜止檢測等但那是加分項(xiàng)。心率算法更復(fù)雜簡化版是把PPG信號(hào)按1秒分成一段找這段區(qū)間內(nèi)的峰值和波谷然后計(jì)算峰值間平均間隔再換算成每分鐘心跳數(shù)。注意運(yùn)動(dòng)偽影很嚴(yán)重戴在手上跑步時(shí)基本沒法用所以畢設(shè)里最好寫清楚“本系統(tǒng)側(cè)重靜息心率測量運(yùn)動(dòng)場景下準(zhǔn)確性會(huì)下降”。承認(rèn)限制不可怕可怕的是測試條件寫不清楚老師一眼看出你在糊弄。3.4 OLED界面菜單用狀態(tài)機(jī)代替一堆if elseOLED顯示界面如果直接在main函數(shù)里寫if else功能一多就亂。我用的方法是狀態(tài)機(jī)typedef enum { PAGE_MAIN, PAGE_STEPS, PAGE_HEARTRATE, PAGE_TEMPERATURE, PAGE_SETTINGS, } PageState; static PageState current_page PAGE_MAIN; void page_key_handle(uint8_t key) { switch (current_page) { case PAGE_MAIN: if (key KEY_NEXT) current_page PAGE_STEPS; break; case PAGE_STEPS: if (key KEY_NEXT) current_page PAGE_HEARTRATE; else if (key KEY_BACK) current_page PAGE_MAIN; break; // ... } }狀態(tài)機(jī)的好處是每次只有“當(dāng)前狀態(tài)”和“觸發(fā)事件”兩個(gè)變量邏輯清晰不容易出bug。頁面上要顯示哪些數(shù)據(jù)可以放在對(duì)應(yīng)狀態(tài)的refresh函數(shù)里主循環(huán)每100ms調(diào)用一次刷新。OLED內(nèi)部有顯存操作時(shí)先改顯存再一次性刷新到屏幕能顯著減少閃爍。主循環(huán)我建議控制頻率不要while(1)瘋狂刷屏??梢杂靡粋€(gè)時(shí)間片輪詢的思路uint32_t tick HAL_GetTick(); if (tick - last_heart_tick 20) { read_heart(); last_heart_tick tick; } if (tick - last_ui_tick 100) { ui_update(); last_ui_tick tick; } if (tick - last_step_tick 50) { step_detect(); last_step_tick tick; }這樣不僅代碼好讀實(shí)際運(yùn)行也更流暢。4. 文檔說明與答辯資料怎么把項(xiàng)目“講明白”4.1 畢設(shè)論文的正文結(jié)構(gòu)拿到一份完整的畢設(shè)項(xiàng)目光會(huì)跑代碼沒用還得會(huì)寫文檔。論文的基本結(jié)構(gòu)我建議這樣安排第一章 緒論寫智能手環(huán)的研究背景、意義、國內(nèi)外現(xiàn)狀。別直接復(fù)制百度百科改寫成自己的話重點(diǎn)強(qiáng)調(diào)“輕量級(jí)、低功耗、可穿戴”。第二章 系統(tǒng)總體設(shè)計(jì)放系統(tǒng)框圖畫出主控、傳感器、顯示、電源、藍(lán)牙等模塊的關(guān)系然后做功能需求分析。第三章 硬件設(shè)計(jì)對(duì)應(yīng)我上面說的電路選型和原理每個(gè)模塊單獨(dú)一節(jié)包括芯片選型理由、接口電路、元器件參數(shù)計(jì)算。第四章 軟件設(shè)計(jì)按模塊寫流程配狀態(tài)轉(zhuǎn)換圖、算法流程圖、關(guān)鍵代碼片段。計(jì)步算法這里值得單獨(dú)寫一節(jié)。第五章 系統(tǒng)測試列出測試環(huán)境、測試表格比如不同行走速度下的計(jì)步準(zhǔn)確率、靜息心率誤差、續(xù)航時(shí)間、OLED刷新表現(xiàn)。第六章 總結(jié)與展望總結(jié)你做了什么指出不足然后寫未來可以增加藍(lán)牙App、手勢識(shí)別、睡眠監(jiān)測等。如果項(xiàng)目是“源碼文檔說明全部資料”的形式這份文檔就是你最后的門面。寫的時(shí)候注意圖文并茂不要大段貼代碼老師沒那么多耐心看代碼要把代碼思路抽出來用流程圖和文字講清楚。4.2 圖表、流程圖和實(shí)物圖的重要性圖表永遠(yuǎn)是答辯評(píng)委最容易抓的“閱讀錨點(diǎn)”。我的經(jīng)驗(yàn)是每一章至少配兩張圖一張結(jié)構(gòu)圖一張流程圖或?qū)嵨飯D。硬件部分配電路連接圖、PCB截圖、整機(jī)照片軟件部分配狀態(tài)轉(zhuǎn)換圖、算法流程圖、OLED界面示意圖。你不需要畫得多專業(yè)用Visio、Draw.io甚至PPT都能做只要要素齊全、箭頭清楚。實(shí)物圖一定要用工整的接線和干凈的背景重拍。很多同學(xué)實(shí)物圖上面沾滿膠帶和飛線老師第一印象就不好。你可以把系統(tǒng)板放在亞克力底座上用彩色杜邦線分類綁扎拍近景和遠(yuǎn)景各一張放到論文和PPT里。越是“看起來專業(yè)”越容易給評(píng)委留下好印象。4.3 答辯演示的節(jié)奏控制與高頻問題答辯演示講究“臺(tái)上一分鐘臺(tái)下十年功”。我的演示流程是先展示手環(huán)外觀然后開機(jī)顯示主界面逐一進(jìn)入步數(shù)、心率、溫度頁面再模擬一次計(jì)步走幾步最后用手按住心率傳感器展示波形。整個(gè)過程控制在5分鐘內(nèi)不要講到藍(lán)牙傳輸和低功耗細(xì)節(jié)那是留給老師提問的。高頻問題至少準(zhǔn)備這幾個(gè)為什么選STM32F103而不是MSP430或ESP32 答開發(fā)資料多、外設(shè)豐富、低功耗模式夠用而且畢設(shè)階段性價(jià)比高。計(jì)步算法怎么排除非走路動(dòng)作 答用了高通濾波和波峰時(shí)間間隔約束能排除大部分抖腕、甩手干擾。心率傳感器在運(yùn)動(dòng)時(shí)準(zhǔn)確嗎 答本系統(tǒng)偏靜息測量運(yùn)動(dòng)場景下會(huì)因?yàn)檫\(yùn)動(dòng)偽影導(dǎo)致誤差這是當(dāng)前方案的限制。如何降低系統(tǒng)功耗 答OLED只在更新時(shí)刷新傳感器按需采樣主控可以進(jìn)入STOP模式。電池能用多久 答實(shí)測待機(jī)約XX小時(shí)正常工作約XX小時(shí)已經(jīng)接入低功耗處理。提前把這些問題和答案寫在文檔的“答辯常見問題”一頁上臺(tái)前多讀兩遍絕對(duì)比臨時(shí)想詞要好。5. 實(shí)測中繞不開的坑從OLED閃爍到I2C通信異常5.1 OLED刷新閃爍罪魁禍?zhǔn)淄侵餮h(huán)太慢很多人在調(diào)試時(shí)發(fā)現(xiàn)OLED顯示的數(shù)字一跳一跳甚至閃爍。第一個(gè)念頭是OLED壞掉了其實(shí)大部分情況是主循環(huán)里有耗時(shí)操作。比如你每次采集傳感器后就馬上顯示傳感器I2C通信一次要好幾毫秒主循環(huán)被拖慢OLED刷新頻率就掉下去了。解決辦法有三個(gè)第一把I2C讀取放到定時(shí)器中斷或狀態(tài)機(jī)里主循環(huán)只負(fù)責(zé)“是否有新數(shù)據(jù)”的判斷第二OLED使用SPI模式并開啟DMA把顯存數(shù)據(jù)搬運(yùn)從CPU手里解放出來第三采用局部刷新只更新變化區(qū)域。我最推薦第三種因?yàn)榇a改動(dòng)最小只需要在寫顯存時(shí)判斷一下舊值和新值是否相同相同就跳過寫操作。手環(huán)界面基本是數(shù)字變化局部刷新后效果立竿見影。5.2 I2C通信偶發(fā)失敗上拉電阻和時(shí)鐘速率MPU6050和MAX30102都用I2C如果總線上設(shè)備多了就容易偶發(fā)通信失敗比如讀到全0或者同時(shí)掛兩條總線時(shí)互相干擾。我第一次遇到時(shí)排查了整整半天最后發(fā)現(xiàn)是模塊自帶上拉電阻和主控上拉電阻重復(fù)導(dǎo)致總線上拉太強(qiáng)通信時(shí)序變形。解決方法是確認(rèn)每個(gè)傳感器模塊是否已經(jīng)自帶上拉電阻。像GY-521模塊默認(rèn)有4.7K上拉如果STM32內(nèi)部也開啟了上拉組合后的等效電阻可能只有2.3K對(duì)標(biāo)準(zhǔn)I2C來說驅(qū)動(dòng)能力過強(qiáng)。相比之下配置為開漏輸出、外部只保留一組4.7K上拉是最穩(wěn)的。另外I2C時(shí)鐘速率可以降到100KHz犧牲一點(diǎn)速度換穩(wěn)定性。代碼里給每次傳輸加超時(shí)處理如果返回錯(cuò)誤就重新初始化一次很多偶發(fā)問題都能被“重試”掩蓋掉。5.3 電池電量顯示不準(zhǔn)不要直接讀ADC電池電壓通過分壓電阻送進(jìn)ADC但直接讀出來的值跳動(dòng)很大充滿電顯示120%用了一會(huì)兒就掉到80%再一會(huì)兒又回升。這是因?yàn)殡姵仉妷涸谪?fù)載變化時(shí)會(huì)有波動(dòng)OLED亮起時(shí)電壓瞬間跌落OLED熄滅時(shí)電壓回升。改進(jìn)思路是軟件上做三點(diǎn)第一多次采樣求平均第二只在固定時(shí)間點(diǎn)采樣比如每10秒采樣一次避開OLED刷新和傳感器讀取的瞬間第三用開路電壓法估算電量先測出滿電4.2V和空電3.3V對(duì)應(yīng)的ADC值再用線性或折線映射。雖然不能像手機(jī)那樣精準(zhǔn)但用來顯示“低電量、中等電量、滿電”三檔已經(jīng)夠用。如果硬件允許還可以加一個(gè)MOS管斷開分壓電阻的長期電流。很多低功耗設(shè)計(jì)都在這里做文章我也在資料里專門寫了這個(gè)電路按著搭就能減少電池額外消耗。5.4 RTC時(shí)間偏差外部晶振與軟件校準(zhǔn)手環(huán)必須顯示時(shí)間STM32內(nèi)部RTC的時(shí)鐘源選擇很重要。如果用了內(nèi)部LSI RC振蕩器誤差會(huì)比較大一天可能偏幾十秒如果改接一個(gè)32.768KHz的外部晶振誤差能降到每天幾秒。但外部晶振需要嚴(yán)格匹配負(fù)載電容一般6pF到12.5pF選錯(cuò)電容可能導(dǎo)致不起振。如果RTC還是走不準(zhǔn)可以做軟件校準(zhǔn)隔一小時(shí)和標(biāo)準(zhǔn)時(shí)間對(duì)比一次算出偏差在代碼里每小時(shí)補(bǔ)償幾秒。這個(gè)思路在文檔里寫出來老師會(huì)覺得你有工程意識(shí)。另外要注意RTC最好用BKP寄存器保存校準(zhǔn)參數(shù)否則斷電重啟后校準(zhǔn)就丟了。調(diào)試RTC時(shí)最煩的是下載程序后時(shí)間復(fù)位。解決辦法是設(shè)定一個(gè)標(biāo)志位寫入備份寄存器如果備份寄存器里的值是特定魔數(shù)說明RTC已經(jīng)初始化過不要重新設(shè)置時(shí)間否則才執(zhí)行初始寫時(shí)間。這樣每次燒錄程序后時(shí)間能繼續(xù)保持不需要每次手動(dòng)輸入。6. 拿到這套源碼資料后怎么變成自己的東西6.1 先燒錄、再拆解、最后才改代碼我見過許多同學(xué)把源碼下載下來后直接打開main.c一行一行地讀讀了幾百行就放棄了。更高效的做法是先編譯燒錄讓系統(tǒng)跑起來看實(shí)際現(xiàn)象然后改一段代碼觀察變化比如把OLED刷新時(shí)間改慢把計(jì)步閾值改大把某個(gè)狀態(tài)機(jī)的初始頁面改掉。通過“破壞性實(shí)驗(yàn)”理解每段代碼的作用比靜態(tài)閱讀快得多。硬件也一樣先拿著資料里的原理圖逐個(gè)量電壓、量信號(hào)確認(rèn)每個(gè)模塊工作正常。不要一上來就拆傳感器、換芯片要先建立“系統(tǒng)是好的”這個(gè)信心再開始做個(gè)性化修改。6.2 明確每個(gè)源文件的職責(zé)給源碼加注釋一份“高分項(xiàng)目”的源碼注釋一定清楚。我的習(xí)慣是每個(gè)文件開頭寫一段模塊說明包括功能、主要接口、依賴關(guān)系每個(gè)關(guān)鍵函數(shù)前面寫輸入輸出和調(diào)用時(shí)機(jī)遇到特殊計(jì)算公式在邊上寫上公式來源和參數(shù)含義。比如計(jì)步算法里的閾值為什么取0.15我會(huì)專門寫一句注釋因?yàn)檎P凶邥r(shí)垂直方向加速度波動(dòng)幅度約為重力加速度的10%到20%閾值取中間值能減少零散抖動(dòng)。這種注釋直接體現(xiàn)你對(duì)算法原理的理解老師在檢查源碼看到這些細(xì)節(jié)分?jǐn)?shù)會(huì)明顯不一樣。6.3 從手環(huán)到產(chǎn)品增加藍(lán)牙和App的擴(kuò)展路徑如果你覺得純手環(huán)界面不夠出彩可以把手環(huán)和手機(jī)連起來。最省事的方案是加一個(gè)HC-08藍(lán)牙串口模塊用UART和STM32通信手機(jī)端用一個(gè)現(xiàn)成的“藍(lán)牙串口”App接收數(shù)據(jù)或者自己寫一個(gè)簡單的微信小程序。不需要做實(shí)時(shí)同步只要在手機(jī)端能顯示步數(shù)、心率、溫度并支持時(shí)間校準(zhǔn)項(xiàng)目檔次就提升了一大截。藍(lán)牙模塊本身代碼量不大初始化UART、接收藍(lán)牙指令、解析數(shù)據(jù)、打包發(fā)送。關(guān)鍵是協(xié)議設(shè)計(jì)我建議用字符串協(xié)議比如發(fā)送“STEPS:1234\n”方便調(diào)試等穩(wěn)定后再換成二進(jìn)制協(xié)議。資料里已經(jīng)放了我常用的協(xié)議文檔直接拿來改一下功能碼就能用。6.4 項(xiàng)目經(jīng)歷在簡歷上的正確寫法這是畢設(shè)之外同樣重要的事。很多人在簡歷上只寫“基于STM32的智能手環(huán)”看起來平平無奇。更好的寫法是分條目描述負(fù)責(zé)手環(huán)硬件電路設(shè)計(jì)完成STM32主控、MPU6050、MAX30102、OLED顯示、鋰電池充電模塊的選型與接線。完成傳感器驅(qū)動(dòng)開發(fā)基于I2C/SPI接口讀取加速度、心率、溫濕度數(shù)據(jù)編寫滑動(dòng)平均和低通濾波算法。設(shè)計(jì)狀態(tài)機(jī)頁面管理框架實(shí)現(xiàn)多級(jí)菜單、按鍵交互、低功耗模式聯(lián)合調(diào)通計(jì)步、心率、溫度顯示功能。實(shí)測數(shù)據(jù)平路步數(shù)準(zhǔn)確率93%靜息心率誤差±5BPM待機(jī)電流約1.2mA。這樣寫一個(gè)關(guān)鍵詞對(duì)應(yīng)一個(gè)能力點(diǎn)HR和面試官才能快速抓到重點(diǎn)。不要小看這些描述同一個(gè)項(xiàng)目表達(dá)方式不同價(jià)值完全不同。最后再分享一點(diǎn)我的體會(huì)做這類嵌入式畢設(shè)最大的收獲往往不是代碼跑通那一刻而是你在反復(fù)拆解、調(diào)試、寫文檔過程中形成的對(duì)系統(tǒng)整體和各模塊間關(guān)系的理解。哪怕你后續(xù)不再接觸STM32這種“先定方案、再搭硬、后寫軟、最后沉淀文檔”的工作方式也值得帶進(jìn)下一份工作。希望這篇筆記能讓你少走幾段我走過的彎路把“智能手環(huán)”這個(gè)題目真正做成一份拿得出手的高分作品。本文還有配套的精品資源點(diǎn)擊獲取