詳解)
簡介基于STM32的信號參數(shù)測量與波形顯示系統(tǒng)源自2014年全國大學(xué)生電子設(shè)計競賽D題面向電子工程與嵌入式開發(fā)初學(xué)者適合作為畢業(yè)設(shè)計、課程設(shè)計或綜合實踐的基礎(chǔ)方案。方案以STM32微控制器為核心完成模擬信號高精度采樣、電壓電流實時測量并在液晶屏上呈現(xiàn)時域波形結(jié)合快速傅里葉算法生成頻域譜線設(shè)計聲反饋振蕩檢測與數(shù)字陷波濾波實現(xiàn)自適應(yīng)抑制完整覆蓋硬件架構(gòu)、數(shù)據(jù)采集與頻譜分析流程。資源包共308個文件以C語言源碼和頭文件為主輔以啟動文件、工程配置與備份以及編譯好的HEX固件壓縮后僅1.01MB。文件結(jié)構(gòu)清晰便于直接導(dǎo)入Keil工程進行學(xué)習(xí)與二次開發(fā)。當(dāng)前已有73人學(xué)習(xí)使用內(nèi)容包含完整項目源碼、工程配置及算法實現(xiàn)可幫助讀者快速理解STM32的ADC采集、LCD驅(qū)動、FFT頻譜分析和數(shù)字濾波等關(guān)鍵技能是電子設(shè)計與競賽準備中一份值得參考的實踐資料。 做電壓電流采集加波形顯示聽起來像是個畢業(yè)設(shè)計題或者實驗室的小項目實際做下來你會發(fā)現(xiàn)真正花時間的不是接線和調(diào)庫而是把采樣鏈路里的精度問題、屏幕刷新邏輯和調(diào)試環(huán)境理順。這套基于STM32的系統(tǒng)核心就干兩件事用ADC把電壓電流變成數(shù)字量再在LCD上把這些數(shù)字量畫成波形。適合正在做課程設(shè)計的學(xué)生、剛?cè)腴T嵌入式想做個完整小項目的開發(fā)者也適合工廠里想做簡易功率監(jiān)測但預(yù)算有限的一線工程師參考。先說結(jié)論這個項目的技術(shù)門檻其實不高STM32的ADC加上一塊SPI接口的LCD屏就能跑起來但要做到波形不閃爍、數(shù)據(jù)不跳變、長時間運行穩(wěn)定里面的細節(jié)非常多。我把我實際調(diào)試過程中的完整方案、參數(shù)計算和踩過的坑都整理在下面照著做能省你至少兩三天時間。1. 項目整體思路與硬件選型1.1 標(biāo)題里的“采集”到底在測什么電壓電流采集通俗說就是用STM32內(nèi)部自帶的模數(shù)轉(zhuǎn)換器ADC去讀取外部模擬電壓信號。電壓采集相對直接用電阻分壓把被測電壓降到STM32能承受的0到3.3V范圍就行電流采集則繞一道彎多數(shù)方案是先把電流轉(zhuǎn)成電壓再送進ADC。常見的做法有兩種一是串聯(lián)一個毫歐級采樣電阻用運放把電阻兩端的微小壓差放大二是直接用霍爾電流傳感器模塊比如ACS712系列輸出已經(jīng)是一個與電流成正比的電壓值。波形顯示則是把連續(xù)采樣到的數(shù)據(jù)點映射到LCD屏幕的坐標(biāo)軸上讓原本枯燥的數(shù)字變成肉眼可見的曲線。這要求采樣率要夠、屏幕刷新要快否則波形要么糊成一片要么像幻燈片一樣卡頓。整個系統(tǒng)的核心鏈路就是信號輸入 - 信號調(diào)理 - STM32 ADC采樣 - 數(shù)據(jù)處理 - LCD繪制波形。1.2 電壓采集分壓電阻的數(shù)學(xué)賬分壓電路是電壓采集的基礎(chǔ)但很多初學(xué)者容易在這里犯迷糊。假設(shè)你要測0到24V的直流電壓STM32的ADC輸入范圍是0到3.3V那就需要把24V按比例縮小到3.3V以內(nèi)。這里有個經(jīng)驗值我習(xí)慣把最大測量電壓對應(yīng)的ADC輸入電壓控制在3.0V左右留出10%的余量防止過壓燒引腳。計算過程并不復(fù)雜。已知最大輸入電壓Vin_max24V期望ADC引腳最大電壓Vadc_max3.0V則分壓比KVadc_max/Vin_max3.0/240.125。選擇上分壓電阻R1100kΩ下分壓電阻R2R1×K/(1-K)100k×0.125/0.875≈14.3kΩ可以取標(biāo)稱值15kΩ。這時候?qū)嶋H分壓比是15/(10015)0.1304最大輸入電壓對應(yīng)ADC引腳電壓是24×0.13043.13V仍在3.3V安全范圍內(nèi)。另一個容易忽略的問題是電阻阻值本身不能太大。STM32的ADC內(nèi)部有采樣電容外部信號源阻抗過高會導(dǎo)致采樣電容充不滿從而產(chǎn)生采樣誤差。100kΩ級別的分壓電阻雖然功耗低但對ADC的驅(qū)動能力是個考驗。我的做法是在分壓網(wǎng)絡(luò)和ADC引腳之間加一級電壓跟隨器用LM358或TL072這類通用運放做緩沖阻抗問題就徹底解決了。1.3 電流采集采樣電阻加運放與ACS712模塊的取舍電流采集的方案選擇直接決定成本和精度。采樣電阻加運放的方式成本低且?guī)捀哌m合做高頻電流波形觀察。比如測0到5A電流選用10mΩ的精密采樣電阻滿量程壓降為5A×0.01Ω50mV這個信號太小必須放大。用軌到軌運放比如LMV358設(shè)置放大倍數(shù)為50倍輸出就是0到2.5V剛好匹配ADC范圍。ACS712模塊則省去了放大電路的設(shè)計它的典型靈敏度是66mV/A以ACS712-05B為例5A滿量程輸出是0.33V實際上模塊內(nèi)部已經(jīng)把靜態(tài)輸出電壓偏置在VCC/2≈1.65V。它的優(yōu)點是即插即用缺點是帶寬只有80kHz測50Hz工頻電流完全沒有問題但如果你想觀察更高速的電流變化它就不太夠用了。我在這個項目里最終選了采樣電阻加運放的方案原因很簡單一是精度可以自己通過校準電阻調(diào)整二是這個方案對理解模擬信號鏈路更有幫助。如果你手頭有ACS712模塊直接用也是可以的后續(xù)代碼里只需要調(diào)整換算系數(shù)。2. STM32 ADC采樣核心細節(jié)2.1 ADC通道配置與多通道切換STM32系列芯片的ADC資源差異較大以常見的STM32F103C8T6為例它有兩個12位ADC共16個外部通道。對于電壓和電流兩路信號可以分別使用ADC1的通道0和通道1這樣硬件上天然隔離互不干擾。配置時我用標(biāo)準外設(shè)庫代碼如下ADC_InitTypeDef ADC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; // 使能時鐘 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 72MHz / 6 12MHz ADC時鐘 // 配置PA0和PA1為模擬輸入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, GPIO_InitStructure); // ADC1配置 ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode ENABLE; // 掃描模式多通道必須開啟 ADC_InitStructure.ADC_ContinuousConvMode DISABLE; ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 2; ADC_Init(ADC1, ADC_InitStructure); // 通道順序和采樣時間 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); ADC_Cmd(ADC1, ENABLE);采樣時間這里選用55.5個周期對應(yīng)12MHz的ADC時鐘大約是4.6微秒。如果只測低頻信號其實20個周期左右就夠了但采樣時間長一些能有效降低噪聲剛做出來的板子不建議一上來就用最快采樣速度。2.2 采樣時間與等效輸入阻抗的“隱形坑”前面提到外部信號源阻抗的問題這里展開說。STM32的ADC核心是一個逐次逼近型轉(zhuǎn)換器內(nèi)部有一個約4pF的采樣電容。每次轉(zhuǎn)換前采樣開關(guān)會閉合一段時間讓電容充電到輸入電壓。如果外部源阻抗過高充電時間常數(shù)就大采樣電容上的電壓可能還沒充到實際值就被斷開了結(jié)果就是轉(zhuǎn)換結(jié)果偏小。計算一下假設(shè)外部源阻抗R_source10kΩ采樣電容C_sample4pF時間常數(shù)τ10k×4pF40ns。55.5個采樣周期在12MHz下是4.625μs約等于115個時間常數(shù)電容基本能充滿。但如果你直接把100kΩ分壓電阻接到ADC引腳τ就變成400ns仍能充滿可如果采樣時間只設(shè)1.5個周期125ns就明顯不夠了。我的經(jīng)驗是分壓電阻總阻值控制在20kΩ以內(nèi)或者加一級運放跟隨器配合至少20個周期的采樣時間測出來的電壓基本不會飄。此外在ADC引腳對地并聯(lián)一個0.1μF的濾波電容能明顯降低高頻噪聲對采樣值的影響代價是信號響應(yīng)變慢但對應(yīng)50Hz工頻信號完全無感。2.3 DMA與數(shù)據(jù)搬移為什么不直接讀數(shù)據(jù)寄存器很多人圖省事循環(huán)里直接調(diào)用ADC_GetConversionValue()讀數(shù)據(jù)這在單通道低頻采集時問題不大。但當(dāng)采樣率提高到每秒幾千次CPU就被困在等待轉(zhuǎn)換和搬數(shù)據(jù)上了根本騰不出手去刷新LCD。正確的做法是開啟DMA讓ADC轉(zhuǎn)換完成后自動把結(jié)果搬進內(nèi)存數(shù)組全程不需要CPU干預(yù)。DMA_InitTypeDef DMA_InitStructure; uint16_t adc_buf[2]; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)adc_buf; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize 2; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel1, DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE); ADC_DMACmd(ADC1, ENABLE);這里的DMA_Mode_Circular循環(huán)模式非常關(guān)鍵它會讓DMA自動回繞持續(xù)不斷地把最新采樣值更新到adc_buf數(shù)組里。主循環(huán)里只需要定期讀取adc_buf[0]和adc_buf[1]就能拿到最新的電壓和電流數(shù)據(jù)CPU大部分時間都用在LCD繪制上。3. LCD波形顯示的實現(xiàn)要點3.1 屏幕選型SPI屏還是并口屏LCD屏的選型直接影響刷新速度和接線復(fù)雜度。市面上常見的小尺寸屏有兩種接口SPI串口屏和8080并口屏。SPI屏引腳少只需四根線SCL、SDA、CS、DC但刷新速度受限于SPI時鐘通常2.4寸的SPI屏全屏刷新一幀要50毫秒以上。并口屏數(shù)據(jù)線16根加上控制線占用的IO很多但刷屏速度能快到20毫秒以內(nèi)。對波形顯示這種需要頻繁局部刷新的場景SPI屏完全夠用因為我并不需要全屏重繪只需要擦掉舊波形、畫上新波形每次操作量很小。我用的是一塊1.8寸的ST7735驅(qū)動屏幕SPI時鐘設(shè)到36MHz實測局部刷新一塊160×80區(qū)域的波形肉眼感知不到閃爍。3.2 波形圖不是“畫圖”而是坐標(biāo)變換把ADC采樣值顯示成波形本質(zhì)上是一個坐標(biāo)換算的過程。屏幕的Y軸對應(yīng)電壓值X軸對應(yīng)時間或采樣點序號。ADC是12位的取值范圍0到4095換算成電壓的公式是float voltage (float)adc_buf[0] * 3.3f / 4095.0f;但屏幕顯示不能直接用這個電壓值得把它映射到屏幕的Y坐標(biāo)。比如LCD顯示區(qū)域?qū)挾仁?28像素高度是160像素我計劃把波形區(qū)域設(shè)為高度120像素頂部留20像素顯示文字信息。那么Y坐標(biāo)就是uint16_t y WAVE_TOP (uint16_t)(voltage / VREF_MAX * WAVE_HEIGHT);這里VREF_MAX是你要顯示的最大電壓如果設(shè)定為24V那么24V對應(yīng)Y軸的底部0V對應(yīng)頂部。注意LCD左上角是原點Y軸向下增長所以電壓越大Y坐標(biāo)反而越大這一點容易搞反。我一開始就沒加這個補償波形是鏡像的后來用軟件取反才解決。3.3 波形滾動與刷新率優(yōu)化波形滾動顯示有幾種策略整屏平移、環(huán)形緩沖區(qū)寫點、或者只顯示最新的一段采樣數(shù)據(jù)。整屏平移在SPI屏上開銷很大因為要頻繁讀取和重寫顯存。我用的是環(huán)形緩沖區(qū)加局部刷新思路是這樣的ADC連續(xù)采樣每采到一個點就存進一個長度為128的數(shù)組數(shù)組滿后從頭覆蓋。在顯示循環(huán)里每次只擦除上一幀波形所在的點再繪制當(dāng)前幀的新點。// 偽代碼簡化顯示邏輯 for (int x 0; x 128; x) { uint16_t y_old y_values[x]; // 舊坐標(biāo) uint16_t y_new get_adc_scaled(x); // 新坐標(biāo) draw_pixel(x, y_old, BG_COLOR); // 擦除舊點 draw_pixel(x, y_new, WAVE_COLOR); // 繪制新點 y_values[x] y_new; }這段邏輯在主循環(huán)里跑得非??煲驗镾PI屏支持單點寫入每次操作不過是寫兩個字節(jié)的命令加兩個字節(jié)的數(shù)據(jù)。再加上把采樣率控制在每秒500點到1000點波形既平滑又不至于讓CPU吃不消。這里有個優(yōu)化技巧不要每采一個點就刷新一次屏幕而是攢夠128個點、湊滿一屏再刷。這樣刷新率等于采樣率除以128大約是4到8幀每秒肉眼看起來是流暢的實時波形。如果覺得卡就把采樣點數(shù)降到64或者只顯示波形區(qū)域的一半寬度。4. 開發(fā)環(huán)境與調(diào)試坑實錄4.1 環(huán)境搭建從0到1的完整路徑這套系統(tǒng)我用的是Keil MDK加STM32標(biāo)準外設(shè)庫開發(fā)雖然現(xiàn)在HAL庫是主流但標(biāo)準庫資料多、代碼直觀用來學(xué)習(xí)ADC和DMA的工作流程更友好。新建工程時有一個關(guān)鍵點必須在Device選項里選擇正確的芯片型號然后從標(biāo)準庫的庫函數(shù)文件夾里把對應(yīng)外設(shè)的源文件加進工程。很多人第一次建工程時報錯找不到頭文件多半是沒在C/C選項卡的Include Paths里添加庫頭文件路徑。還有一個平臺通吃的坑Keil MDK 5默認是不兼容C51工程的如果你之前學(xué)過51單片機電腦上可能裝了C51版本的Keil直接打開STM32工程會報一堆莫名其妙的錯誤。解決辦法是安裝Keil MDK后再裝一個C51支持包或者在電腦上共存兩個版本、分別建立快捷方式。4.2 ST-Link連接報錯no stm32 target found這個問題大概能排進STM32初學(xué)者十大噩夢之首報錯信息為Error: No STM32 target found如果你用ST-Link下載程序時遇到它先冷靜排查確認ST-Link的SWDIO、SWCLK、GND三根線是否連對目標(biāo)板是否供電正常。多數(shù)情況下是杜邦線接觸不良或者目標(biāo)板沒上電。如果硬件沒問題再看看Keil的Debug選項卡里是否選擇了ST-Link Debugger以及Utilities選項卡的Flash Download設(shè)置是否正確。更隱蔽的情況是目標(biāo)芯片的SWD引腳被代碼復(fù)用成了普通GPIO。比如GPIOA的PA13和PA14默認是SWDIO和SWCLK如果初始化代碼把它們重新配置為推挽輸出芯片就沒法和調(diào)試器通信了。解決辦法是用ST-Link Utility的Connect Under Reset模式按住目標(biāo)板復(fù)位鍵再連接通常能救回來。建議在項目早期就禁用JTAG只保留SWDGPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);4.3 虛擬串口嘆號與延時函數(shù)卡死如果你打算用STM32的USB虛擬串口把采樣數(shù)據(jù)實時傳到電腦大概率會遇到設(shè)備管理器里出現(xiàn)黃嘆號的問題。這不是硬件壞了而是驅(qū)動沒安裝或者固件里的USB描述符不匹配。下載ST官方STM32 Virtual COM Port驅(qū)動重新安裝即可。要注意的是如果固件里定義的USB PID和驅(qū)動程序不匹配同樣的驅(qū)動也裝不上。還有那個經(jīng)典問題延時函數(shù)delay卡死。用標(biāo)準庫的朋友如果直接復(fù)制網(wǎng)上的延時函數(shù)而不配置SysTick定時器程序就會死在Delay內(nèi)部。我的習(xí)慣是優(yōu)先使用DWTData Watchpoint and Trace模塊做微秒級延時它不需要占用定時器外設(shè)。如果你的代碼卡在某一行while循環(huán)先查中斷優(yōu)先級是否配置正確再查時鐘初始化是否完整這兩個原因占了九成。5. 常見問題與排查技巧速查現(xiàn)象可能原因排查思路ADC讀數(shù)始終為0通道未使能或引腳配置錯誤檢查GPIO模式是否為模擬輸入ADC通道號是否對應(yīng)采樣值跳變嚴重電源紋波大或采樣時間過短增加濾波電容增大ADC采樣周期LCD白屏SPI引腳初始化錯誤或復(fù)位時序不對檢查CS、DC、RST引腳電平狀態(tài)確認屏幕供電電壓波形顯示方向反了坐標(biāo)變換未取反檢查Y軸計算電壓越大Y值應(yīng)越大下載程序時提示找不到目標(biāo)SWD引腳被復(fù)用或接線不良嘗試Connect Under Reset檢查杜邦線再補一個我做這個項目時踩過的坑LCD極化和視角問題。屏幕的偏光方向決定了最佳可視角度如果畫出來的波形顏色淡或者閃爍不一定是代碼問題也有可能是屏幕的對比度寄存器配置不對。ST7735這類屏上電后需要發(fā)送一組初始化命令來設(shè)置偏壓和顯示方向直接用廠家給的參考代碼最穩(wěn)妥不要自己折騰顯示方向的旋轉(zhuǎn)參數(shù)。另外電源設(shè)計很重要。STM32和LCD共用一個USB供電時如果電流采集的是電機這種感性負載啟動瞬間的電流尖峰會導(dǎo)致屏幕閃屏。我的做法是用一個獨立的AMS1117-3.3穩(wěn)壓給STM32和LCD供電采樣電路的參考地單獨走線回電源地信號地和功率地單點連接波形瞬間干凈了很多。想把這個項目繼續(xù)擴展的話有兩個方向可以考慮一是加一個ESP8266模塊把采樣數(shù)據(jù)通過MQTT上傳到手機端做遠程監(jiān)控二是把數(shù)據(jù)存儲到SD卡實現(xiàn)長時間記錄后再離線分析。核心的ADC采集和LCD顯示框架都可以復(fù)用改動的工程量不大。本文還有配套的精品資源點擊獲取