計:從硬件到標(biāo)定)
簡介面向STM32嵌入式開發(fā)與電源監(jiān)測場景這份資料提供了一套基于STM32F103的電流電壓檢測模塊完整實(shí)現(xiàn)方案適用于工業(yè)控制、物聯(lián)網(wǎng)設(shè)備中的實(shí)時電壓電流采集與遠(yuǎn)程監(jiān)控。壓縮包共183個文件約25.51MB核心包含C源碼h/c、Keil工程文件uvproj/uvprojx、已編譯固件hex/axf以及設(shè)計文檔pdf、PCB/截圖jpg/png/bmp和調(diào)試腳本bat等從源碼到硬件布局均有覆蓋。目前已有2236人學(xué)習(xí)下載。資源圍繞ADC采樣、霍爾電流傳感器/互感器信號處理、分壓電壓測量、軟件濾波與UART/Modbus通信等關(guān)鍵環(huán)節(jié)展開固件中包含ADC初始化、通道配置和數(shù)據(jù)處理代碼并附有Modbus Poll讀取說明用戶手冊和測試報告可幫助快速接線、配置與驗(yàn)證。整體對想在真實(shí)項目中快速落地電壓電流監(jiān)測功能的開發(fā)者和學(xué)生有較高參考價值。 前陣子給一套戶外電源系統(tǒng)做狀態(tài)監(jiān)測需要在主控端實(shí)時讀取電池電壓和充放電電流于是我順手用STM32搭了一個電流電壓檢測模塊。這個小模塊看著簡單——不就是ADC采樣嘛——真正落地的時候從采樣電路選型到軟件標(biāo)定每一步都有不少坑。這篇就把它完整拆一遍從方案選型、硬件設(shè)計到STM32端的采集、濾波與標(biāo)定給正在做類似項目的朋友一個能直接參考的模板。1. 項目整體思路與方案選型1.1 先想清楚你要測的是什么信號動手之前我習(xí)慣先問自己三個問題被測信號的變化范圍是多少要求的精度是多高現(xiàn)場有沒有強(qiáng)干擾以我的場景為例電池電壓大致在9V~14V之間波動充放電電流在0~3A范圍分辨率希望做到電壓0.01V、電流0.01A環(huán)境是戶外機(jī)箱開關(guān)電源在旁邊噪聲不小。這些約束直接決定了后續(xù)所有方案的選擇。如果是畢業(yè)設(shè)計或者智能家居小項目需求往往沒這么嚴(yán)格比如只測個鋰電池電壓、檢測設(shè)備是否過流那完全沒必要上高精度儀表放大器普通的電阻分壓加MCU內(nèi)置ADC就夠用。第一件事是把需求量化這決定了電路復(fù)雜度和成本。1.2 電流檢測方案橫向?qū)Ρ入娏鳈z測常見的方案就那幾種我在不同項目里都試過各有取舍方案原理優(yōu)點(diǎn)缺點(diǎn)適用場景采樣電阻運(yùn)放在回路串入低值電阻測壓降換算電流成本低、響應(yīng)快、精度可控插入損耗、需要運(yùn)放、需要隔離中小電流、嵌入式板級霍爾電流傳感器ACS712等磁場感應(yīng)隔離、無插入損耗精度一般、溫漂大、帶寬有限大電流、粗暴場景專用電流檢測芯片INA226等內(nèi)部集成了放大和ADC精度高、帶I2C接口成本高、發(fā)貨周期長精密測量、通信模塊我的項目電流只有3A對精度有要求但預(yù)算有限果斷選了采樣電阻加運(yùn)放的方案。這里額外說一句別用那種標(biāo)稱1%的碳膜電阻做采樣電阻溫漂很感人盡量選低溫漂的錳銅電阻或者精密合金電阻阻值根據(jù)電流和運(yùn)放增益配合計算。1.3 電壓檢測方案與ADC采集基礎(chǔ)電壓檢測比電流簡單得多。STM32的ADC輸入范圍是0~3.3V所以外部電壓必須先經(jīng)過電阻分壓網(wǎng)絡(luò)把它降到ADC的安全輸入范圍內(nèi)。這個網(wǎng)絡(luò)的設(shè)計重點(diǎn)不是分壓比本身而是等效阻抗對ADC采樣精度的影響以及輸入過壓保護(hù)。STM32的ADC是逐次逼近型內(nèi)部有采樣保持電容。如果信號源阻抗太高采樣時鐘又很快電容來不及充到穩(wěn)定值讀數(shù)就會偏小。所以分壓電阻總值不能太大一般控制在10k~100k之間必要時在ADC引腳并聯(lián)一個0.1uF電容或者開啟STM32內(nèi)部的采樣時間配置給它多留一點(diǎn)充電窗口。2. 硬件電路設(shè)計要點(diǎn)2.1 采樣電阻選型與放大電路計算我用的是0.05Ω的錳銅電阻額定功率2W。最大電流3A時采樣電阻上的壓降是3×0.050.15V遠(yuǎn)低于運(yùn)放的輸出擺幅限制。但這0.15V給到ADC是不行的12位ADC在3.3V參考下每個LSB大約0.8mV直接測的話電流分辨率只有16mA達(dá)不到想要的0.01A。所以加了一級同相放大電路我用的運(yùn)放是LMV358單電源3.3V供電軌到軌輸出。放大倍數(shù)怎么定的先算需求要分辨率0.01A采樣電阻壓降變化是0.01×0.050.0005V0.5mV這個信號給到ADC至少要占據(jù)2~3個LSB才不會被噪聲淹沒。把放大倍數(shù)定在20倍0.5mV變成10mV對應(yīng)12個LSB余量足夠。放大倍數(shù)20倍怎么用電阻搭同相放大倍數(shù)公式是1Rf/Rg取Rf19kΩ、Rg1kΩ正好是20倍。注意這兩個電阻要用1%精度低溫漂的否則增益誤差比運(yùn)放本身還大。這里有一個澳門場外賭錢、在路上行駛的經(jīng)驗(yàn)心得放的提示新手容易忽略運(yùn)放的供電去耦電容一定要靠近電源引腳10uF鉭電容并聯(lián)0.1uF陶瓷電容靠近放置。開關(guān)電源帶來的高頻噪聲如果不在這里濾掉后面ADC采出來的數(shù)據(jù)就是一團(tuán)亂麻。2.2 電壓分壓網(wǎng)絡(luò)與輸入保護(hù)電壓檢測通道我用了兩個精密電阻組成分壓網(wǎng)絡(luò)上臂100kΩ下臂10kΩ分壓比是1/11。這樣14V輸入對應(yīng)ADC引腳1.27V24V輸入對應(yīng)2.18V都在3.3V安全范圍內(nèi)。這兩個電阻同樣要求1%精度因?yàn)榉謮罕染戎苯記Q定了電壓讀數(shù)的絕對精度。分壓電阻總值110kΩ在STM32 ADC標(biāo)準(zhǔn)采樣時間比如1.5周期下阻抗偏高讀數(shù)會偏低。我的解法是開啟ADC的采樣時間到最大239.5周期同時在ADC引腳對地并聯(lián)一個0.1uF電容讓采樣保持電容能從外部儲能電容取電而不是從高阻網(wǎng)絡(luò)取電。實(shí)測這樣處理后讀數(shù)穩(wěn)定度明顯改善。保護(hù)電路不要省。我在ADC引腳和地之間并聯(lián)了一對背靠背的BAV99把輸入鉗位在-0.7V~4V之間防止分壓網(wǎng)絡(luò)某個電阻虛焊導(dǎo)致高壓直接灌進(jìn)MCU。另外串了一個1kΩ的限流電阻即使外部誤接入高壓電流也被限制在毫安級不會燒片子。2.3 電源、參考電壓與PCB布局STM32 ADC的參考電壓引腳VREF如果條件允許盡量用獨(dú)立的基準(zhǔn)源而不是直接接3.3V電源。因?yàn)閿?shù)字電路開關(guān)時的電流尖峰會在3.3V上產(chǎn)生紋波參考電壓跟著晃ADC讀數(shù)必然跳。我這個項目對成本不敏感用了REF3030輸出3.0V基準(zhǔn)精度0.2%。如果預(yù)算有限至少要在VREF腳旁邊加強(qiáng)去耦并讓模擬電路盡量遠(yuǎn)離數(shù)字部分。PCB布局上采樣電阻一定要用四線開爾文連接即電流流過采樣電阻電壓檢測線從電阻兩端單獨(dú)引出到運(yùn)放輸入端。不要直接把導(dǎo)線焊在采樣電阻的同一焊盤上否則焊點(diǎn)阻抗和接觸電阻會竄入測量回路精度損失嚴(yán)重。這是很多DIY項目測量不準(zhǔn)的隱形原因。模擬地AGND和數(shù)字地DGND在PCB上單點(diǎn)連接位置選在MCU的焊盤處保證回流電流不會穿過模擬信號區(qū)域。開關(guān)電源的地線電流是比較臟的盡量讓它直接從電源入口回到主電容別繞經(jīng)模擬區(qū)。3. STM32軟件實(shí)現(xiàn)與數(shù)據(jù)處理3.1 基于HAL庫的ADC多通道配置我用的是標(biāo)準(zhǔn)外設(shè)庫改HAL之后的工程看起來大家現(xiàn)在更喜歡HAL。其實(shí)核心邏輯都一樣無非是配置ADC的采樣通道、采樣時間和轉(zhuǎn)換模式。這里貼一下我用的關(guān)鍵配置代碼ADC_HandleTypeDef hadc1; ADC_ChannelConfTypeDef sConfig {0}; // 使能ADC時鐘和GPIO時鐘 __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); // PA0 - 電壓采樣通道PA1 - 電流采樣通道放大后 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_0 | GPIO_PIN_1; GPIO_InitStruct.Mode GPIO_MODE_ANALOG; GPIO_InitStruct.Pull GPIO_NOPULL; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); hadc1.Instance ADC1; hadc1.Init.ClockPrescaler ADC_CLOCK_SYNC_PCLK_DIV2; hadc1.Init.Resolution ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode ENABLE; // 多通道掃描 hadc1.Init.ContinuousConvMode ENABLE; // 連續(xù)轉(zhuǎn)換 hadc1.Init.NbrOfConversion 2; hadc1.Init.DiscontinuousConvMode DISABLE; HAL_ADC_Init(hadc1); sConfig.Channel ADC_CHANNEL_0; // PA0對應(yīng)ADC1_IN0 sConfig.Rank 1; sConfig.SamplingTime ADC_SAMPLETIME_239CYCLES_5; // 拉長采樣時間 HAL_ADC_ConfigChannel(hadc1, sConfig); sConfig.Channel ADC_CHANNEL_1; // PA1對應(yīng)ADC1_IN1 sConfig.Rank 2; HAL_ADC_ConfigChannel(hadc1, sConfig);讀取數(shù)據(jù)時要注意多通道掃描模式下數(shù)據(jù)寄存器只有一個轉(zhuǎn)換完一個通道就會更新一個值所以必須在轉(zhuǎn)換完成中斷里按順序讀取或者用DMA。我一般用DMA方式一次性把兩通道的結(jié)果搬到內(nèi)存數(shù)組里簡單省心uint16_t adc_buf[2]; HAL_ADC_Start_DMA(hadc1, (uint32_t*)adc_buf, 2); // 每次轉(zhuǎn)換完成后會回調(diào)在回調(diào)里處理數(shù)據(jù)3.2 采樣濾波噪聲是常態(tài)不加濾波的白板數(shù)據(jù)沒法用硬件上做了那么多防護(hù)但ADC原始數(shù)據(jù)仍然會有跳動尤其在開關(guān)電源附近。這是常態(tài)不要指望硬件把所有噪聲都干掉軟件濾波必須跟上。我用的組合拳是先做中值濾波去掉脈沖毛刺再做滑動平均平滑曲線。具體來說連續(xù)采集5次數(shù)據(jù)去掉最大值和最小值剩下3個求平均然后把這3次平均結(jié)果放到一個長度為8的滑動窗口里再取平均。這個組合對工頻干擾和隨機(jī)噪聲的抑制效果都不錯而且實(shí)時性損失很小。中值濾波可以濾掉偶發(fā)的尖峰比如電機(jī)刷火花、繼電器吸合瞬間產(chǎn)生的干擾滑動平均則能把剩余的高頻抖動平滑掉。兩者配合我實(shí)測下來ADC讀數(shù)的峰峰值抖動從±30個LSB降到了±3個LSB以內(nèi)。這個數(shù)據(jù)對大部分應(yīng)用都夠用了。如果對實(shí)時性要求極高比如要做電流環(huán)PID控制就不建議用太長的滑動窗口否則相位滯后會很嚴(yán)重。那種場景下我建議把窗口長度壓到4以內(nèi)或者在濾波器和控制環(huán)路之間加一個前饋補(bǔ)償。3.3 數(shù)據(jù)標(biāo)定從ADC原始值到真實(shí)物理量ADC讀回來的數(shù)字必須換算成真實(shí)的電壓和電流這一步不是簡單的乘一個系數(shù)就完事因?yàn)殡娐防锩總€環(huán)節(jié)都有誤差分壓電阻精度誤差、運(yùn)放增益誤差、ADC參考電壓誤差。最靠譜的做法是逐點(diǎn)標(biāo)定。我用的方法很簡單用一臺精度0.5%的萬用表作為基準(zhǔn)給電壓通道輸入幾個已知電壓比如0V、5V、10V、15V、20V記錄對應(yīng)的ADC讀數(shù)然后用最小二乘法擬合出直線ykxb。電流通道也一樣用電子負(fù)載設(shè)定不同電流記錄ADC讀數(shù)與設(shè)定電流的關(guān)系。擬合得到的k和b直接寫進(jìn)代碼的配置參數(shù)里。這里有個實(shí)際經(jīng)驗(yàn)我不吐不快標(biāo)定點(diǎn)的范圍一定要覆蓋實(shí)際工作區(qū)間外推標(biāo)定比內(nèi)插標(biāo)定誤差大得多。比如你實(shí)際只用到0~3A就別只標(biāo)定0~1A然后線性外推到3A誤差會放大好幾倍。具體代碼里我一般把標(biāo)定結(jié)果以結(jié)構(gòu)體的方式存起來typedef struct { float k; // 斜率 float b; // 截距 float unit; // 物理量單位如V、A } CalibParam; CalibParam voltage_calib {0.00312, -0.0042, 1.0}; // 電壓通道 CalibParam current_calib {0.01024, 0.0021, 1.0}; // 電流通道 float get_voltage(uint16_t adc_value) { return voltage_calib.k * adc_value voltage_calib.b; } float get_current(uint16_t adc_value) { return current_calib.k * adc_value current_calib.b; }4. 校準(zhǔn)方法與誤差分析4.1 兩點(diǎn)校準(zhǔn)法的實(shí)操步驟最小二乘擬合適合有時間慢慢做的朋友如果現(xiàn)場條件不允許兩個點(diǎn)就能完成校準(zhǔn)精度也可以接受。兩個點(diǎn)怎么選選零點(diǎn)附近和工作區(qū)間的中上段這樣的直線對區(qū)間內(nèi)的擬合效果最好。我拿電壓通道舉例先把輸入短接記錄ADC讀數(shù)為n0這叫零點(diǎn)校準(zhǔn)對應(yīng)0V。再用一個穩(wěn)壓電源調(diào)到12V精確輸入記錄ADC讀數(shù)為n1。那么實(shí)際的換算關(guān)系就是[ V_{real} \frac{V_{known}}{n_1 - n_0} \times (n - n_0) ][ V_{real} \frac{12.00}{n_1 - n_0} \times (n - n_0) ]注意這里的n0不一定是一個ADC碼如果硬件有零點(diǎn)偏置n0可能不是0。這個用代碼實(shí)現(xiàn)非常簡單現(xiàn)場操作也不費(fèi)時間比完全依賴?yán)碚撚嬎憔雀咭驗(yàn)樗堰\(yùn)放失調(diào)電壓、參考電壓誤差等系統(tǒng)誤差一并校準(zhǔn)掉了。電流通道的標(biāo)定方法相同只是需要一個恒流源或者電子負(fù)載。我在現(xiàn)場用了一個可以設(shè)定電流的電子負(fù)載分別設(shè)定0A和2A記錄兩組數(shù)據(jù)計算k和b。注意校準(zhǔn)用的負(fù)載精度不要比你的待測設(shè)備低否則標(biāo)定本身就沒有意義了。4.2 誤差來源與應(yīng)對策略開始調(diào)試前先把可能引入誤差的環(huán)節(jié)列了一遍這對定位問題很有幫助誤差來源影響量級應(yīng)對策略分壓電阻精度1%每級1%用0.1%精度電阻或標(biāo)定運(yùn)放增益電阻精度每級1%用0.1%精度電阻或標(biāo)定運(yùn)放輸入失調(diào)電壓數(shù)十uV級別選低失調(diào)運(yùn)放、軟件校準(zhǔn)ADC參考電壓波動每mV約1LSB獨(dú)立參考源、加強(qiáng)去耦采樣回路壓降取決于電流開爾文連接、減小接觸電阻溫度漂移視器件而定選低溫漂器件、軟件補(bǔ)償這里特別提醒一點(diǎn)即使你所有電阻都選了0.1%精度系統(tǒng)總誤差也不會是簡單的相加因?yàn)橛行┱`差是系統(tǒng)性的通過標(biāo)定可以消除有些是隨機(jī)性的只能通過濾波減少。做完兩點(diǎn)校準(zhǔn)之后系統(tǒng)性誤差基本被消除剩下的主要是非線性誤差和溫度漂移。5. 常見問題與調(diào)試實(shí)錄5.1 ADC采樣值亂跳、不穩(wěn)定怎么辦這是大家問得最多的一個問題也是我剛開始踩過最深的一個坑。遇到采樣值亂跳先別急著加濾波按照這個順序排查先看電源紋波是不是太大。用示波器測量MCU的3.3V電源如果紋波超過50mVADC讀數(shù)想穩(wěn)定都難。解決方法是加強(qiáng)3.3V的去耦必要時串磁珠。再看參考電壓VREF是否干凈如果VREF和電源共用一條走線數(shù)字電路動作產(chǎn)生的電流尖峰會直接干擾參考電壓ADC讀數(shù)自然亂跳。最后才是看采樣保持時間配置信號源阻抗大就增加采樣周期。我當(dāng)時遇到的情況是采樣值周期性跳變用示波器一查發(fā)現(xiàn)3.3V上疊加了一個開關(guān)電源的開關(guān)頻率紋波大概±20mV。在電源入口加了LC濾波問題就消失了。濾波和去耦是解決跳變的根本手段軟件濾波只用來兜底。5.2 讀數(shù)偏差比較大怎么定位問題讀數(shù)的絕對值偏差往往比跳變更難定位因?yàn)樯婕暗囊蛩馗唷N业呐挪榉椒ㄊ怯靡粋€已知精度的信號源輸入從傳感器端到ADC引腳逐級測量電壓對比理論值和實(shí)測值哪一級對不上就是哪一級的問題。比如電壓通道讀數(shù)比萬用表低5%先用萬用表量分壓網(wǎng)絡(luò)輸出端電壓是不是理論值如果分壓網(wǎng)絡(luò)輸出電壓正確那就是ADC參考電壓或者標(biāo)定參數(shù)的問題如果輸出不正確那就是分壓電阻本色的問題。電流通道類似先用萬用表量采樣電阻兩端電壓和運(yùn)放輸出電壓再對比ADC讀數(shù)逐級定位。我在調(diào)試一個項目時發(fā)現(xiàn)電流讀數(shù)一直偏大8%查了很久才發(fā)現(xiàn)是采樣電阻旁邊一條走線經(jīng)過了大電流PCB銅箔的壓降被采樣電路誤當(dāng)成采樣電阻的壓降了。這就是前面提到的開爾文連接問題的另一個案例——采樣信號和功率回路在PCB上必須嚴(yán)格分開走線。5.3 調(diào)試中需要注意的幾個細(xì)節(jié)最后分享幾個調(diào)試中容易忽略的小問題。STM32的ADC引腳如果懸空讀數(shù)會亂飄所以沒有用到的通道要配置成關(guān)閉狀態(tài)或者把引腳接固定電平。采樣電阻的功率裕量要夠我用0.05Ω電阻通3A電流時功耗是0.45W2W封裝余量夠足但如果長期滿負(fù)荷運(yùn)行熱量積累也會帶來溫漂。再有一個經(jīng)驗(yàn)是代碼里別忘了對ADC DMA中斷做防呆處理如果DMA傳輸出現(xiàn)半字對齊問題讀數(shù)會很詭異。我習(xí)慣在DMA配置里明確指定數(shù)據(jù)寬度為半字16位避免默認(rèn)配置下高低字節(jié)顛倒的問題。此外板子上電瞬間ADC還沒穩(wěn)定這個時候讀出來的數(shù)據(jù)毫無意義一定要加一個上電延時邏輯比如延時100ms之后再開啟ADC轉(zhuǎn)換。這塊檢測模塊從最開始的概念到最終穩(wěn)定運(yùn)行中間折騰了不少時間。但把每一個環(huán)節(jié)都吃透之后它其實(shí)就是一個很標(biāo)準(zhǔn)的“傳感器信號鏈”設(shè)計感知、調(diào)理、采集、處理。后面你要是遇上類似的測量項目無論換什么MCU、改什么量程這條鏈路的設(shè)計思路都是相通的。紙上得來終覺淺還是建議大家自己親手焊一塊板子、寫一遍代碼、做一次完整的標(biāo)定踩過的坑才會變成真正的經(jīng)驗(yàn)。本文還有配套的精品資源點(diǎn)擊獲取