計(jì):示波器、信號(hào)發(fā)生器與頻譜儀實(shí)現(xiàn))
簡(jiǎn)介這是一套基于STM32F407VET6的嵌入式綜合測(cè)量?jī)x器開(kāi)發(fā)工程面向嵌入式軟硬件進(jìn)階學(xué)習(xí)者與電子類課程設(shè)計(jì)/畢業(yè)設(shè)計(jì)實(shí)踐者解決單片機(jī)平臺(tái)下實(shí)時(shí)信號(hào)采集、波形生成與頻域分析的一體化實(shí)現(xiàn)難題。資源包共1277個(gè)文件涵蓋604個(gè)C源碼含HAL驅(qū)動(dòng)、FFT算法調(diào)用與TFT顯示邏輯、298個(gè)頭文件模塊接口定義、78個(gè)匯編啟動(dòng)與DSP庫(kù)相關(guān).S文件以及編譯中間產(chǎn)物與Keil/IAR工程配置文件整體壓縮后20.18MB結(jié)構(gòu)清晰、模塊解耦便于理解ADC采樣鏈路、DAC波形合成、Hamming窗加權(quán)及ARM CMSIS-DSP庫(kù)FFT幅頻變換全流程。已有2178人學(xué)習(xí)下載配套B站全套視頻講解賬號(hào)18655531986并提供咸魚(yú)整套實(shí)物支持工程全程由CubeMX圖形化配置生成.ioc文件所有外設(shè)與時(shí)鐘均可視化設(shè)定顯著降低HAL底層理解門(mén)檻。1. 項(xiàng)目概述為什么選擇STM32F407做三合一儀器在嵌入式開(kāi)發(fā)領(lǐng)域尤其是電子測(cè)量?jī)x器方向很多工程師和愛(ài)好者都曾有過(guò)一個(gè)想法能不能用一塊開(kāi)發(fā)板實(shí)現(xiàn)示波器、信號(hào)發(fā)生器和頻譜儀這三個(gè)最常用的基礎(chǔ)功能這個(gè)想法背后是實(shí)驗(yàn)室設(shè)備昂貴、個(gè)人工作臺(tái)空間有限以及希望深入理解儀器底層原理的切實(shí)需求。我這次的項(xiàng)目就是基于意法半導(dǎo)體的STM32F407VET6核心板利用其HAL庫(kù)將這三個(gè)功能整合到一個(gè)便攜設(shè)備中。選擇STM32F407作為核心是經(jīng)過(guò)深思熟慮的。首先它擁有Cortex-M4內(nèi)核帶FPU浮點(diǎn)運(yùn)算單元主頻高達(dá)168MHz這對(duì)于需要實(shí)時(shí)進(jìn)行FFT快速傅里葉變換運(yùn)算的頻譜分析功能至關(guān)重要。其次它的ADC模數(shù)轉(zhuǎn)換器性能不俗F407的ADC最高支持2.4MSPS的采樣率雖然比不上專業(yè)示波器的GHz級(jí)別但對(duì)于音頻、低頻傳感器信號(hào)、電源紋波等常見(jiàn)場(chǎng)景的觀測(cè)已經(jīng)足夠。再者它具備DAC數(shù)模轉(zhuǎn)換器這是實(shí)現(xiàn)信號(hào)發(fā)生器的硬件基礎(chǔ)。最后豐富的定時(shí)器、DMA和通信接口為多任務(wù)調(diào)度、高速數(shù)據(jù)搬運(yùn)和用戶交互提供了硬件保障。而選擇HAL庫(kù)而非標(biāo)準(zhǔn)庫(kù)或LL庫(kù)主要是出于開(kāi)發(fā)效率和可移植性的考慮。HAL庫(kù)的硬件抽象層做得比較完善雖然代碼體積稍大但其結(jié)構(gòu)清晰跨STM32系列移植相對(duì)容易。對(duì)于這樣一個(gè)整合了ADC、DAC、定時(shí)器、DMA、USB、顯示屏驅(qū)動(dòng)等多個(gè)復(fù)雜外設(shè)的項(xiàng)目使用HAL庫(kù)可以讓我更專注于應(yīng)用邏輯和算法實(shí)現(xiàn)而不是反復(fù)查閱寄存器手冊(cè)去調(diào)試底層驅(qū)動(dòng)。當(dāng)然在極端追求性能的環(huán)節(jié)比如ADC的DMA雙緩沖乒乓操作我也會(huì)配合使用LL庫(kù)或直接操作寄存器進(jìn)行微調(diào)。這個(gè)三合一設(shè)備的目標(biāo)是成為一個(gè)實(shí)用的學(xué)習(xí)工具和輕量級(jí)調(diào)試助手。它不能、也無(wú)意替代專業(yè)的泰克、是德科技或羅德與施瓦茨的儀器但其價(jià)值在于“透明”——你可以完全掌控從信號(hào)輸入、采樣、處理到顯示的每一個(gè)環(huán)節(jié)深刻理解數(shù)字信號(hào)處理DSP的基本原理。接下來(lái)我將從整體設(shè)計(jì)、核心模塊實(shí)現(xiàn)、操作邏輯到避坑經(jīng)驗(yàn)完整地拆解這個(gè)項(xiàng)目。2. 系統(tǒng)整體架構(gòu)與硬件選型思路一個(gè)三合一儀器系統(tǒng)其架構(gòu)設(shè)計(jì)必須清晰確保三個(gè)功能既能獨(dú)立工作又能在必要時(shí)協(xié)同例如用自產(chǎn)信號(hào)進(jìn)行自檢。我的核心設(shè)計(jì)思路是以STM32F407為核心處理器ADC負(fù)責(zé)信號(hào)采集示波器/頻譜儀輸入DAC負(fù)責(zé)信號(hào)生成信號(hào)發(fā)生器輸出定時(shí)器提供精準(zhǔn)的時(shí)基DMA保障數(shù)據(jù)流無(wú)阻塞最后通過(guò)一個(gè)彩色LCD屏和幾個(gè)按鍵實(shí)現(xiàn)人機(jī)交互。2.1 核心MCU與最小系統(tǒng)我使用的是常見(jiàn)的STM32F407VET6最小系統(tǒng)板它擁有512KB Flash和192KB RAM。RAM大小是關(guān)鍵因?yàn)槲覀冃枰_(kāi)辟較大的數(shù)組來(lái)存儲(chǔ)采樣數(shù)據(jù)、顯示緩存以及進(jìn)行FFT運(yùn)算。192KB的RAM在進(jìn)行1024點(diǎn)或2048點(diǎn)的浮點(diǎn)FFT時(shí)是足夠的。主頻配置為168MHz這是F407的極限性能為實(shí)時(shí)處理提供充足的算力。2.2 信號(hào)輸入通道示波器/頻譜儀前端這是決定“示波器”性能上限的關(guān)鍵部分。F407內(nèi)部ADC的絕對(duì)性能指標(biāo)是固定的但前端調(diào)理電路的設(shè)計(jì)直接影響可用性。輸入保護(hù)與衰減直接使用一個(gè)1MΩ電阻與一個(gè)100pF電容并聯(lián)作為輸入端的默認(rèn)負(fù)載。然后通過(guò)一個(gè)雙向TVS管如SMBJ5.0A對(duì)地鉗位防止過(guò)高電壓損壞MCU。我并未設(shè)計(jì)程控衰減網(wǎng)絡(luò)而是通過(guò)軟件設(shè)置ADC的采樣范圍0-3.3V。這意味著輸入信號(hào)必須經(jīng)過(guò)外部衰減探頭或電路將幅度限制在3.3V以內(nèi)。對(duì)于更高電壓測(cè)量必須使用外置有源或無(wú)源衰減探頭。耦合方式通過(guò)一個(gè)電容隔直電路實(shí)現(xiàn)簡(jiǎn)單的AC耦合選項(xiàng)。使用一個(gè)繼電器或模擬開(kāi)關(guān)如CD4053來(lái)選擇直通DC耦合或經(jīng)電容耦合AC耦合。AC耦合的截止頻率由電容和輸入電阻決定我選擇0.1uF電容使得低頻截止點(diǎn)在約1.6Hz適合觀察交流信號(hào)。ADC配置使用ADC1配置為12位分辨率。最關(guān)鍵的是采樣時(shí)鐘和采樣時(shí)間。為了達(dá)到較高的有效采樣率我使用APB2時(shí)鐘84MHz經(jīng)過(guò)分頻得到ADC時(shí)鐘最高36MHz我設(shè)為21MHz。采樣時(shí)間設(shè)置為ADC_SAMPLETIME_3CYCLES。這樣一次轉(zhuǎn)換時(shí)間約為312個(gè)ADC時(shí)鐘周期理論最高采樣率約為21MHz / 15 ≈ 1.4MSPS。在實(shí)際使用DMA連續(xù)傳輸時(shí)受限于內(nèi)存搬運(yùn)和觸發(fā)開(kāi)銷穩(wěn)定在1MSPS左右是可行的。2.3 信號(hào)輸出通道信號(hào)發(fā)生器后端信號(hào)發(fā)生器由內(nèi)部DAC實(shí)現(xiàn)。F407的DAC輸出能力較弱驅(qū)動(dòng)電流很小因此必須后接運(yùn)放進(jìn)行緩沖和放大。DAC配置使用DAC通道1輸出為0-3.3V。通過(guò)定時(shí)器觸發(fā)DAC可以產(chǎn)生非常精確的頻率。例如使用TIM6作為觸發(fā)源更新事件觸發(fā)DAC轉(zhuǎn)換通過(guò)改變定時(shí)器ARR自動(dòng)重裝載值來(lái)改變輸出波形的“點(diǎn)頻率”。后端運(yùn)放電路DAC輸出直接接入一個(gè)電壓跟隨器如使用OPA2350運(yùn)放提高帶負(fù)載能力。如果需要輸出雙極性信號(hào)如-1.65V至1.65V則需要再增加一級(jí)運(yùn)放構(gòu)成加法器電路。本項(xiàng)目第一版僅實(shí)現(xiàn)0-3.3V單極性輸出通過(guò)軟件偏移可以模擬雙極性信號(hào)。波形存儲(chǔ)與生成在RAM中預(yù)先計(jì)算好一個(gè)周期的正弦波、方波、三角波、鋸齒波的數(shù)據(jù)表。DMA在定時(shí)器的驅(qū)動(dòng)下循環(huán)從這張表中讀取數(shù)據(jù)并送入DAC。改變波形就是切換DMA讀取的源數(shù)據(jù)表改變頻率就是調(diào)整觸發(fā)DMA的定時(shí)器頻率。2.4 人機(jī)交互與顯示選用一款帶ILI9341或類似驅(qū)動(dòng)芯片的2.4寸/3.5寸TFT LCD屏使用FSMC接口驅(qū)動(dòng)以保證刷屏速度。界面設(shè)計(jì)采用分層式第一層功能選擇示波器、信號(hào)發(fā)生器、頻譜儀。第二層對(duì)應(yīng)功能的參數(shù)設(shè)置時(shí)基、幅值、頻率、波形等。第三層圖形顯示區(qū)域。 通過(guò)4-5個(gè)機(jī)械按鍵上、下、左、右、確認(rèn)實(shí)現(xiàn)所有菜單操作。雖然不如編碼器流暢但成本低且可靠。2.5 供電與時(shí)鐘使用USB 5V供電通過(guò)LDO如AMS1117-3.3轉(zhuǎn)換為3.3V為整個(gè)系統(tǒng)供電。注意模擬部分ADC參考電壓、DAC、運(yùn)放的電源最好經(jīng)過(guò)LC濾波以降低數(shù)字噪聲干擾。高速時(shí)鐘使用外部8MHz晶振通過(guò)PLL倍頻到168MHz。3. 示波器功能的核心實(shí)現(xiàn)與軟件設(shè)計(jì)示波器功能是這個(gè)項(xiàng)目中最復(fù)雜的部分它需要實(shí)時(shí)采集、實(shí)時(shí)顯示并且要處理觸發(fā)、時(shí)基縮放、幅值縮放等操作。3.1 ADC采樣與DMA雙緩沖機(jī)制要實(shí)現(xiàn)流暢的波形顯示必須使用DMA。更高級(jí)的做法是使用雙緩沖Ping-Pong Buffer模式。配置我分配了兩個(gè)大小為1024的數(shù)組adc_buffer1[1024]和adc_buffer2[1024]。配置ADC為連續(xù)轉(zhuǎn)換模式DMA為循環(huán)模式但數(shù)據(jù)寬度設(shè)為半字16位與ADC的12位數(shù)據(jù)對(duì)齊。雙緩沖邏輯啟動(dòng)時(shí)DMA將數(shù)據(jù)填充到buffer1。當(dāng)buffer1填滿時(shí)DMA產(chǎn)生半傳輸完成中斷如果支持或傳輸完成中斷。在中斷中我立刻將DMA的目標(biāo)地址切換到buffer2并設(shè)置一個(gè)標(biāo)志位通知主循環(huán)“buffer1數(shù)據(jù)已就緒可以處理”。主循環(huán)收到標(biāo)志后將buffer1的數(shù)據(jù)進(jìn)行縮放、處理并準(zhǔn)備送入顯示隊(duì)列。與此同時(shí)DMA正在向buffer2填充數(shù)據(jù)。當(dāng)buffer2填滿時(shí)再切換回buffer1如此往復(fù)。優(yōu)勢(shì)這種機(jī)制實(shí)現(xiàn)了采集與處理的并行。處理上一幀數(shù)據(jù)的同時(shí)下一幀數(shù)據(jù)正在采集避免了等待DMA傳輸完成造成的顯示卡頓。3.2 觸發(fā)功能的軟件實(shí)現(xiàn)沒(méi)有觸發(fā)的示波器幾乎無(wú)法使用。我在軟件中實(shí)現(xiàn)了邊沿觸發(fā)。觸發(fā)點(diǎn)檢測(cè)主循環(huán)在處理一個(gè)已就緒的緩沖區(qū)如1024個(gè)點(diǎn)時(shí)會(huì)遍歷數(shù)據(jù)尋找滿足觸發(fā)條件的點(diǎn)。例如設(shè)置為上升沿觸發(fā)觸發(fā)電平為1.65V50%。算法會(huì)尋找一個(gè)從低于1.65V到高于1.65V的跳變點(diǎn)。波形對(duì)齊找到觸發(fā)點(diǎn)后我將這個(gè)點(diǎn)在緩沖區(qū)中的索引位置記錄下來(lái)。在顯示時(shí)我會(huì)以這個(gè)觸發(fā)點(diǎn)為基準(zhǔn)將波形“畫(huà)”在屏幕的水平中央。例如如果觸發(fā)點(diǎn)是第256個(gè)數(shù)據(jù)那么我就從這個(gè)點(diǎn)開(kāi)始向左取一部分?jǐn)?shù)據(jù)顯示觸發(fā)點(diǎn)之前的波形向右取一部分?jǐn)?shù)據(jù)顯示觸發(fā)點(diǎn)之后的波形共同組成一屏要顯示的波形。觸發(fā)模式除了常規(guī)Normal和自動(dòng)Auto我還實(shí)現(xiàn)了單次Single模式。在單次模式下一旦檢測(cè)到一次觸發(fā)并完成顯示后就停止采集直到用戶手動(dòng)重啟。這對(duì)于捕捉單次事件非常有用。3.3 時(shí)基與幅值的縮放算法時(shí)基和幅值是示波器的兩個(gè)核心旋鈕在軟件中對(duì)應(yīng)的是縮放算法。時(shí)基Time/Div時(shí)基決定了水平方向每格代表的時(shí)間。假設(shè)屏幕水平有240個(gè)像素顯示10個(gè)格子則每格24像素。如果時(shí)基設(shè)為1ms/div那么整個(gè)屏幕代表10ms。我的采樣率是1MSPS即1us一個(gè)點(diǎn)。那么要顯示10ms就需要10000個(gè)點(diǎn)。但我的緩沖區(qū)只有1024點(diǎn)。這時(shí)就需要抽點(diǎn)顯示。我會(huì)從連續(xù)的10000個(gè)點(diǎn)中每隔約10個(gè)點(diǎn)10000/1024≈9.8取一個(gè)點(diǎn)組成1024個(gè)顯示點(diǎn)。更精細(xì)的做法是采用峰值檢測(cè)在每個(gè)間隔內(nèi)取最大和最小值這樣能保留毛刺信息。幅值Volt/Div幅值決定了垂直方向每格代表的電壓。ADC的12位值0-4095對(duì)應(yīng)0-3.3V。如果設(shè)為1V/div屏幕垂直方向共8格假設(shè)對(duì)應(yīng)8V但我們的量程只有3.3V。所以實(shí)際上這是對(duì)3.3V量程的放大。在軟件中我將ADC原始值轉(zhuǎn)換為電壓值浮點(diǎn)數(shù)然后根據(jù)設(shè)定的V/div進(jìn)行縮放最后映射到屏幕的Y坐標(biāo)。例如屏幕Y軸0像素對(duì)應(yīng)-4V假設(shè)240像素對(duì)應(yīng)4V那么1.65V零點(diǎn)就應(yīng)該畫(huà)在屏幕Y軸的中央120像素處。3.4 波形顯示與網(wǎng)格繪制顯示部分使用LCD的畫(huà)線函數(shù)。我預(yù)先在顯存或一個(gè)緩沖區(qū)中繪制好固定的坐標(biāo)網(wǎng)格灰色細(xì)線。然后將處理好的波形數(shù)據(jù)點(diǎn)用直線連接起來(lái)畫(huà)在網(wǎng)格上層亮綠色或白色。為了消除閃爍可以采用局部刷新或雙幀緩沖技術(shù)但為了簡(jiǎn)化我是在繪制新波形前用背景色擦除舊波形所在的矩形區(qū)域再繪制新波形雖然有一定閃爍但在可控范圍內(nèi)。注意抽點(diǎn)算法是性能與細(xì)節(jié)的權(quán)衡。當(dāng)采樣率遠(yuǎn)高于顯示所需點(diǎn)數(shù)時(shí)簡(jiǎn)單的間隔抽點(diǎn)會(huì)導(dǎo)致高頻信號(hào)失真混疊。對(duì)于要求不高的場(chǎng)合可以接受但更嚴(yán)謹(jǐn)?shù)淖龇ㄊ窍冗M(jìn)行數(shù)字低通濾波再降采樣顯示。4. 信號(hào)發(fā)生器功能的DDS實(shí)現(xiàn)信號(hào)發(fā)生器我采用直接數(shù)字頻率合成DDS原理在軟件中實(shí)現(xiàn)這樣比純硬件DDS芯片更靈活。4.1 DDS基本原理與查表法DDS的核心是一個(gè)相位累加器。我有一個(gè)32位的相位累加器寄存器每個(gè)時(shí)鐘周期由定時(shí)器觸發(fā)產(chǎn)生累加器就加上一個(gè)“頻率控制字”FTW。相位累加器 FTW;累加器的高N位例如高10位作為索引去查詢一個(gè)預(yù)先存儲(chǔ)好的波形表如1024點(diǎn)的正弦波表查到的值送給DAC輸出。DAC輸出 WaveTable[相位累加器 22]; // 取高10位32-1022FTW決定了輸出頻率輸出頻率 (FTW * 系統(tǒng)時(shí)鐘頻率) / 2^32。 通過(guò)改變FTW就能無(wú)縫、精確地改變輸出頻率。4.2 多波形生成我預(yù)先在ROM中計(jì)算并存儲(chǔ)了幾種標(biāo)準(zhǔn)波形的數(shù)據(jù)表。正弦波使用sin()函數(shù)計(jì)算一個(gè)周期1024點(diǎn)的值量化為0-4095對(duì)應(yīng)DAC的12位輸出。方波前512點(diǎn)填最大值4095后512點(diǎn)填最小值0。三角波值從0線性增加到4095再線性減少到0。鋸齒波值從0線性增加到4095然后瞬間歸零重復(fù)。 在運(yùn)行時(shí)根據(jù)用戶選擇的波形將對(duì)應(yīng)的波形表首地址賦值給DMA的源地址。DMA在定時(shí)器觸發(fā)下自動(dòng)循環(huán)讀取這個(gè)表并發(fā)送給DAC。4.3 頻率與幅度的精確控制頻率控制頻率由兩個(gè)因素決定波形表長(zhǎng)度L1024和定時(shí)器觸發(fā)頻率F_timer。輸出頻率F_out F_timer / L。因此我通過(guò)改變定時(shí)器如TIM6的ARR和PSC值來(lái)改變F_timer從而改變F_out。通過(guò)精細(xì)計(jì)算ARR和PSC可以獲得非常精確的頻率。例如系統(tǒng)時(shí)鐘168MHz經(jīng)過(guò)分頻后給定時(shí)器可以產(chǎn)生從不到1Hz到幾百KHz的穩(wěn)定信號(hào)。幅度控制幅度在數(shù)字域控制。DAC輸出數(shù)據(jù) 波形表數(shù)據(jù) * 幅度系數(shù)。幅度系數(shù)是一個(gè)0.0到1.0之間的浮點(diǎn)數(shù)。在送入DAC前我會(huì)將查表得到的數(shù)值乘以這個(gè)系數(shù)并限制在0-4095之間。這樣就實(shí)現(xiàn)了軟件可調(diào)的幅度。對(duì)于偏移直流分量則是加上一個(gè)固定的數(shù)值。4.4 定時(shí)器觸發(fā)DAC的DMA傳輸配置這是實(shí)現(xiàn)穩(wěn)定輸出的關(guān)鍵。我配置TIM6為向上計(jì)數(shù)產(chǎn)生更新事件UEV。這個(gè)更新事件同時(shí)觸發(fā)兩件事觸發(fā)DAC開(kāi)始一次轉(zhuǎn)換。觸發(fā)DMA進(jìn)行一次傳輸從波形表搬運(yùn)一個(gè)數(shù)據(jù)到DAC數(shù)據(jù)寄存器。 通過(guò)將DAC設(shè)置為“定時(shí)器觸發(fā)”模式并將DMA與DAC關(guān)聯(lián)就實(shí)現(xiàn)了一個(gè)由定時(shí)器精確節(jié)拍的“自動(dòng)播放機(jī)”。只要改變定時(shí)器的頻率播放速度就改變從而改變輸出信號(hào)頻率。5. 頻譜儀功能的FFT實(shí)現(xiàn)與顯示頻譜儀功能是將示波器采集到的時(shí)域信號(hào)通過(guò)FFT變換成頻域信號(hào)并顯示。這是對(duì)MCU運(yùn)算能力的一大考驗(yàn)。5.1 FFT庫(kù)的選擇與集成STM32F407有FPU因此可以使用浮點(diǎn)FFT以獲得更高精度和更方便的處理。我選擇了ARM官方提供的CMSIS-DSP庫(kù)其中包含了高度優(yōu)化的浮點(diǎn)FFT函數(shù)arm_cfft_f32。這個(gè)庫(kù)針對(duì)Cortex-M4和M7內(nèi)核做了大量匯編優(yōu)化速度遠(yuǎn)超自己編寫(xiě)的C語(yǔ)言FFT代碼。集成在Keil或STM32CubeIDE中需要手動(dòng)添加CMSIS-DSP庫(kù)的源文件和頭文件路徑。初始化需要初始化一個(gè)FFT實(shí)例結(jié)構(gòu)體指定FFT點(diǎn)數(shù)如1024點(diǎn)。5.2 數(shù)據(jù)預(yù)處理窗函數(shù)應(yīng)用直接對(duì)截取的一段時(shí)域信號(hào)做FFT會(huì)由于信號(hào)非周期截?cái)喽a(chǎn)生“頻譜泄漏”導(dǎo)致頻率譜線擴(kuò)散、幅度不準(zhǔn)。為了解決這個(gè)問(wèn)題需要在做FFT之前對(duì)時(shí)域數(shù)據(jù)乘以一個(gè)窗函數(shù)。 我常用的是漢寧窗Hanning Window。我預(yù)先計(jì)算好一個(gè)1024點(diǎn)的漢寧窗系數(shù)數(shù)組存儲(chǔ)在ROM中。在調(diào)用FFT函數(shù)前執(zhí)行一個(gè)循環(huán)fft_input[i] adc_buffer[i] * hanning_window[i];這個(gè)步驟能有效抑制頻譜泄漏讓頻率峰值更尖銳幅度估算更準(zhǔn)確當(dāng)然也會(huì)帶來(lái)一定的幅度誤差需要后續(xù)補(bǔ)償。5.3 執(zhí)行FFT與計(jì)算幅值譜CMSIS-DSP庫(kù)的FFT函數(shù)是“原位”運(yùn)算即輸入數(shù)組也會(huì)被用來(lái)存放輸出結(jié)果。準(zhǔn)備數(shù)據(jù)將ADC采集到的整數(shù)數(shù)據(jù)轉(zhuǎn)換為浮點(diǎn)數(shù)并減去直流分量平均值然后乘上窗函數(shù)結(jié)果放入一個(gè)復(fù)數(shù)數(shù)組的實(shí)部虛部置零。執(zhí)行FFT調(diào)用arm_cfft_f32函數(shù)進(jìn)行FFT計(jì)算。計(jì)算幅值譜FFT輸出是復(fù)數(shù)數(shù)組。對(duì)于0到N/2512個(gè)點(diǎn)因?yàn)閷?duì)稱計(jì)算每個(gè)點(diǎn)的模值magnitude[i] sqrt(real[i]*real[i] imag[i]*imag[i])。ARM庫(kù)也提供了arm_cmplx_mag_f32函數(shù)來(lái)高效完成這個(gè)操作。幅度校正由于窗函數(shù)對(duì)信號(hào)能量有衰減需要對(duì)幅值譜進(jìn)行校正。對(duì)于漢寧窗其相干增益約為0.5噪聲帶寬約為1.5。一個(gè)簡(jiǎn)單的全頻帶幅度補(bǔ)償系數(shù)是2.0補(bǔ)償能量損失。更精確的做法是查找窗函數(shù)的幅度補(bǔ)償系數(shù)表。5.4 頻譜顯示與頻率定標(biāo)得到幅值譜數(shù)組后需要將其顯示在屏幕上。對(duì)數(shù)坐標(biāo)聲音、振動(dòng)等信號(hào)的頻譜通常用對(duì)數(shù)坐標(biāo)dB顯示因?yàn)槿硕鷮?duì)響度的感知是對(duì)數(shù)的。我將幅值轉(zhuǎn)換為dB值dB_value 20 * log10(magnitude / reference)。其中reference是一個(gè)參考值可以設(shè)置為FFT點(diǎn)數(shù)或一個(gè)固定值用于調(diào)整顯示尺度。頻率定標(biāo)水平軸代表頻率。頻率分辨率Δf 采樣率Fs / FFT點(diǎn)數(shù)N。例如Fs1MHzN1024則Δf≈976.6Hz。屏幕上第i個(gè)點(diǎn)對(duì)應(yīng)的頻率就是i * Δf。最大顯示頻率是Fs / 2奈奎斯特頻率即500KHz。顯示優(yōu)化由于頻譜數(shù)據(jù)點(diǎn)很多512個(gè)而屏幕水平像素可能只有240個(gè)需要進(jìn)行抽點(diǎn)或取區(qū)間最大值來(lái)顯示。我采用取區(qū)間最大值的方法將512個(gè)頻率點(diǎn)分成240組每組取最大的dB值來(lái)繪制柱狀圖或連線圖這樣能保留頻譜中的峰值信息。6. 多任務(wù)管理與用戶界面實(shí)現(xiàn)如何讓示波器、信號(hào)發(fā)生器、頻譜儀三個(gè)功能協(xié)調(diào)工作并響應(yīng)用戶操作需要一個(gè)簡(jiǎn)單的任務(wù)調(diào)度機(jī)制。6.1 基于狀態(tài)機(jī)的裸機(jī)調(diào)度我沒(méi)有使用RTOS而是采用一個(gè)超級(jí)循環(huán)main loop配合狀態(tài)機(jī)State Machine來(lái)管理。主循環(huán)結(jié)構(gòu)while (1) { key_scan(); // 掃描按鍵更新?tīng)顟B(tài) ui_handler(); // 根據(jù)當(dāng)前系統(tǒng)狀態(tài)和按鍵更新用戶界面 switch (sys_mode) { case MODE_OSCILLOSCOPE: oscilloscope_task(); break; case MODE_GENERATOR: generator_task(); // 通常由定時(shí)器中斷驅(qū)動(dòng)這里只處理參數(shù)更新 break; case MODE_SPECTRUM: spectrum_task(); break; } // 處理其他低優(yōu)先級(jí)任務(wù)如USB虛擬串口通信 }中斷服務(wù)ADC的DMA半傳輸/傳輸完成中斷、定時(shí)器中斷等只做最緊急的事設(shè)置標(biāo)志位、切換緩沖區(qū)。所有耗時(shí)的處理如FFT、波形繪制都放在主循環(huán)中根據(jù)標(biāo)志位來(lái)執(zhí)行。6.2 菜單界面設(shè)計(jì)用戶界面采用層次化菜單。頂層菜單三個(gè)大圖標(biāo)分別代表示波器、信號(hào)發(fā)生器、頻譜儀。通過(guò)左右鍵選擇按確認(rèn)鍵進(jìn)入。功能層菜單進(jìn)入某個(gè)功能后屏幕下方或側(cè)邊顯示可調(diào)節(jié)的參數(shù)列表如時(shí)基、幅值、觸發(fā)類型、波形、頻率、幅度等。通過(guò)上下鍵選擇參數(shù)項(xiàng)左右鍵改變參數(shù)值。圖形顯示區(qū)屏幕大部分區(qū)域用于實(shí)時(shí)顯示波形或頻譜。 界面繪制使用基本的畫(huà)點(diǎn)、畫(huà)線、畫(huà)矩形和字符顯示函數(shù)。為了流暢我只在參數(shù)變化時(shí)重繪菜單區(qū)域波形顯示區(qū)域則持續(xù)刷新。6.3 參數(shù)存儲(chǔ)與掉電記憶用戶設(shè)置好的參數(shù)如常用的時(shí)基、幅值、波形頻率等需要保存下次上電后可以直接使用。我使用STM32F407內(nèi)部的Flash最后一頁(yè)P(yáng)age 127作為參數(shù)存儲(chǔ)區(qū)。定義結(jié)構(gòu)體定義一個(gè)包含所有需要保存參數(shù)的結(jié)構(gòu)體。寫(xiě)入前擦除Flash寫(xiě)入前必須先擦除整個(gè)扇區(qū)。寫(xiě)入數(shù)據(jù)將結(jié)構(gòu)體數(shù)據(jù)轉(zhuǎn)換為uint64_t數(shù)組調(diào)用HAL庫(kù)的HAL_FLASH_Program函數(shù)寫(xiě)入。上電讀取系統(tǒng)初始化時(shí)從固定地址讀取數(shù)據(jù)到結(jié)構(gòu)體中并校驗(yàn)數(shù)據(jù)的有效性可以增加CRC校驗(yàn)或魔數(shù)。注意Flash擦寫(xiě)次數(shù)有限約1萬(wàn)次不要頻繁保存。我僅在用戶退出某個(gè)功能模式或長(zhǎng)按某個(gè)按鍵時(shí)才觸發(fā)保存操作。7. 關(guān)鍵問(wèn)題排查與性能優(yōu)化經(jīng)驗(yàn)在實(shí)際開(kāi)發(fā)中遇到了不少坑這里總結(jié)幾個(gè)最關(guān)鍵的。7.1 ADC采樣值不準(zhǔn)或噪聲大問(wèn)題現(xiàn)象波形毛刺多底噪大測(cè)量直流電壓跳動(dòng)大。排查與解決電源噪聲這是最常見(jiàn)原因。務(wù)必給模擬部分VDDA、VSSA使用獨(dú)立的LC濾波電路。我在3.3V的模擬電源入口處串聯(lián)一個(gè)10Ω電阻并并聯(lián)一個(gè)10uF鉭電容0.1uF陶瓷電容效果顯著。參考電壓確保VREF引腳連接了干凈、穩(wěn)定的3.3V。最好使用專用的低噪聲基準(zhǔn)電壓芯片如REF3033。采樣時(shí)間不足如果信號(hào)源阻抗較高ADC的采樣電容充電不足。增加ADC的采樣周期數(shù)ADC_SAMPLETIME我通常設(shè)置為ADC_SAMPLETIME_84CYCLES來(lái)采樣高阻抗信號(hào)。數(shù)字干擾在ADC轉(zhuǎn)換期間讓CPU進(jìn)入休眠或停止其他高速數(shù)字操作如刷屏、大量計(jì)算。可以在ADC轉(zhuǎn)換期間關(guān)閉無(wú)關(guān)外設(shè)時(shí)鐘或使用DMA讓ADC獨(dú)立工作。軟件濾波在軟件中對(duì)連續(xù)采樣值進(jìn)行滑動(dòng)平均濾波例如取16個(gè)點(diǎn)求平均作為一個(gè)顯示點(diǎn)能有效平滑噪聲但會(huì)降低有效帶寬。7.2 信號(hào)發(fā)生器輸出頻率不準(zhǔn)或有毛刺問(wèn)題現(xiàn)象用示波器測(cè)量自產(chǎn)的信號(hào)頻率與設(shè)定值有偏差或方波邊沿有振鈴。排查與解決定時(shí)器時(shí)鐘計(jì)算錯(cuò)誤仔細(xì)檢查定時(shí)器的時(shí)鐘源、PSC和ARR的計(jì)算公式。確保系統(tǒng)時(shí)鐘配置正確APB1/APB2的預(yù)分頻器設(shè)置會(huì)影響定時(shí)器時(shí)鐘。DMA傳輸延遲確保DMA的優(yōu)先級(jí)足夠高且數(shù)據(jù)寬度與DAC寄存器匹配。如果DMA傳輸被中斷可能導(dǎo)致某個(gè)點(diǎn)輸出延遲。可以嘗試將DMA和定時(shí)器中斷優(yōu)先級(jí)設(shè)為最高。運(yùn)放電路問(wèn)題方波邊沿振鈴?fù)ǔJ呛蠖诉\(yùn)放電路響應(yīng)速度不夠或存在寄生振蕩。檢查運(yùn)放的壓擺率Slew Rate是否足夠在輸出端串聯(lián)一個(gè)小電阻如22-100Ω后再接負(fù)載可以抑制振鈴。波形表精度對(duì)于低頻信號(hào)1024點(diǎn)的波形表精度足夠。但對(duì)于高頻信號(hào)接近奈奎斯特頻率點(diǎn)數(shù)會(huì)顯得稀疏導(dǎo)致波形階梯狀明顯。此時(shí)可以嘗試提高定時(shí)器觸發(fā)頻率或者使用更高點(diǎn)數(shù)的波形表但會(huì)占用更多內(nèi)存。7.3 FFT運(yùn)算速度慢或結(jié)果異常問(wèn)題現(xiàn)象頻譜刷新率極低或者頻譜圖明顯不對(duì)如只有一根譜線。排查與解決啟用FPU這是最重要的步驟在編譯器中必須設(shè)置啟用硬件FPU-mfpufpv4-sp-d16 -mfloat-abihard。在代碼開(kāi)頭調(diào)用SCB-CPACR | ((3UL 10*2)|(3UL 11*2));來(lái)使能FPU。使用CMSIS-DSP庫(kù)務(wù)必使用優(yōu)化過(guò)的庫(kù)函數(shù)不要自己寫(xiě)FFT循環(huán)。內(nèi)存對(duì)齊CMSIS-DSP庫(kù)的FFT函數(shù)要求輸入輸出數(shù)組在內(nèi)存中32位對(duì)齊4字節(jié)對(duì)齊。在定義數(shù)組時(shí)使用__attribute__((aligned(4)))或__ALIGNED(4)來(lái)確保。數(shù)據(jù)溢出ADC數(shù)據(jù)轉(zhuǎn)換為浮點(diǎn)數(shù)后數(shù)值范圍在0-3.3之間。做FFT和求模運(yùn)算一般不會(huì)溢出。但如果錯(cuò)誤地使用了Q格式定點(diǎn)數(shù)FFT且未進(jìn)行縮放很容易溢出導(dǎo)致結(jié)果全零或異常。理解頻譜圖對(duì)于單一頻率的正弦波其頻譜圖應(yīng)該是在對(duì)應(yīng)頻率處有一根譜線并且由于泄漏和窗函數(shù)影響旁邊會(huì)有一些較低的旁瓣。如果看到很多雜散譜線可能是電源噪聲或數(shù)字干擾耦合進(jìn)了ADC輸入。7.4 顯示閃爍或操作卡頓問(wèn)題現(xiàn)象波形刷新時(shí)屏幕閃爍或按鍵反應(yīng)遲鈍。排查與解決局部刷新不要每次刷新都重繪整個(gè)屏幕包括靜態(tài)網(wǎng)格和菜單。只刷新波形變化的區(qū)域。例如計(jì)算新舊波形Y坐標(biāo)的差異區(qū)域只清除和重繪這個(gè)矩形區(qū)域。雙緩沖在內(nèi)存中開(kāi)辟一個(gè)和屏幕分辨率一樣的緩沖區(qū)frame_buffer。所有的繪圖操作都先在這個(gè)緩沖區(qū)中進(jìn)行。完成一幀的繪制后一次性將這個(gè)緩沖區(qū)的數(shù)據(jù)通過(guò)DMA傳輸?shù)絃CD的顯存GRAM。這需要LCD驅(qū)動(dòng)芯片支持GRAM連續(xù)寫(xiě)入。ILI9341支持此功能可以極大減少屏幕撕裂和閃爍。優(yōu)化繪圖函數(shù)LCD的底層畫(huà)點(diǎn)、畫(huà)線函數(shù)可能很慢。優(yōu)化這些函數(shù)比如使用寄存器直接寫(xiě)入代替HAL庫(kù)函數(shù)或者使用FSMC的存儲(chǔ)器映射模式批量寫(xiě)入數(shù)據(jù)。任務(wù)拆分將耗時(shí)的FFT運(yùn)算拆分成多個(gè)步驟分幀執(zhí)行。例如一幀只做數(shù)據(jù)采集和預(yù)處理下一幀做FFT前半部分再下一幀做FFT后半部分和顯示。雖然降低了實(shí)時(shí)性但保證了界面的響應(yīng)速度。這個(gè)三合一項(xiàng)目的實(shí)現(xiàn)過(guò)程是對(duì)STM32F407各項(xiàng)性能的一次全面壓榨和深入理解。從硬件電路的設(shè)計(jì)與調(diào)試到底層ADC/DAC/DMA的精準(zhǔn)操控再到上層的信號(hào)處理算法和用戶交互每一個(gè)環(huán)節(jié)都充滿了挑戰(zhàn)和樂(lè)趣。它最終呈現(xiàn)的不僅僅是一個(gè)工具更是對(duì)數(shù)字信號(hào)處理、實(shí)時(shí)系統(tǒng)、嵌入式硬件等知識(shí)的綜合實(shí)踐。對(duì)于想要深入嵌入式信號(hào)處理領(lǐng)域的開(kāi)發(fā)者來(lái)說(shuō)親手實(shí)現(xiàn)一遍這樣的項(xiàng)目收獲將遠(yuǎn)大于僅僅使用一臺(tái)現(xiàn)成的儀器。本文還有配套的精品資源點(diǎn)擊獲取