今天隨機分享4篇基于第一個平臺的項目:
“寒假在家一起練”(1) - 兩個月嵌入式編程DIY示波器和信號發生器,玩起來就免購板費
本文為中國科技大學王赫男同學完成并分享的項目內容。。
1 項目需求
完成對板上音頻信號的采集和波形顯示,可以通過手機播放音樂或App產生音頻信號的方式提供聲音信號源,通過板上電路的放大、MCU中ADC的采集以后將波形顯示在OLED屏幕上,可以通過板上按鍵的操作在兩個方向(橫軸 - 時間;縱軸 - 幅度)來擴展、壓縮波形的顯示,按鍵的功能可自行定義;
實現信號發生器的功能,能夠產生2KHz以內的正弦波、三角波、方波三種常用波形,通過按鍵的操作能夠實現頻率可調、幅度可調,通過調整板上的R、C的值,可以最高生成200KHz的模擬信號;
能夠通過Ain管腳測量外部模擬信號(0-3.3V,DC-200KHz),并能夠對外部的周期性波形測量其周期和峰-峰值;
能夠對采集到的信號進行FFT變換,并在屏幕上顯示其基頻及低次諧波(比如2、3、4、5次)的分量。
2 完成的功能及達到的性能
2.1 波形顯示
顯示波形時,按下L提高采樣率,按下R降低采樣率,采樣率取值范圍為1kHz、2.5kHz、5kHz、10kHz、25kHz、50kHz、100kHz、250kHz、500kHz、1MHz,通過改變采樣率來實現橫軸的縮放。
Y軸(幅度范圍)默認為自動調整,即程序自動根據采樣序列調整Y軸中心電壓值和縮放范圍,使波形完整顯示在屏幕上。通過菜單可以改為手動模式,即手動調整Y軸中心電壓值和Y軸縮放范圍。
左下角顯示波形參數,可以顯示時間軸分度值、信號峰峰值、直流分量和頻率。
正下方顯示當前狀態,包含輸入通道、觸發狀態和前述的Y軸縮放方式(A:自動縮放,MO (Manual Offset):U/D按鍵調整Y軸中心電壓值,MS (Manual Scale):U/D按鍵調整Y軸縮放范圍。
按下OK鍵可以暫停波形刷新,再按可以繼續刷新。
2.2 觸發顯示和觸發菜單
程序默認為上升沿觸發,觸發電平為1.68V。顯示波形且觸發開啟時,屏幕正下方顯示當前觸發邊沿(上升沿、下降沿)和觸發狀態(箭頭點亮為觸發成功、背景點亮為觸發失敗)。
長按R鍵打開觸發菜單,在觸發菜單中可以開啟/關閉觸發,選擇觸發邊沿,選擇自動觸發還是單次觸發。
2.3 示波器菜單
長按OK鍵打開示波器菜單,示波器菜單共有4項,分別是:波形/頻譜顯示切換、Y軸縮放方式、波形參數切換、通道切換(麥克風與板上信號輸入)。LRUD四個按鍵用來對上述四項功能進行切換。
2.4 頻譜顯示
通過菜單切換至頻譜顯示時,屏幕顯示信號的頻譜,顯示頻率范圍為直流至采樣頻率的一半。同樣按下L提高采樣率,按下R降低采樣率。左下角顯示頻率軸分度值。
2.5 信號輸出
長按L鍵打開輸出菜單,在輸出菜單中,可以開啟/關閉信號輸出,增加/降低輸出信號的頻率(步長100Hz,上限2kHz)、峰峰值(步長0.1V,上限3.3V)和調整輸出波形(正弦波、三角波、方波)。
3 實現思路
ADC對模擬輸入進行采樣,采樣由定時器觸發,采樣結果由DMA搬運;
將采樣得到的ADC量化值映射到屏幕坐標點上,實現波形顯示;
按下按鍵調整采樣頻率,實現波形在時間軸上的擴展與壓縮;
對采樣序列進行FFT變換,繪制頻譜;
信號參數的顯示,如峰峰值、直流分量、信號頻率等;
輸出PWM波并通過RC低通濾波實現方波、正弦波、三角波的生成,通過按鍵改變PWM波的頻率與占空比,從而改變輸出信號的頻率和幅度。
4 實現過程
4.1 程序流程圖
注:每個框圖右下角名稱為執行該功能的主要文件
4.2 ADC對數據進行采樣
為了方便進行FFT計算,ADC共采集256個采樣點。每次ADC轉換由定時器1觸發,觸發頻率最高為1MHz,即ADC采樣率最高為1Msps。ADC的轉換結果直接由DMA搬運至內存。
ADC轉換開始函數(定義位置:sample.c,調用位置:main.c):
/** * @brief Start a new sample sequence. * @param[in] ADCValue Array to store incoming sample values. * @retval None */ void start_sample(uint16_t *ADCValue) { HAL_Delay(1); HAL_ADCEx_Calibration_Start(&hadc1); HAL_ADC_Start_DMA(&hadc1, (uint32_t *)ADCValue, SAMPLE_POINTS); }
256次轉換結束后進入中斷,置位結束標志位,進入后續的數據處理程序。
ADC轉換結束中斷回調函數(定義位置:adc.c):
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if(hadc == &hadc1) { finish_sample(); } }
4.3 采樣結果的處理
得到256個采樣的ADC量化值后,根據觸發電平選擇波形起始點,返回起始點在數組中的下標,顯示從起始點開始的100個點。
波形觸發部分代碼(定義位置:wave.c,調用位置:app.c,其中total_points=256, GRAPH_WIDTH=101):
/** * @brief Wave trigger. * @param[in] ADCValue Array of sampled ADC values. * @param[in] total_points Total sampled points. * @retval Index of the trigger start point(》1)。 0 means trigger off or failed. */ uint16_t trigger(uint16_t *ADCValue, uint16_t total_points) { uint16_t i; uint16_t trigger_value = VOL2ADC(1.68); if (!is_trigger_on()) return 0; for (i = 1; i 《 total_points - GRAPH_WIDTH + 2; i++) { if (get_trigger_edge()) // falling edge { if (ADCValue[i-1] 》 trigger_value && ADCValue[i] 《= trigger_value) { trigger_success(); if (is_trigger_single()) pause(); return i; } } else { if (ADCValue[i-1] 《= trigger_value && ADCValue[i] 》 trigger_value) { trigger_success(); if (is_trigger_single()) pause(); return i; } } } trigger_fail(); return 0; }
取起始點后100個采樣值使其顯示在OLED屏幕上(一次性刷新)。為此需要將ADC量化值與OLED屏幕上的坐標進行線性映射。在自動模式(自動縮放y軸)中,程序自動找出量化值中的最大最小值,并使最大最小值也能不超出繪制范圍以外,這樣屏幕就可以顯示完整的波形。
自動縮放y軸代碼(定義位置:wave.c,調用位置:app.c):
/** * @brief Automatically find the central/max/min voltage on y-axis. * @param[in] ADCValue Array of sampled ADC values. * @note The function calculates the min/max voltage of the sampled signal, * then find a proper scale voltage and a central voltage on y-axis. * @retval None */ void auto_scale(uint16_t *ADCValue) { uint16_t a_max_value, a_min_value, a_pp_value; float exact_voltage, floor_voltage, ceil_voltage; get_max_min_pp_value(ADCValue, &a_max_value, &a_min_value, &a_pp_value); voltage_range_auto_select(ADC2VOL(a_pp_value/2)); exact_voltage = ADC2VOL(a_max_value + a_min_value) / 2; floor_voltage = (uint8_t)(ADC2VOL((a_max_value + a_min_value)*5)) / 10.0; //keep one decimal ceil_voltage = floor_voltage + 0.1; // round center_voltage volt_on_y_axis.center_voltage = ceil_voltage - exact_voltage 《 exact_voltage - floor_voltage ? ceil_voltage : floor_voltage; volt_on_y_axis.max_voltage = volt_on_y_axis.center_voltage + v_scale_list[v_scale_index]; volt_on_y_axis.min_voltage = volt_on_y_axis.center_voltage - v_scale_list[v_scale_index]; }
坐標映射代碼(定義位置:wave.c,調用位置:app.c):
/** * @brief Generate y-coordinates of the wave. * @param[in] ADCValue Array of sampled ADC values. * @param[out] y Y-coordinate array of the wave. * @note The function map ADCValues to OLED y coordinates. * @retval None */ void generate_wave(uint16_t *ADCValue, uint8_t *y) { // Quantize y-axis min/max/central voltages to ADC values. int16_t a_max_value = VOL2ADC(volt_on_y_axis.max_voltage); int16_t a_min_value = VOL2ADC(volt_on_y_axis.min_voltage); uint8_t i; // Linearly map every ADC value to its coordinate. for (i = 0; i 《 GRAPH_WIDTH - 1; i++) { if (ADCValue[i] 《= a_max_value && ADCValue[i] 》= a_min_value) y[i] = (GRAPH_HEIGHT - 1) * (a_max_value - ADCValue[i]) / (a_max_value - a_min_value) + GRAPH_START_Y; else if (ADCValue[i] 》 a_max_value) y[i] = GRAPH_START_Y; else if (ADCValue[i] 《 a_min_value) y[i] = GRAPH_HEIGHT + GRAPH_START_Y - 1; } }
波形顯示代碼(定義位置:display.c,調用位置:app.c):
/** * @brief Display wave on OLED. * @param[in] y Y-coordinate array of the wave. * @retval None */ void display_wave(const uint8_t *y) { uint8_t x; for (x = GRAPH_START_X; x 《 GRAPH_WIDTH - 1; x++) OLED_DrawLine(x, y[x-GRAPH_START_X], x + 1, y[x-GRAPH_START_X+1], 1); OLED_DrawPoint(x, y[x-GRAPH_START_X], 1); }
在手動模式中,可以手動調節y軸的縮放范圍和y軸中心電壓值,但此時波形不一定會完整顯示。得到采樣點坐標后,使用OLED的繪制直線函數,連接屏幕上各個離散的點,就可以得到信號的波形。
當需要顯示頻譜時,就需要對所有的ADC的量化值進行256點FFT變換,由于FFT變換結果關于中心點對稱,且屏幕x方向分辨率為128點,所以保留FFT需要為0~127的結果,進行線性映射后顯示在屏幕上。
FFT的代碼定義在fftutil.c中,對變換結果的處理及顯示分別定義在spectrum.c和display.c中。
4.4 信號發生器
板上有一個1Kohm的電阻和10nF的電容構成的低通濾波器,截止頻率為1.6KHz。若在該輸出端輸出頻率足夠的PWM信號,則輸出電壓大小就和PWM的占空比成正比。通過改變PWM的占空比就可以調節輸出電壓波形。通過實驗可知,當信號的每一個周期由500個PWM脈沖組成時,信號的紋波較小。
以正弦信號為例,在程序外,在電腦中生成一個正弦信號,并在一個周期中進行500次采樣,根據電壓和PWM占空比的正比關系可以計算出500個PWM脈沖的占空比。將其定義為長度為500的數組寫入程序。程序中使能PWM的DMA通道,這樣就可以在每個PWM脈沖結束后自動將數組中的元素載入定時器輸出比較寄存器,從而改變占空比。低通濾波器再將STM32產生的PWM脈沖轉變為模擬信號,即可重新生成正弦波。方波和三角波同理。
開啟PWM和DMA代碼(定義位置:source.c,調用位置:app.c,其中SIGNAL_LENGTH=500):
/** * @brief Start signal output at Aux. * @retval None */ void start_output(void) { HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_2, (uint32_t *)output_wave_value, SIGNAL_LENGTH); }
信號的幅度調節可以直接對上述數組每個元素乘一個常數來實現;頻率調節首先要調節定時器的自動重載值(ARR),改變PWM的頻率。為保證幅度不變,數組中每個元素也要同比例縮放。
5 遇到的主要難題
5.1 中斷與DMA
項目共有兩處使用DMA,分別用于儲存ADC采樣結果和調整輸出PWM定時器的自動重載值(ARR)。如果用中斷處理數據而非DMA,則會產生以下問題:
若在ADC轉換完成中斷中讀取轉換結果,則在一次采樣序列(256點)中,中斷頻率過于頻繁,且由于中斷耗時,無法得到很高的采樣率,最高只能達到幾十kHz。若使用DMA,則只需在整個采樣序列結束后進入中斷,不會對采樣造成影響。
若使用PWM中斷更新自動重載值,中斷耗時會使PWM頻率產生偏差,且會對OLED屏幕的SPI時序造成影響,導致屏幕無法正常顯示。若使用DMA更新自動重載值,則不需要PWM中斷,更新耗時相比于中斷有很大改善。
綜上所述,在頻率較高或需要頻繁更新數據的情況,中斷會帶來各種各樣的問題,而DMA則可以高效完成任務。
5.2 RAM和Flash大小(FFT優化)
項目使用的FFT算法根據Adafruit ZeroFFT修改而來。該算法最高可支持4096點FFT,其旋轉因子表、窗函數表和信號序列數組占用空間極大。而本項目使用的STM32G031G8只有64K的Flash和8K的RAM,資源極為有限,無法直接運行ZeroFFT。
為此需要對ZeroFFT的代碼進行優化。該項目只需256點FFT,刪去256點之外的部分,縮短查找表,能極大減小RAM和Flash占用。
具體的優化步驟:
將Adafruit_ZeroFFT.h中的宏定義ZERO_FFT_MAX改為512。(對應256點FFT)
刪去fftutil.c中ZeroFFT函數所有其他點數的FFT代碼,只保留256點FFT的代碼。同樣刪去窗函數中256點以外的部分和窗函數查找表。
此時fftutil.c中只調用了arm_common_tables.c中armBitRevTable和twiddleCoefQ15兩個查找表,刪去其他所有數組。
在fftutil.c中所有調用armBitRevTable和twiddleCoefQ15查找表的代碼下面添加printf,用PC運行FFT程序,打印調用的下標。
以twiddleCoefQ15數組為例,原長度為6144;對于256點FFT,只有其中384個值被調用。PC中編寫一個臨時程序,根據調用的下標,用printf打印一個新的長度為384的查找表替換掉原來的。另一個查找表同理。
fftutil.c中部分變量代表查找表的步進值,查找表改變后這些步進值也要改變。
此時FFT的代碼應該就可以在STM32G0上運行了~
此外,由于Flash和RAM的資源有限,在FFT之外的其他很多地方也需要對空間進行優化,比如刪去oled不需要的字庫等。
5.3 PWM輸出頻率
由于電容的充放電,由PWM經過低通濾波輸出的信號會有鋸齒,信號幅度較低時鋸齒更為明顯,并會造成波形顯示的不穩定。開始時輸出信號一個周期內有50個PWM脈沖,即PWM的頻率是信號頻率的50倍,當信號幅度較低時鋸齒極為明顯,對輸出波形造成極大干擾。將一個周期內PWM脈沖數提升至500,鋸齒密度變大,同時幅度減小,對輸出信號的干擾也減小。但同時儲存輸出信號幅度信息的查找表也變大10倍,消耗了更多的空間。
6 未來的計劃建議
該項目已經成功實現了簡易示波器和信號發生器的功能,并達到了預期指標。然而通過更換硬件,還有許多可以提升與擴展的地方:
板上的OLED屏幕分辨率較低,無法顯示信號細節與更多信息。可以使用分辨率更高的屏幕,或將波形信息直接發送給上位機,由上位機進行顯示。
主控芯片STM32G031的資源有限。可以更換更好的主控芯片,來提高采樣率,采樣點數等從而實現更高的性能。
可以對輸入信號進行衰減,從而增大輸入信號的電壓范圍。
增加模擬輸入的通道,并添加波形的數學運算功能,如波形之間的加減。
改變輸出端的RC值,擴展輸出信號頻率范圍。
不更換硬件可以提升與擴展的地方(懶得做的部分):
自動/手動調整觸發電平。
改變輸入信號耦合方式(直流/交流耦合)。
對輸入信號進行數字濾波。
信號源實現更高的頻率分辨率。
原文標題:如何在STM32G031上實現示波器和頻譜分析功能?
文章出處:【微信公眾號:FPGA入門到精通】歡迎添加關注!文章轉載請注明出處。
責任編輯:haq
-
示波器
+關注
關注
113文章
6240瀏覽量
184801 -
STM32
+關注
關注
2270文章
10896瀏覽量
355757 -
信號發生器
+關注
關注
28文章
1472瀏覽量
108739
原文標題:如何在STM32G031上實現示波器和頻譜分析功能?
文章出處:【微信號:xiaojiaoyafpga,微信公眾號:電子森林】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論