日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

基于STM32G031的失真度测试仪(CubeMX+ADC+DMA+OLED+EC11)

發(fā)布時間:2023/12/29 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基于STM32G031的失真度测试仪(CubeMX+ADC+DMA+OLED+EC11) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

目錄

    • 項目介紹
    • 硬件介紹
    • 設計思路
    • 各功能代碼及說明
      • SPWM波生成
      • ADC采樣
      • FFT
      • 獲取按鍵動作
      • OLED顯示
      • 系統(tǒng)頂層
    • 功能展示
      • OLED顯示采樣波形
      • OLED顯示頻譜/失真度曲線
    • 項目總結

👉工程文件及代碼:參見【2022寒假在家練】基于STM32G031的失真度測試儀
👉 CSDN:工程源代碼下載
👉 Github-KafCoppelia/DistortionMeter

項目介紹

本項目基于電子森林的STM32G031口袋儀器訓練平臺,基于CubeMX與Keil,實現(xiàn)了:

  • 通過芯片的PWM+板上LPF電路生成頻率在DC~20KHz,頻率可調,并且幅度可調,從10mV~500mV正弦波信號;
  • 將該信號通過Test端口連接到測試電路的輸入端,通過運算放大器輸入至ADC+DMA,對其進行量化處理;
  • 計算該電路頻譜(歸一化幅值譜),與總諧波(THD);
  • 在OLED上繪制了波形圖及歸一化幅值譜、失真度曲線(線性及對數(shù)坐標)。
  • 硬件介紹

    👉 電子森林-基于STM32的簡易示波器/頻譜儀/信號發(fā)生器學習平臺

  • 基于STM32G031微控制器,Arm Cortex M0+內核,主頻為64MHz;
  • 2個按鍵+1個光電旋轉編碼器用于控制輸入;
  • 1個SPI接口的OLED顯示屏(128*128分辨率);
  • 1路音頻放大電路用于產生ADC的測試信號,并可作為測試電路使用;
    一個蜂鳴器用于音效輸出;
  • 1路基于PWM的DDS信號輸出,用于產生測試信號(任意波形);
  • 2路增益可調的模擬信號輸入,通過12bits ADC采集2mVpp~30Vpp,帶寬為100KHz的模擬信號;

  • 基于STM32G031的測試測量學習套件的構成框圖如上圖所示。

    設計思路

    設計的整體結構框圖如下圖所示:

    👉 整體結構參考:SCOPE-F072–基于STM32F072的多功能掌中儀器


    由于沒有上操作系統(tǒng),初始化外設及OLED后,整體為一個循環(huán),判斷當前系統(tǒng)處于示波器或頻譜/失真度狀態(tài)(即OLED顯示的內容是什么),再各判斷是否為頁初始化(初次進入該狀態(tài)時會設置狀態(tài),之后相互切換就不會重置狀態(tài)位),之后執(zhí)行對應的操作。按鍵、旋鈕的交互功能如上圖箭頭所示。

    各功能代碼及說明

    該開發(fā)板電路圖如圖所示:


    板卡左下角PB0產生PWM,可通過示波器測PWM測試點調試,連接左上角JP1的1、2(實物排針右兩個),通過對ADC_M(PA0)做采樣即可獲取波形數(shù)據(jù)。

    SPWM波生成

    PWM信號源相關代碼參見source.c/.h

    SPWM波主要是調節(jié)一般PWM波的占空比,使輸出波所占面積和對應正弦波面積相等。所以首先需要一組正弦波數(shù)據(jù),可以通過Python等方式計算:

    import numpy as npdef sin_wave(point, num):y = []for i in range(0, point):fz = num/2 * np.sin(np.pi/point*2*i) + num/2y.append(fz)return yif __name__ == "__main__":y = sin_wave(256, 256)print(y)y2 = []for dot in y:y2.append(round(dot))print(y2)

    其中,point為生成數(shù)據(jù)的點數(shù),如128、256個;num為生成數(shù)據(jù)的范圍,表示從0~num。生成的數(shù)據(jù)可以static const uint16_t儲存:

    #define SIGNAL_LENGTH 256 static const uint16_t sine_table[SIGNAL_LENGTH] = {128, 131, 134, 137, 141, 144, 147, 150, 153, 156, 159, 162, 165, 168, 171, 174, 177, 180, 183, 186, 188, 191, 194, 196, 199, 202, 204, 207, 209, 212, 214, 216, 219, 221, 223, 225, 227, 229, 231, 233, 234, 236, 238, 239, 241, 242, 244, 245, 246, 247, 249, 250, 250, 251, 252, 253, 254, 254, 255, 255, 255, 256, 256, 256,256, 256, 256, 256, 255, 255, 255, 254, 254, 253, 252, 251, 250, 250, 249, 247, 246, 245, 244, 242, 241, 239, 238, 236, 234, 233, 231, 229, 227, 225, 223, 221, 219, 216, 214, 212, 209, 207, 204, 202, 199, 196, 194, 191, 188, 186, 183, 180, 177, 174, 171, 168, 165, 162, 159, 156, 153, 150, 147, 144, 141, 137, 134, 131, 128, 125, 122, 119, 115, 112, 109, 106, 103, 100, 97, 94, 91, 88, 85, 82, 79, 76, 73, 70, 68, 65, 62, 60, 57, 54, 52, 49, 47, 44, 42, 40, 37, 35, 33, 31, 29, 27, 25, 23, 22, 20, 18, 17, 15, 14, 12, 11, 10, 9, 7, 6, 6, 5, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 5, 6, 6, 7, 9, 10, 11, 12, 14, 15, 17, 18, 20, 22, 23, 25, 27, 29, 31, 33, 35, 37, 40, 42, 44, 47, 49, 52, 54, 57, 60, 62, 65, 68, 70, 73, 76, 79, 82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 112, 115, 119, 122, 125 };

    將TIM3_CH3(PB0)設為PWM輸出,prescaler與counter period暫且不管。從正弦波表至設置頻率的SPWM波及振幅還需變換:

    void Generate_Sine(void) {uint16_t tim_period;uint16_t i;if(is_source_on())Sine_Stop();tim_period = 64000000 / SIGNAL_LENGTH / source_signal.frequency; // 25__HAL_TIM_SET_AUTORELOAD(&htim3, tim_period-1);for (i = 0; i < SIGNAL_LENGTH; i++)sine_value[i] = (sine_table[i]-128) * (tim_period-1) / 256 * source_signal.amplitude / 1650 + tim_period/2;if(!is_source_on())Sine_Start(); }

    根據(jù):
    fSPWM=fsine?Nf_{SPWM}=f_{sine}\cdot NfSPWM?=fsine??N

    由此算得TIM3的周期,通過__HAL_TIM_SET_AUTORELOAD()設定不同頻率正弦波下的SPWM的頻率。此外,還需對正弦波表做歸一化,將其直流偏置移動到1.65V(IO口輸出最高3.3V),其中source_signal為信號源參數(shù)的結構體,儲存源信號的頻率和幅度,開始/關閉產生正弦波調用
    HAL_TIM_PWM_Start_DMA()及HAL_TIM_PWM_Stop_DMA()即可。

    typedef struct {uint32_t frequency;uint16_t amplitude; } Source_Params; /*-----------------*/ Source_Params source_signal = {.frequency = 1000, .amplitude = 500};void Sine_Start(void) { HAL_TIM_PWM_Start_DMA(&htim3, TIM_CHANNEL_3, (uint32_t*)sine_value, SIGNAL_LENGTH);set_source_on(); }void Sine_Stop(void) {HAL_TIM_PWM_Stop_DMA(&htim3, TIM_CHANNEL_3);set_source_off(); }

    由于當設置好輸出頻率及振幅后,傳輸至TIM3_CH3的AutoReload值為周期性循環(huán)的,因此設置TIM3_CH3的DMA有利于提高CPU的效率:

    ParametersValue
    Channel隨意
    DirectionMemory to Peripheral
    PriorityMedium
    ModeCircular
    Increment AddressMemory
    Data Width of PeripheralWord
    Data Width of MemoryHalf Word

    ADC采樣

    ADC采樣相關代碼參見sample.c/.h

    一些采樣的全局變量,256個采樣值,9檔采樣率,初始設置采樣率下標:

    #define SAMPLE_RATES_NUM 9 uint16_t ADC_Value[SAMPLE_POINTS]; static const uint32_t sample_rate_list[SAMPLE_RATES_NUM] = {2560, 5120, 10240, 20480, 40960, 81920, 102400, 204800, 409600}; static int8_t sample_rate_index = 5;

    ADC部分參數(shù)設置如下,其余參數(shù)大致選默認的即可:

    ParametersValue
    Clock Prescaler/2
    Resolution12-bit
    Data AligmentRight
    SamplingTime Common 11.5
    SamplingTime Common 21.5
    N of Conversion1
    External Trigger Conversion SourceTimer 1 Tigger Out Event 2
    External Trigger Conversion EdgeTrigger detection on the falling edge

    ADC對PA0的采樣使用TIM1觸發(fā),當TIM1出現(xiàn)下邊沿時,開始或結束采樣,此時TIM1的頻率即為ADC實際采樣率。設置TIM1_CH3 PWM Generation No Output,prescaler與counter period暫且不管,重點設置TRGO Parameters:

    ParametersValue
    Master/Slave ModeDisable
    Trigger Event Selection TRGOReset
    Trigger Event Selection TRGO2Update Event

    每次開始采樣前,需要根據(jù)采樣值設置TIM1的AutoReload與CCR值(占空比50%即可):

    uint32_t Get_SampleRate(void) {return sample_rate_list[sample_rate_index]; } void Set_SampleRate(uint32_t sample_rate) {__HAL_TIM_SET_AUTORELOAD(&htim1, 64000000 / sample_rate - 1);__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, 32000000 / sample_rate); }

    從ADC采集的數(shù)據(jù)經(jīng)過DMA存入數(shù)組,DMA設置如下:

    ParametersValue
    Channel隨意
    DirectionMemory to Peripheral
    PriorityHigh
    ModeNormal
    Increment AddressMemory
    Data Width of PeripheralHalf Word
    Data Width of MemoryHalf Word

    開啟采樣需要開啟TIM1及ADC的DMA傳輸,注意開始采樣前需要對ADC進行校準:

    void Sample_Start(uint16_t *ADCValue) {HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);HAL_ADCEx_Calibration_Start(&hadc1);HAL_ADC_Start_DMA(&hadc1, (uint32_t *)ADCValue, SAMPLE_POINTS); }void Sample_Stop(void) {HAL_TIM_Base_Stop(&htim1);HAL_TIM_Base_Stop_IT(&htim1);HAL_ADC_Stop_DMA(&hadc1); }

    FFT

    FFT及頻譜相關代碼參見specturm.c/.h及庫`

    該部分使用Adafruit_ZeroFFT庫,選定做FFT點數(shù),選擇對應的窗函數(shù),刪去庫多余的代碼節(jié)約空間。

    👉 Adafruit_ZeroFFT

    通過調用ZeroFFT()即可計算FFT:

    memcpy(FFT_Value, ADC_Value, sizeof(FFT_Value)); ZeroFFT((int16_t*)FFT_Value, FFT_POINT)

    原代碼內的FFT最后計算舍去了虛部,只保留了實部,在此參考寒假在家一起練(1) - 有信號發(fā)生器功能的簡易示波器的該部分代碼,改為計算幅值譜,并修正直流分量,最后將整個FFT數(shù)組做歸一化即可得到歸一化幅值譜:

    for (i = 0; i < length; i++) {real = *pOut++;img = *pOut++;*pSrc++ = sqrt((int32_t)real * real + (int32_t)img * img); } source[0] /= 2;

    通過計算后的頻譜計算中心頻率、獲得某頻率所在頻譜下標調用FFT_BIN、FFT_INDEX即可:

    float Get_ActualFreq(uint16_t *FFTValue, uint32_t sample_rate) {return FFT_BIN(Get_SpectrumMax(FFTValue, 1), sample_rate, FFT_POINT); } uint8_t Get_SpectrumMax(uint16_t *FFTValue, uint8_t ignore_dc) {uint8_t i;uint8_t temp_max_index = ignore_dc ? 2 : 0;for (i = ignore_dc ? 3 : 1; i <= FFT_POINT / 2; i++)if (FFTValue[i] > FFTValue[temp_max_index])temp_max_index = i;return temp_max_index; }

    重點計算THD,需要計算中心頻率功率、高次諧波的功率和,作者計算了一定采樣率下頻譜包含的所有諧波的功率和,當然也可只取N次,但容易出現(xiàn)交互調整采樣率后該諧波不在頻譜內的意外(頻譜所包含的頻率只有Sample Rate/2)。

    float Get_THDx(uint16_t *FFTValue, uint8_t ignore_dc, uint32_t sample_rate) {uint16_t maxN_power = 0;uint16_t max_power = 0;int i = 1; max_power = FFT_Value[FFT_INDEX(Get_SourceFreq(), sample_rate, FFT_POINT)];while(Get_SourceFreq()*(i+1) <= sample_rate/2){maxN_power += FFT_Value[FFT_INDEX(Get_SourceFreq()*(i+1), sample_rate, FFT_POINT)];i++;}return sqrtf(((float)maxN_power)/max_power); }

    最后,由于OLED顯示僅128像素,只能將256點FFT的0~127歸一化后作為數(shù)據(jù)顯示(顯示區(qū)域高90,寬128)。當對數(shù)形式顯示時,將0值映射至-30dB,將最大值映射為0,由于算得的FFT數(shù)組已為幅度譜,因此只需lg?(x)\lg(x)lg(x)即可,乘-30為改符號為正,并映射至0~90。

    void Generate_Spectrum(uint16_t *FFTValue, uint8_t *y, uint8_t log_or_linear) {uint8_t max_index = Get_SpectrumMax(FFTValue, 0);uint8_t i;if(log_or_linear){for(i = 0; i < FFT_POINT / 2; i++){if(FFTValue[i] > 0){y[i] = (uint8_t)(-30*log10f((1.0*FFTValue[i]/FFTValue[max_index])));}else{y[i] = GRAPH_HEIGHT-1;}}}else{for(i = 0; i < FFT_POINT / 2; i++){y[i] = (GRAPH_HEIGHT-1) * (FFTValue[max_index] - FFTValue[i]) / FFTValue[max_index];}} }

    獲取按鍵動作

    按鍵相關代碼參見keys.c/.h

    對常規(guī)按鍵的處理參考SCOPE-F072–基于STM32F072的多功能掌中儀器中對按鍵的處理,通過設置TIM14每1ms產生Update中斷,并對按鍵掃描,可以獲取按鍵下邊沿、上邊沿、長按、短點擊、長按后置高、長按時間等多個動作,當按鍵產生動作后,執(zhí)行相應操作(如改變采樣率、切換示波器/頻譜等)。

    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM14){Key_Handle(); // 在Key_Handle()中進行按鍵動作的操作} }

    對于EC11旋轉編碼器的處理,按鍵部分參考前述即可,左右旋轉(PB4、PA15)通過一側IO作為輸入時鐘捕獲,判斷另一側IO的電位即可,在此設置TIM2_CH1(對應PA15):

    ParametersValue
    Prescaler63
    Counter Period999
    Input Capture Channel 1參數(shù)如下:
    ParametersValue
    Polarity SelectionFalling Edge
    IC SelectionDirect
    Prescaler Dividion RatioNo division
    Input Filter4
    void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {if(htim->Instance == TIM2){if(HAL_GPIO_ReadPin(KeyB_GPIO_Port, KeyB_Pin)){zoom_out = 0x00;zoom_in = 0x01;}else{ zoom_out = 0x01;zoom_in = 0x00;}} }

    OLED顯示

    關于OLED顯示相關代碼參見display.c/.h、wave.c/.h及OLED庫

    經(jīng)典128*128 4-wire SPI OLED。SPI2參數(shù)設置如下:

    將oled.c中的部分代碼修改:

    void OLED_WR_Byte(u8 dat,u8 cmd) { //u8 i; if(cmd)OLED_DC_Set();else OLED_DC_Clr();HAL_SPI_Transmit(&hspi2, &dat, 1, 1000);OLED_DC_Set(); } void OLED_Clear(void) {u8 i, n;for(i = 0; i < 16; i++){for(n = 0; n < 128; n++){OLED_GRAM[n][i] = 0;//清除所有數(shù)據(jù)}}//OLED_Refresh();//更新顯示 }

    修改為硬件SPI寫入,刪去清屏函數(shù)后的刷新可使OLED屏幕顯示不閃爍

    wave.c主要負責獲取的數(shù)據(jù)的處理,做線性映射以顯示在屏幕上,還做調整Y軸顯示范圍等功能。

    display.c主要負責顯示波形,顯示文字、顯示其他信息等。

    系統(tǒng)頂層

    系統(tǒng)相關代碼參見user.c/.h

    主要負責監(jiān)視OLED的狀態(tài)與按鍵的動作:

    typedef enum {Oscilloscope,Distortion }System_State; /* ------------------- */ System_State System = Oscilloscope; uint8_t Page_Init = 1; uint8_t zoom_in = 0x00; // left for zoom in, right for zooming out uint8_t zoom_out = 0x00; void System_Change_State(System_State State) {System = State;Page_Init = 1; } void OLED_Handle(void) {switch(System){case Oscilloscope:{if(Page_Init){Page_Init = 0;zoom_in = 0x00;zoom_out = 0x00;Oscilloscope_Init();}memset(FFT_Value, 0x0000, sizeof(FFT_Value));memset(ADC_Value, 0x0000, sizeof(ADC_Value));Source_Init();Sample_Init();Wave_View(ADC_Value, Get_SampleRate(), graph);break;}case Distortion:{if(Page_Init){Page_Init = 0;zoom_in = 0x00;zoom_out = 0x00;Distortion_Init();}Sample_Init();memcpy(FFT_Value, ADC_Value, sizeof(FFT_Value));if(ZeroFFT((int16_t*)FFT_Value, FFT_POINT)== 0){Spectrum_View((uint16_t*)FFT_Value, graph, Get_SampleRate());}break;}default:break;} }

    按鍵動作大致設置如下:

    void Key_Handle(void) {static Key_Type Key[3] = {0};Get_Key(Key);switch(System){case Oscilloscope:{if(Get_Rise(Key1) || Get_Long_Tri(Key1)){if(is_setting_source_freq())Inc_SourceFreq();elseInc_SourceAmp();}if(Get_Rise(Key2) || Get_Long_Tri(Key2)){if(is_setting_source_freq())Dec_SourceFreq();elseDec_SourceAmp();}if(Get_Long_Press(KeyP)) // 長按旋鈕{toggle_display();System_Change_State(Distortion);}if(Get_Cont_Click(KeyP) == 2){toggle_scale();}if(Get_Rise(KeyP)) // 短按旋鈕{toggle_source_setting();}if(zoom_in == 0x01){if(is_auto_scale()){Dec_SampleRate();}else{Inc_YScale();}zoom_in = 0x00;}else if(zoom_out == 0x01){if(is_auto_scale()){Inc_SampleRate();}else{Dec_YScale();}zoom_out = 0x00;}break;}case Distortion:{if(Get_Long_Press(KeyP)) // 長按旋鈕{toggle_display();System_Change_State(Oscilloscope);}if(Get_Rise(KeyP)) // 短按旋鈕{toggle_spectrum_yaxis();}if(zoom_in == 0x01){Inc_SampleRate();zoom_in = 0x00;}else if(zoom_out == 0x01){Dec_SampleRate();zoom_out = 0x00;}break;}default:break;} }

    在示波器狀態(tài)下,按下板卡下方兩個按鈕,若目前是調整源頻率/幅度,增大/減小頻率/幅度;長按旋鈕,切換至頻譜/失真度曲線顯示。

    功能展示

    OLED顯示采樣波形

    示波器頁面,中間橫線顯示直流電平所在處,左上角顯示采樣時間,中間顯示目前所調整的為源頻率/幅度,右上角標識當前頁面。下方左側顯示信號峰值、直流偏置電壓及目前Y軸顯示范圍;右側顯示源頻率、源幅度及Y軸顯示范圍自動/手動調整。


    長按旋鈕,切換至頻譜/失真度頁面。

    OLED顯示頻譜/失真度曲線

    左上方顯示橫軸每格代表頻率,隨采樣率而變化,右上角標識當前頁面。下方左側標識當前為線性/對數(shù)坐標顯示,及THD;右側為通過FFT計算的中心頻率。


    短按旋鈕即可切換線性/對數(shù)坐標。


    👉 項目演示視頻參見:基于STM32G031的失真度測試儀

    項目總結

  • 實現(xiàn)了PWM+板上LPF電路生成頻率在DC~20KHz的正弦波信號,頻率可調,并且幅度可調,從10mV~500mV,但當幅度小時生成的正弦波幅度偏差較大,當生成直流時,計算FFT會卡死;
  • 實現(xiàn)了256點ADC+DMA采樣,將采樣的波形及其信息顯示在OLED上;
  • 實現(xiàn)了256點FFT、THD的計算,顯示了歸一化幅值譜、對數(shù)坐標顯示失真度曲線。
  • 由于此前作者純Keil與庫函數(shù)開發(fā),此次項目接觸到了CubeMX與HAL庫工具鏈,一鍵生成MDK工程雀食方便,工程項目的排布省時省力。HAL庫在某些包裝上也有其獨特優(yōu)勢,今后用在F407的開發(fā)試試。

    👉工程文件及代碼:參見【2022寒假在家練】基于STM32G031的失真度測試儀
    👉 CSDN:工程源代碼下載
    👉 Github-KafCoppelia/DistortionMeter

    總結

    以上是生活随笔為你收集整理的基于STM32G031的失真度测试仪(CubeMX+ADC+DMA+OLED+EC11)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內容還不錯,歡迎將生活随笔推薦給好友。