實戰(zhàn):從ADC精度到實時固件架構(gòu))
做基于MCU的實驗室儀器Lab Instruments這個事我前前后后折騰了快五年。一開始是給實驗室搭一套溫度采集系統(tǒng)后來逐漸做到LCR表級別的阻抗分析、微型光譜儀的信號鏈甚至給無人機遙控器的地面站做了個通道校準儀。這一類項目有個共同點看起來好像就是“單片機加傳感器再連個屏”但真要把精度、穩(wěn)定性和實時性做到位里面全是細節(jié)。這篇東西我打算把從選型到固件再到調(diào)試的完整思路捋一遍重點講清楚那些只會在實操中踩到的坑。這套內(nèi)容適合兩類人。一類是剛接手儀器類項目的嵌入式工程師很多坑我在踩過之后能幫你直接繞開另一類是想把MCU性能榨干的老手尤其是對ADC精度、實時閉環(huán)控制和多任務(wù)調(diào)度有要求的人。我會結(jié)合STM32H7、TI AM261x這類常見器件來講也會提到普冉這類國產(chǎn)MCU在開發(fā)環(huán)境上的特殊處理。總之核心不是教你抄一份代碼而是建立一套做儀器類項目的方法論。1. 整體設(shè)計思路核心是需求拆分而不是先選芯片1.1 實驗室儀器到底需要什么樣的MCU很多剛上手的人會犯一個錯上來就選個頂級芯片覺得主頻高、內(nèi)存大就一定能把儀器做好。其實實驗室儀器對MCU的需求通常集中在三件事上。第一是模擬鏈路的精度ADC和DAC的位數(shù)、參考電壓的溫漂、信號調(diào)理電路的噪聲這些決定了這臺儀器測出來的數(shù)可不可信。第二是實時控制能力比如FOC電機控制里的電流環(huán)、鎖相放大器的參考信號生成、PID反饋閉環(huán)都要求MCU在微秒級內(nèi)完成計算和輸出更新。第三是數(shù)據(jù)交互與通信上位機指令解析、波形上傳、固件升級這些需要穩(wěn)定的協(xié)議棧和足夠的通信帶寬。我做過一個簡單的對照拿我常用的幾類芯片來舉例需求場景代表芯片選型理由代價高精度采樣為主STM32H743內(nèi)置16位ADC、FMAC和CORDIC加速器FOC運算幾乎不占主核功耗偏高封裝大多通道同步采集TI AM261x異構(gòu)架構(gòu)有專用實時控制核適合多回路并行開發(fā)復(fù)雜度高低成本電化學儀器普冉PY32F002Cortex-M0內(nèi)核夠跑基本采樣和邏輯資源緊張需精簡代碼有一點我特別想強調(diào)儀器的精度20%靠MCU選型80%靠你外圍電路和固件算不算法對。你用一個16位ADC但參考電壓是直接從LDO拉的那實際有效位數(shù)可能不到10位。反過來說用普通12位ADC配上低噪聲運放和軟件過采樣也能做到等效14位的效果。1.2 方案選型的兩個核心判斷標準第一個判斷標準是信號鏈的瓶頸分析。你先確定傳感器出來的信號大小、帶寬、阻抗和噪聲水平倒推需要的ADC位數(shù)、采樣率和前端增益。舉個例子做電化學伏安法檢測電流信號通常在μA級別那你需要的不是更高分辨率的ADC而是一個低偏置電流的跨阻放大器把電流轉(zhuǎn)成2-3V的電壓再進ADC。第二個判斷標準是實時環(huán)路的算力估算。以FOC電機控制為例典型的電流環(huán)執(zhí)行周期是10-20kHz每次需要做Clarke變換、Park變換、PID計算、反Park變換和SVPWM調(diào)制。STM32H7系列內(nèi)置硬件CORDIC和FMAC一次完整的變換計算可以從軟件純算的幾十微秒壓縮到幾微秒。你在選型時不要只看主頻要看有沒有針對性的硬件加速器。我自己踩過一個很深的坑一開始用某國產(chǎn)M3核單片機做電機電流環(huán)主頻72MHz也不算低但算一次SVPWM加PID要將近15微秒10kHz中斷加起來根本跑不滿。后來換了帶FPU和硬件除法器的芯片同樣的算法直接縮到3微秒以內(nèi)。這件事讓我意識到算力評估一定要具體到每個數(shù)學運算的周期數(shù)而不是籠統(tǒng)看主頻。1.3 別忽略研發(fā)效率這個隱形指標選型這件事容易被忽略的是開發(fā)工具鏈和生態(tài)。比如普冉MCU價格便宜、貨源穩(wěn)定但官方SDK和文檔相對薄弱用VS Code搭建開發(fā)環(huán)境時就沒有ST那樣成熟的工具鏈支持。如果你的項目周期緊張優(yōu)先選那些你能快速上手、社區(qū)資料豐富的芯片。如果非要選偏門型號就要預(yù)留出至少一到兩周的環(huán)境搭建和踩坑時間。2. 深入ADC信號鏈從工作原理到精度實踐2.1 理解SAR ADC的工作節(jié)奏是第一步現(xiàn)在MCU里集成的ADC絕大多數(shù)是逐次逼近型SAR它的基本原理說簡單點就是拿一個內(nèi)部DAC不斷和你輸入信號比大小從最高位到最低位逐位逼近。STM32H7的16位ADC和普通12位ADC在工作原理上沒本質(zhì)區(qū)別都是靠比較器和電容陣列CDAC完成轉(zhuǎn)換。你如果看完這個原理就應(yīng)該明白一件事ADC的輸入引腳在采樣瞬間會開一個采樣電容這個電容會從你的信號源“吸”走一定電荷。如果信號源輸出阻抗太高采樣瞬間的電壓就會被拉低導(dǎo)致測量結(jié)果偏低尤其是在測量高阻抗傳感器和高頻信號時會很明顯。解決方式有兩個。一是降低信號源阻抗通常在ADC輸入端加一個運放緩沖器用低輸出阻抗去驅(qū)動ADC的采樣電容。二是延長采樣時間STM32的ADC可以配置采樣周期數(shù)比如從1.5個周期拉長到810個周期這樣采樣電容就有足夠時間充到穩(wěn)定值。但這會犧牲吞吐率需要平衡。2.2 參考電壓被忽視的精度天花板我見過太多人買了高分辨率ADC的開發(fā)板結(jié)果一測發(fā)現(xiàn)噪聲大得離譜排查半天發(fā)現(xiàn)是參考電壓引腳直接接到了3.3V電源上。你要知道ADC輸出的數(shù)字量本質(zhì)是輸入電壓和參考電壓的比例值參考電壓抖一下子你的所有測量結(jié)果都會跟著抖。這個比例關(guān)系是數(shù)字輸出 Vin / Vref × 2^N。如果Vref不穩(wěn)定就等于在模擬信號上疊加了一個乘性噪聲。在儀器類項目里參考電壓必須用專門的基準源芯片。常用的是REF50252.5V溫漂3ppm/°C或者ADR4525這類低噪聲基準。PCB布局上要注意基準輸出到ADC的VREF引腳走線要短、要粗旁邊加一個1-10uF的鉭電容并聯(lián)一個100nF陶瓷電容壓低高頻和低頻噪聲。另外如果你用同一個LDO既給模擬電路供電又給數(shù)字電路供電那基本就告別高精度了。數(shù)字引腳翻轉(zhuǎn)時會產(chǎn)生大量地彈噪聲通過電源網(wǎng)絡(luò)耦合到模擬部分。我也不是說要你用完全隔離的兩路電源但至少要在模擬供電入口加一個磁珠或者LC濾波把數(shù)字和模擬電源域分開。2.3 過采樣用軟件換精度的有效手段如果你的MCU自帶的ADC只有12位但儀器需要14位以上的分辨率可以通過過采樣加均值來實現(xiàn)。原理是白噪聲是隨機分布的采樣四次平均信噪比能提升約3dB對應(yīng)約0.5位有效精度。每多采樣4倍并取平均就能多獲得約0.5位的有效分辨率。想從12位提升到14位理論上需要過采樣4^216倍也就是每個輸出數(shù)據(jù)點要采16次。但要注意這個方法只對高斯白噪聲有效如果噪聲里有固定頻率的干擾比如50Hz工頻你過采樣多少次都消不掉。遇到這種問題正確的做法是先在信號鏈里做好工頻陷波或者采用相關(guān)的數(shù)字濾波算法。我一般習慣在過采樣之后加一個滑動平均或者IIR濾波效果比單純過采樣好很多。3. 核心固件架構(gòu)保證實時性又不丟數(shù)據(jù)3.1 DMA雙緩沖是數(shù)據(jù)采集的基本盤做儀器類固件最忌諱的就是在中斷里用阻塞方式讀ADC數(shù)據(jù)。如果你采樣的速率是100kHz每10微秒來一次中斷你在中斷里做幾行除法都可能超時更別提還要做濾波、存儲、通信。我在項目里一律采用DMA雙緩沖模式。這個模式的思路是ADC轉(zhuǎn)換完成后由DMA自動把結(jié)果搬運到內(nèi)存緩沖區(qū)不需要CPU介入。當緩沖A填滿時DMA自動切換到緩沖B繼續(xù)填充同時觸發(fā)中斷通知CPU去處理緩沖A里的數(shù)據(jù)。這樣CPU處理數(shù)據(jù)和DMA采集數(shù)據(jù)完全并行不會互相拖累。關(guān)鍵配置是DMA的中斷優(yōu)先級要高于普通外設(shè)中斷且緩沖區(qū)的內(nèi)存要放在SRAM且對齊到緩存行Cortex-M7如果開了D-Cache這一步尤其重要否則會出現(xiàn)緩存一致性問題。STM32H7在開啟D-Cache的情況下DMA寫入的數(shù)據(jù)不會馬上被CPU看到必須在DMA傳輸完成中斷里做SCB_InvalidateDCache_by_Addr操作否則你讀到的還是舊數(shù)據(jù)。3.2 用狀態(tài)機而不是RTOS處理多任務(wù)儀器固件通常需要同時處理采樣、UI刷新、通信、按鍵掃描等任務(wù)。很多人第一反應(yīng)是引入RTOS比如FreeRTOS。但以我的經(jīng)驗除非你的系統(tǒng)真的需要嚴格的多線程調(diào)度和任務(wù)間通信否則裸機狀態(tài)機是更穩(wěn)的選擇。原因也很簡單RTOS的調(diào)度延遲和上下文切換開銷雖然不大但在高精度采樣場景下你很難保證某個關(guān)鍵任務(wù)能精確地在某個時刻被執(zhí)行。狀態(tài)機把所有任務(wù)拆成有限狀態(tài)配合定時器輪詢和中斷優(yōu)先級管理執(zhí)行時序是完全確定的排錯也容易。我常用的架構(gòu)是這樣SysTick產(chǎn)生1ms時基作為主循環(huán)心跳ADC和DMA在后臺持續(xù)運行。主循環(huán)里跑狀態(tài)機狀態(tài)A處理按鍵掃描和菜單邏輯狀態(tài)B處理實時數(shù)據(jù)緩存和顯示刷新狀態(tài)C處理通信協(xié)議解析。每個狀態(tài)的執(zhí)行時間被嚴格控制在1ms以內(nèi)跑完一圈回到起點。3.3 測量算法濾波、校準與線性化現(xiàn)實世界的傳感器幾乎沒有完美線性的。做儀器必須要有校準和線性化這一步。我的做法是出廠前用標準源做多點校準在每個校準點記錄ADC原始值和實際物理量然后擬合成多項式或查表。運行時把ADC值代入校正算法就能得到比原始量程精度高得多的結(jié)果。以熱電偶為例溫度與電壓的關(guān)系是分度表直接查表會更準。此時MCU里要存一個斷點分度表用線性插值計算中間值。這個查表插值的過程對MCU來說代價很低但能把精度從單純查表的1°C提升到0.1°C以內(nèi)。4. 開發(fā)環(huán)境實操VS Code下完成從零到下載調(diào)試4.1 搭建cortex-m系列通用開發(fā)環(huán)境不管你用的是ST、TI還是普冉只要是Cortex-M系列內(nèi)核開發(fā)環(huán)境的搭建思路基本是相通的。我目前主力用VS Code加四條工具鏈arm-none-eabi-gcc編譯器、OpenOCD調(diào)試器、Cortex-Debug插件、Makefile或CMake構(gòu)建系統(tǒng)。這套組合免費、跨平臺而且比某些廠商自己的IDE更輕量。安裝流程大概是先裝VS Code和插件然后裝ARM GCC工具鏈最后裝OpenOCD。以普冉MCU為例調(diào)試器可以用DAPLink或者J-Link在launch.json里指定device類型和接口速度就行。我踩過的坑是OpenOCD默認的CMSIS-DAP配置文件有時會對特定MCU報錯需要根據(jù)芯片的Flash大小和RAM起始地址手動調(diào)整。另外普冉的某些型號默認代碼讀保護是開啟的第一次連接前就要注意否則可能直接把芯片鎖死。真鎖死了也別慌運氣好在boot引腳拉低后擦除全片就能救回來。4.2 構(gòu)建腳本里的兩個關(guān)鍵點編寫Makefile時要特別注意鏈接腳本.ld文件。MCU的內(nèi)部Flash和RAM地址必須和手冊嚴格對應(yīng)這個不對的話代碼燒進去要么跑不起來要么直接進HardFault。另外一個關(guān)鍵是-specsnano.specs和-u _printf_float的處理如果要用printf打印浮點數(shù)必須把float打印功能鏈接進來否則串口輸出全是亂碼或者直接不輸出。但鏈接了浮點打印固件體積會增大幾十KB對Flash緊張的小容量MCU來說要慎重。4.3 在線調(diào)試時的實用觀察手法真正調(diào)試儀器算法時我很少單步執(zhí)行更多是用實時變量監(jiān)視和波形圖。Cortex-Debug插件支持SWO引腳輸出可以把printf重定向到一個單獨的調(diào)試通道不占用串口。這個功能在調(diào)試實時控制算法時真的很好用因為它不會因為調(diào)試而影響程序運行時序。配置方法是在啟動文件里初始化SWO然后在代碼里用ITM_SendChar輸出調(diào)試信息。5. 儀器通信接口設(shè)計讓數(shù)據(jù)更穩(wěn)定地上位5.1 MCU串口接收端口是否需要上拉這個問題在網(wǎng)上被問得非常多原因是不同教程給出了相反的答案。關(guān)于MCU串口接收端RX是否需要上拉核心要看空閑狀態(tài)時的電平是否確定。UART協(xié)議規(guī)定總線在空閑時必須保持高電平這樣起始位的下降沿才能被識別。如果你的RX引腳懸空且內(nèi)部沒有上拉那空閑狀態(tài)的電平就會處于不定區(qū)很容易產(chǎn)生誤中斷和雜波。大部分MCU的USART模塊在啟用時RX引腳的內(nèi)部上拉會默認開啟但內(nèi)部上拉電阻通常在30-50kΩ對于高速通信或長走線來說效果有限。我的建議是如果板內(nèi)走線短使用內(nèi)部上拉就夠了如果有外部連接器或線纜尤其超過10cm的最好在RX到GND之間加一個10kΩ外部上拉電阻。這一點在連接無人機接收機這類PWM轉(zhuǎn)串口設(shè)備時尤其重要因為它們的信號是單向的很多模塊并不會主動拉高TX空閑電平。5.2 儀器通信的精髓命令幀設(shè)計給儀器設(shè)計一個健壯的通信協(xié)議核心就是把“數(shù)據(jù)怎么打包”和“數(shù)據(jù)怎么解析”這兩件事標準化。我常用的幀格式是幀頭0xAA 0x55 設(shè)備地址 命令字 數(shù)據(jù)長度 數(shù)據(jù)部分 CRC校驗。CRC校驗這一項千萬不能省。很多人在初期聯(lián)調(diào)時覺得加CRC麻煩但一旦通信鏈路受到干擾數(shù)據(jù)錯一個字節(jié)你得到的測量結(jié)果可能錯得離譜而且極難排查。算CRC16的代價非常低幾微秒就夠但能幫你過濾掉幾乎所有偶發(fā)錯誤。解析時要注意幀超時處理收到字節(jié)后如果連續(xù)超過xxx毫秒沒接收完整就丟棄當前幀重新開始。避免長時間停在等待狀態(tài)。5.3 高速數(shù)據(jù)上傳的緩沖策略儀器的采樣率做高了以后串口就成了瓶頸。比如你以100kHz采樣每個點20字節(jié)那一秒鐘就是2MB這早就超過普通串口115200bps的能力了。我一般用USB虛擬串口或者以太網(wǎng)STM32H7自帶的USB HS加外部PHY可以做到幾十Mbps幾乎不占CPU。但USB還有個麻煩就是它的事務(wù)是以包為單位的你必須在固件里做好流控設(shè)置好合適的緩沖區(qū)不然很容易丟數(shù)據(jù)。6. 實戰(zhàn)案例從零做一臺簡易LCR表6.1 信號源和鑒相電路怎么配合拿一個我做過的迷你LCR表來說核心思路和理論說一遍。LCR表要測的是待測元件的阻抗和相位角方法是用DDS產(chǎn)生一個固定頻率的正弦波激勵經(jīng)過待測器件后用ADC采集電壓和電流信號再在MCU里做數(shù)字鑒相算出實部和虛部得到電阻、電容或電感值。這里有個關(guān)鍵點為了測出相位ADC必須對輸入信號和參考信號同步采樣。我在硬件上用的是兩路同步采樣ADC一路采激勵源的參考電壓一路采通過待測件的電流。如果在同一個MCU內(nèi)做這件事要注意兩路ADC觸發(fā)時序的同步否則相位就有固定的系統(tǒng)誤差。6.2 FFT頻域分析在MCU上的可行性很多人一聽到FFT就覺得MCU做不了。實際上做256點FFT在STM32H7這種帶FPU的芯片上耗時大約在幾百微秒到1毫秒之間完全可以在1kHz-10kHz激勵頻率下實時運行。關(guān)鍵是選用Cortex-M4/M7內(nèi)核或帶DSP指令集對FFT的蝶形運算會有明顯加速。如果你需要做更高分辨率的頻譜分析可以把FFT放在上位機做MCU只負責原始數(shù)據(jù)采集和傳輸。6.3 校準數(shù)據(jù)的存儲與管理每次上電開機儀器都需要把校準參數(shù)從Flash加載到RAM里關(guān)機時再保存回去。這里最怕的就是頻繁擦寫Flash廠家給的Flash擦寫壽命一般在1萬到10萬次之間。所以一定要把校準參數(shù)的存儲分成多區(qū)輪轉(zhuǎn)寫入每次只寫一個區(qū)寫滿才擦除。我一般用一個簡單的計數(shù)器做輪轉(zhuǎn)這樣即使每天校準10次也能用上好幾年。7. 常見問題與排查技巧實錄7.1 采樣數(shù)據(jù)跳變既不是噪聲也不是代碼問題有一次我做一塊四通道數(shù)據(jù)采集卡測量一通道正常二通道偶爾跳變幾十個數(shù)。排查思路我整理成了一張問題排查參考表現(xiàn)象可能原因排查方向單一通道數(shù)值跳變采樣引腳虛焊或受到數(shù)字信號串擾檢查PCB布線引腳加100pF濾波電容所有通道共模跳變參考電壓不穩(wěn)定用示波器看VREF波形檢查基準源輸出隨輸入電壓增大跳變越明顯運放接近輸出軌或超出線性區(qū)檢查運放輸出是否超過規(guī)格偶發(fā)大量錯誤數(shù)據(jù)DMA緩沖區(qū)越界或指針錯亂檢查DMA中斷處理時序增加環(huán)形緩沖保護那次最終發(fā)現(xiàn)是第二通道的數(shù)字地回流路徑被一條高速SPI線截斷干擾耦合進了模擬輸入。解決辦法是把SPI的IO改到另一組引腳把模擬部分的地平面完整鋪通就好了。這種問題用代碼是排查不出來的必須對PCB布局做檢查。另外ADC結(jié)果寄存器的數(shù)據(jù)對齊問題也容易踩坑。不同MCU的ADC結(jié)果寄存器數(shù)據(jù)對齊方式不一樣左對齊和右對齊相差很大如果你只讀了高字節(jié)可能得到的結(jié)果是真實值的256倍。初始化的時候要認真看手冊這是很多新手最常碰到的bug之一。7.2 中斷搶占導(dǎo)致實時控制偶爾抖一下實時控制系統(tǒng)最怕的是一切正常但偶爾出現(xiàn)一次控制量突變。這塊我在做FOC驅(qū)動時遇到過一次找了很久才發(fā)現(xiàn)是UART發(fā)送中斷和定時器中斷發(fā)生了競爭。UART一次發(fā)送一個字節(jié)發(fā)送完成中斷如果優(yōu)先級高于定時器中斷它就可能打斷正在執(zhí)行的電流環(huán)導(dǎo)致控制周期被拉長或者抖動。解決方法一個是把定時器中斷優(yōu)先級設(shè)為最高UART中斷降低讓控制環(huán)始終優(yōu)先執(zhí)行。另一個更徹底的辦法是UART發(fā)送改成DMA方式發(fā)送數(shù)據(jù)根本不進入CPU中斷徹底杜絕搶占問題。7.3 程序跑飛后不能自動恢復(fù)有些儀器需要長時間無人值守運行比如溫濕度記錄儀一旦程序卡死就會導(dǎo)致數(shù)據(jù)斷檔。這種場景建議開啟獨立看門狗IWDG在主循環(huán)里按要求周期地喂狗。但要注意看門狗超時時間必須大于所有中斷處理的最壞情況耗時否則系統(tǒng)在正常滿載工作時可能被誤復(fù)位。我一般取超時時間500ms到2s并在停機前用RTC保存現(xiàn)場狀態(tài)方便復(fù)位后快速恢復(fù)工作狀態(tài)。8. 真實分享儀器類MCU項目的幾條心得體會做MCU儀器和做普通消費電子最大的不同就在于“測量鏈路”的存在。消費電子你只要把功能跑通就完事儀器項目你得花一大半時間在精度、穩(wěn)定性、校準和驗證上。很多細節(jié)是我反復(fù)踩坑后才體會到的我挑幾條最直觀的和大家分享。第一永遠不要相信首次上電就是這么準的。任何儀器項目都要預(yù)留校準環(huán)節(jié)軟件里做一個校準模式硬件上預(yù)留標準源的接口。這樣量產(chǎn)或現(xiàn)場部署時才能把個體差異的誤差消掉。第二示波器是調(diào)試工具不是測量標準。我見過很多人在調(diào)試時直接拿示波器的“測量”功能當作可信的精度參考其實示波器的ADC只有8-12位用來做精度基準完全不夠。真正驗證精度要購買或借用一個更高精度的標準源或者校準過的萬用表作為參考。第三儀器的“手感”很重要。同樣是數(shù)據(jù)刷新的界面采樣后延遲50ms展示和實時展示使用者的主觀體驗差別巨大。做儀器固件的時候不要為了省電把刷新率壓的太狠要時刻關(guān)注從物理量變化到屏幕顯示出數(shù)值的總延遲時間。再一點經(jīng)驗如果你要在多個項目里復(fù)用代碼我建議把所有儀器相關(guān)模塊ADC采集、DMA緩沖、濾波算法、協(xié)議解析獨立成純C庫把硬件相關(guān)部分通過函數(shù)指針注入。這樣你從STM32換到普冉或者TI平臺時應(yīng)用層代碼幾乎不用改只需要適配BSP層。我現(xiàn)在的項目基本都是這種結(jié)構(gòu)新平臺的開發(fā)周期可以從兩個月壓縮到三周。做儀器類MCU開發(fā)最難的不是某個算法或多高的性能而是把精度、實時性、穩(wěn)定性、易用性這四個互相沖突的訴求壓在一個小芯片上做平衡。希望這篇長文能讓你少走一些彎路尤其是那些只會在真實項目中暴露出來的細節(jié)和坑。后續(xù)如果有機會我還會單獨寫一篇關(guān)于儀器固件測試驗證方法的文章講講怎么用自動化腳本把長時間穩(wěn)定性測試做扎實。