Individální projekty MPOA

Mikroprocesory s architekturou ARM

Uživatelské nástroje

Nástroje pro tento web


2019:audio-visualizer

Zvukový vizualizér

Zadání

Pomocí vývojového kitu STM32F429I-DISCOVERY realizujte zvukový vizualizér. Zvukový vstup bude vzorkován pomocí AD převodníku, který je součástí MCU kitu. Spektrum signálu bude zobrazeno na LCD displeji. Za pomoci TFT displeje vytvořte jednoduché ovládání vizualizéru, např. přepínání módů pro zobrazování spektra.

Struktura programu

Na obrázku níže lze vidět stavový diagram zvukového vizualizéru. Program začíná inicializací všech potřebných periferií, jako je např. LCD, GPIO, ADC, TIMER, … Časovač nastavený na 48 kHz trigruje ADC. Pokud je navzorkováno FFT_SIZE vzorků, fft_data_ready se nastaví a nad vzorky se vypočítá FFT. Následně se vypočítané spektrum vykreslí. Po vykreslení spektra (nebo pokud fft_data_ready není nastaven) se čeká na klepnutí na displej. Pokud k tomu dojde, vykreslí se menu s nastavením pro vykreslované spektrum. Testuje se, zda bylo stisknuto nějaké z tlačítek a provádí se příslušná operace. Při stisku tlačítka BACK se program vrací před výpočet spektra, v opačném případě program znova vykreslí menu. Paralelně s programem běží čítač nastavený na trigrování ADC s frekvencí fvz = 48 kHz. Pokud byl vyvolán trigger ADC a zároveň je požadavek na aktuální data (fft_data_ready == false), získá se aktuální hodnota signálu ze vstupu ADC. Pokud se dosáhne počtu vzorků o velikosti FFT_SIZE, fft_data_ready se nastaví na true a může se počítat nová FFT. V opačném případě se inkrementuje pozice pro nový vzorek (sample_pos++).

Pro vypracování projektu jsem využíval Cube MX 5.4.0 IDE s kombinací s projektovou strukturou z předmětu MPOA a editorem EmBitz.

Vzorkování vstupního signálu

Vstupní audio signál je nejdříve nutné posunout do napěťového rozsahu 0-3V, který odpovídá referenci analogového převodníku. K tomu slouží zapojení, které je na obrázku. Kondenzátor 0,47 uF s odporem 20 kΩ tvoří horní propust s mezním kmitočtem fm = 16,93 Hz. Zároveň dělič s odpory 2×20 kΩ posunuje vstupní signál o stejnosměrnou úroveň, která je rovna Vref/2 = 1,5 V. Pokud by zbyl čas, bylo by dobré použít pásmovou propust (např. Wienův článek) s mezními kmitočty fm1 = 20 Hz a fm2 = 20 kHz případně pouze dolní propust s fm = 20 kHz. Frekvence vyšší, než fvz/2 = 24 kHz sice neslyšíme, ale mohou nám pronikat do zobrazovaného spektra z důvodu zrcadlení spektra kolem fvz/2 (aliasing).

Pro AD převod byl zvolen kanál IN13 převodníku ADC2 z MCU vývojového kitu, který je vyveden na pin PC3. Nastavení ADC je vidět na následujícím obrázku. Důležité je nastavení External Trigger Conversion Source, což je zdroj pro vyvolání konverze. Dále pak je třeba v záložce NVIC Settings povolit přerušení od ADC2. Jako časovač byl zvolen TIM2, jehož nastavení lze vidět na následujícím obrázku. Zdrojem hodinového signálu pro časovač je 90 MHz (APB2 timer clocks). Aby časovač vyvolal trigger s frekvencí fvz = 48 kHz, je nutné vypočítat Prescaler a Counter Period z rovnice: (PSC+1)*(ARR+1) = fAPB2/fvz = 90M/48k = 1875. Pak bylo zvoleno PSC = 124 a ARR = 14. Trigger Event Selection se dále nastavilo na Update Event.

HAL_ADC_ConvCpltCallback() se volá po dokončení konverze ADC. Zde se navzorkovaná data přepočítávají na milivolty a ukládají do pole adc_samples[] o maximální velikosti FFT_SIZE. Pokud je toto pole plné nových vzorků, je nastaven flag fft_data_ready, což signalizuje, že se může spočítat FFT nad nově navzorkovanými daty.

Využívaná makra

//ADC values
#define     VREF                  3000    //[mV]
#define     ADC_MAX_RAW           4096    //2pow12
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
  UNUSED(hadc);
 
  uint32_t adc_raw;
 
  static uint16_t sample_pos = 0;
 
  if(!fft_data_ready) //if new data for FFT can be saved
  {
    adc_raw = HAL_ADC_GetValue(&hadc2);//adc_raw = HAL_ADC_GetValue(&hadc1);
    adc_samples[sample_pos] = (adc_raw*VREF/ADC_MAX_RAW);
    if(sample_pos == FFT_SIZE-1)
    {
      sample_pos = 0;
      fft_data_ready = true;
      HAL_GPIO_TogglePin(LD4_GPIO_Port, LD4_Pin);
    }
    else
    {
      sample_pos++;
    }
  }
  //HAL_GPIO_TogglePin(LD4_GPIO_Port, LD4_Pin);
  //HAL_GPIO_TogglePin(PF6_GPIO_Port, PF6_Pin);
}

Výpočet frekvenčního spektra

Pokud jsou navzorkována nová data, překopírují se do pole audio_in[]. Pomocí funkce arm_rfft_fast_f32() je nad nimi vypočítána reálná FFT (resp. reálná diskrétní FFT). Poslední vstupní parametr zmíněné funkce určuje, že se má počítat FFT a ne IFFT. Výsledky jsou uloženy do pole audio_out[]. Jelikož funkce vrací kromě X[0] a X[FFT_SIZE/2] vzorků, které jsou reálné, komplexní čísla, je třeba vypočítat absolutní hodnotu z pole audio_out[] funkcí arm_cmplx_mag_f32(). Výsledná amplituda se převede na decibely a z vypočítaných hodnot se funkcí DrawSpectrum() vykreslí frekvenční spektrum. Vykreslování frekvenčního spektra je popsané níže.

Makro definující velikost FFT.

#define     FFT_SIZE              512
if(fft_data_ready == true)
{
  for(i=0; i<FFT_SIZE; i++)
  {
    //Read all samples from ADC
    audio_in[i] = ((float32_t)adc_samples[i]);
  }
 
  arm_rfft_fast_instance_f32 S;
  arm_rfft_fast_init_f32(&S, FFT_SIZE);
  arm_rfft_fast_f32(&S, audio_in, audio_out, 0);  //Real FFT
  arm_cmplx_mag_f32(audio_out, magnitude, FFT_SIZE);  //Magnitude of FFT data
  magnitude[0] = 1;
 
  for(j=0; j<FFT_SIZE/2; j++)
  {
    //Decibels of magnitude
    magnitude_db[j] = 20.0*log10(magnitude[j]/*/VREF_SPECTRUM*/);
  }
 
  arm_max_f32(magnitude_db, FFT_SIZE, &maxValue, &testIndex);
 
  DrawSpectrum(magnitude_db, scale_y_inc3db, dB_per_px);
 
  //New data for FFT can be saved
  fft_data_ready = false;
}

Pro využití funkcí k výpočtu FFT a absolutní hodnoty komplexního signálu je třeba includovat knihovnu arm_math.h a zkopírovat například z adresáře staženého CubeMX\Drivers\CMSIS zdrojové soubory knihovny DSP do projektové složky (viz. přiložené zdrojáky projektu).

Zobrazení frekvenčního spektra

K vykreslení frekvenčního spektra slouží funkce DrawSpectrum(), kde do ní vstupuje pole vzorků frekvenčního spektra v decibelech, offset osy Y a rozlišení osy Y. Offset slouží k dolazení spektra na požadované zobrazení po hrubém zvětšení/zmenšení spektra pomocí změny rozlišení. Funkce vymaže displej, nastaví barvu pozadí a zavolá funkci pro vykreslení osy X a Y (popis funkce je níže). Následně dojde k vykreslení jednotlivých vzorků. Výška každé spektrální čáry je násobena převrácenou hodnotou rozlišení a je k ní přičítán offset. Měřítko osy Y tedy může být změněno pomocí ovládacích tlačítek v menu, do kterého se dostaneme kliknutím na libovolnou pozici na displeji při vykreslování spektra.

Následující makra slouží pro definování pozice frekvenčního spektra na displeji.

//display and border sizes [px]
#define     MAX_X                 320   //[px]    Maximum LCD size in x axis
#define     MAX_Y                 240   //[px]    Maximum LCD size in y axis
#define     BORDER_X_LEFT         50    //[px]    Border on left side of LCD
#define     BORDER_X_RIGHT        10    //[px]    Border on right side of LCD
#define     BORDER_Y_DOWN         50    //[px]    Down border of LCD
#define     BORDER_Y_UP           10    //[px]    Up border of LCD
 
//spectrum and axis sizes [px]
#define     SPECTRUM_X_START      BORDER_X_LEFT           //50    X axis start of spectrum
#define     SPECTRUM_X_STOP       MAX_X-BORDER_X_RIGHT    //320-10  X axis stop of spectrum
#define     SPECTRUM_Y_START      MAX_Y-BORDER_Y_DOWN     //240-50  Y axis start of spectrum
#define     SPECTRUM_Y_STOP       BORDER_Y_UP             //10    Y axis stop of spectrum
void DrawSpectrum(float32_t *spectrum, int16_t y_scale3db,
                  float32_t y_dB_per_px)
{
  uint16_t x_pos;       //position on X axis of spectrum
  uint32_t y_height;   //height of spectral component
 
    //Clear display, set color, draw axis X and Y and draw spectrum
    GUI_Clear();
    GUI_SetBkColor(GUI_BLACK);
    DrawAxis(y_scale3db, y_dB_per_px);
 
    for(x_pos = 0; x_pos < FFT_SIZE/2; x_pos++)
    {
      y_height = (uint32_t)((spectrum[x_pos]*y_dB_per_px)+((float32_t)(y_scale3db*3)));
 
      GUI_SetColor(GUI_GREEN);
      GUI_SetPenSize(1);
      GUI_DrawLine(SPECTRUM_X_START+x_pos, SPECTRUM_Y_START,
                   SPECTRUM_X_START+x_pos, SPECTRUM_Y_START-y_height);
    }
}
}

Pro vykreslování na LCD displej je třeba nakopírovat do složky projektu zdrojové soubory STemWinu, který lze buďto stáhnout samostatně (STemWin) a nebo je součástí Cube MX v adresáři CubeMX\Middlewares\ST\STemWin. STemWin je profesionální grafická knihovna, ze které se využily pouze základní funkce pro kreslení geometrických tvarů. Tato knihovna byla zvolena, protože bylo třeba otočit orientaci displeje (hlavně textu) o 90°, což nebylo s BSP knihovnou k LCD možné. GUI, které je součástí CubeMX nebylo použito z časových důvodů.

Na následujícím obrázku je nastavení pro displej z CubeMX. Displej byl nastavován podle videa [8] a kromě LTDC bylo nutné nastavit FMC (Flexible Memory Controller) a SPI (Serial Peripheral Interface)

Makra používaná při vykreslování osy X a Y a jejich popisů.

#define     AXIS_WIDTH            3                       //Width of X and Y axis
#define     AXIS_POINT_LENGTH     4                       //Length of points on X and Y axis
#define     TEXT_SIZE             5                       //Text size for X axis labels
#define     TEXT_SIZE2             15                     //Text size for Y axis labels
#define     AXIS1_X_START         SPECTRUM_X_START-AXIS_WIDTH     //axis X
#define     AXIS1_X_STOP          SPECTRUM_X_STOP+BORDER_X_RIGHT/2//axis X
#define     AXIS1_Y_START         SPECTRUM_Y_START+1              //axis X
#define     AXIS1_Y_STOP          SPECTRUM_Y_START+AXIS_WIDTH     //axis X
#define     AXIS2_X_START         SPECTRUM_X_START-AXIS_WIDTH     //axis Y
#define     AXIS2_X_STOP          SPECTRUM_X_START-1              //axis Y
#define     AXIS2_Y_START         BORDER_Y_UP-BORDER_Y_UP/2       //axis Y
#define     AXIS2_Y_STOP          SPECTRUM_Y_START+AXIS_WIDTH     //axis Y
#define     TEXT_OFFSET_X         130                             //X position of text with offset value
#define     TEXT_OFFSET_Y         MAX_Y-20                        //Y position of text with offset value

Voláním funkce DrawAxis() se vykreslí osa X a Y, jejich základní dělení a popisy a také informace o nastaveném offsetu a rozlišení osy Y spektra. Jelikož je funkce dlouhá, je tu zobrazen pouze její začátek a konec. Celá funkce je ve zdrojovém souboru main.c.

//Function to draw X and Y axis of spectrum with their labels and resolution and offset under spectrum
void DrawAxis(int16_t scale_y_inc3db, float32_t dB_px_axis)
{
  uint8_t text_offset[50];
 
 
  GUI_SetColor(GUI_RED);
  //Draw X axis
  GUI_FillRect(AXIS1_X_START, AXIS1_Y_START, AXIS1_X_STOP, AXIS1_Y_STOP);
  //Draw Y axis
  GUI_FillRect(AXIS2_X_START, AXIS2_Y_START, AXIS2_X_STOP, AXIS2_Y_STOP);
 
  //Set of colors and fonts and drawing of X axis points and their labels
  GUI_SetFont(&GUI_Font13B_ASCII);
  GUI_SetColor(GUI_RED);
  GUI_SetTextMode(GUI_TM_TRANS);
  GUI_DispStringHCenterAt("0", SPECTRUM_X_START-15, SPECTRUM_Y_START+10);
 
  GUI_DrawLine(BORDER_X_LEFT+AXIS_WIDTH+CALC_FREQ_POS(FREQ_1k), MAX_Y-BORDER_Y_DOWN+AXIS_WIDTH,
               BORDER_X_LEFT+AXIS_WIDTH+CALC_FREQ_POS(FREQ_1k), MAX_Y-BORDER_Y_DOWN+AXIS_WIDTH+AXIS_POINT_LENGTH);
 
  GUI_DispStringHCenterAt("1", BORDER_X_LEFT+AXIS_WIDTH+CALC_FREQ_POS(FREQ_1k),
                          MAX_Y-BORDER_Y_DOWN+AXIS_WIDTH+AXIS_POINT_LENGTH+TEXT_SIZE);
 
                          .
                          .
                          .
 
  //y label
  GUI_DispStringHCenterAt("A [px]", LABEL1_X, LABEL1_Y);
 
  //Draw set offset and resolution of Y axis of spectrum
  if(dB_px_axis <= 1)
    sprintf(text_offset, "Y - Resolution:%d dB/px;   Offset is %d dB", (uint16_t)(1/dB_px_axis), (3*scale_y_inc3db));
  else if(dB_px_axis > 1)
    sprintf(text_offset, "Y - Resolution: 0.%d dB/px;   Offset is %d dB", (uint16_t)((1/dB_px_axis)*1000), (3*scale_y_inc3db));
 
  GUI_DispStringHCenterAt(text_offset, TEXT_OFFSET_X, TEXT_OFFSET_Y);
 
}

Zobrazení menu

Menu je možné zobrazit dotykem kamkoliv na displej. V menu je pak možné nastavit hrubé rozlišení osy Y a offset pro jemné dolazení s hodnotou +3 dB nebo -3 dB.

//menu buttons sizes [px]
#define     BUTTON_SCALE1_POS_X   20    //button for +3dB (adding offset)
#define     BUTTON_SCALE1_POS_Y   50
#define     BUTTON_SCALE1_SIZE    50
#define     BUTTON_SCALE2_POS_X   2*BUTTON_SCALE1_POS_X+BUTTON_SCALE1_SIZE  //button for -3dB (subtractiong offset)
#define     BUTTON_SCALE2_POS_Y   50
#define     BUTTON_SCALE2_SIZE    50
#define     BUTTON_SCALE3_POS_X   180    //button for +dB/px (increase resolution)
#define     BUTTON_SCALE3_POS_Y   50
#define     BUTTON_SCALE3_SIZE    50
#define     BUTTON_SCALE4_POS_X   BUTTON_SCALE3_POS_X+BUTTON_SCALE3_SIZE+20    //button for -dB/px (decrease resolution)
#define     BUTTON_SCALE4_POS_Y   50
#define     BUTTON_SCALE4_SIZE    50
#define     BUTTON_BACK_POS_X     0       //BACK button
#define     BUTTON_BACK_POS_Y     200
#define     BUTTON_BACK_SIZE_X    80
#define     BUTTON_BACK_SIZE_Y    40
#define     TEXT_BOX1_POS_X       BUTTON_SCALE3_POS_X    //text box for showing +-db/px (resolution of spectrum y axis)
#define     TEXT_BOX1_POS_Y       120
#define     TEXT_BOX1_POS_SIZE_X  120
#define     TEXT_BOX1_POS_SIZE_Y  60
#define     TEXT_BOX2_POS_X       BUTTON_SCALE1_POS_X    //text box for showing +-dB offset (+- 3dB)
#define     TEXT_BOX2_POS_Y       120
#define     TEXT_BOX2_POS_SIZE_X  120
#define     TEXT_BOX2_POS_SIZE_Y  60
//delay for touch [ms]
#define     DELAY_TOUCH           80                      //50ms

Do funkce vstupují nastavené hodnoty rozlišení a offsetu, které pak funkce zobrazí u nastavovacích tlačítek, kterým odpovídá. Nejdříve je vykreslena část pro offset, tzn. dvě nastavovací tlačítka s textem a 1 pole pro zobrazení nastavené hodnoty. Následuje vykreslení části rozlišení (funkce zkrácena-kompletní lze vidět v souboru main.c projektu) opět s dvěma tlačítky pro zvýšení/snížení rozlišení a pak pole pro zobrazení nastavené hodnoty. Zároveň jsou tyto hodnoty vykreslovány spolu osami X a Y a jsou pod spektrem pro informaci. Z menu se lze dostat tlačítkem BACK.

// Function to draw menu with settings of resolution and offset
void DrawMenu(int16_t y_scale3db, float32_t dB_per_px)
{
  uint8_t text_dB_per_px[10];   //array to save string with dB/px resolution
  uint8_t text_3db_offset[10];  //array to save string with offset
 
  //clear display, set colors, set pen size for geometric and set fonts and sizes of text
  GUI_Clear();
  GUI_SetBkColor(GUI_BLACK);
  GUI_SetPenSize(3);
  GUI_SetTextMode(GUI_TM_TRANS);
  GUI_SetFont(&GUI_Font24B_ASCII);
  GUI_SetColor(GUI_RED);
 
  //Y offset column-------------------------------------
  GUI_DispStringHCenterAt("Y Offset", 80, 20);
 
  //Button for offset +3dB and text
  GUI_DrawGradientRoundedV(BUTTON_SCALE1_POS_X, BUTTON_SCALE1_POS_Y,
                           BUTTON_SCALE1_POS_X+BUTTON_SCALE1_SIZE, BUTTON_SCALE1_POS_Y+BUTTON_SCALE1_SIZE,
                           10, GUI_LIGHTGRAY, GUI_GRAY);
 
  GUI_SetColor(GUI_BLACK);
  GUI_DispStringHCenterAt("+3dB", 45, 65);
 
  //Button for offset -3dB and text
  GUI_DrawGradientRoundedV(BUTTON_SCALE2_POS_X, BUTTON_SCALE2_POS_Y,
                           BUTTON_SCALE2_POS_X+BUTTON_SCALE2_SIZE, BUTTON_SCALE2_POS_Y+BUTTON_SCALE2_SIZE,
                           10, GUI_LIGHTGRAY, GUI_GRAY);
 
  GUI_SetColor(GUI_BLACK);
  GUI_DispStringHCenterAt("-3dB", BUTTON_SCALE2_POS_X+(BUTTON_SCALE2_SIZE/2),
                          (BUTTON_SCALE2_POS_Y+(BUTTON_SCALE2_SIZE/2))-10);
 
  //Text box to display offset (+-3dB) and text
  GUI_DrawGradientRoundedV(TEXT_BOX2_POS_X, TEXT_BOX2_POS_Y,
                           TEXT_BOX2_POS_X+TEXT_BOX2_POS_SIZE_X, TEXT_BOX2_POS_Y+TEXT_BOX2_POS_SIZE_Y,
                           10, GUI_LIGHTGRAY, GUI_GRAY);
 
                           .
                           .
                           .
 
}

Závěr

Byl naprogramován zvukový vizualizér, který vzorkuje vstupní signál pomocí ADC převodníku s vzorkovací frekvencí fvz = 48 kHz. Vypočítané frekvenční spektrum je vykreslováno na dotykovém TFT displeji. Pomocí jednoduchého menu je možné měnit rozlišení Y osy spektra. Pro vstup audio signálu byl použit filtr typu horní propust, který ovšem nebyl ideální (frekvence větší jak fvz by způsobovaly aliasing) a z časových důvodů nebyl předělán. Lepší by by byla pásmová propust nalazená na slyšitelné spektrum, tzn. 20 Hz - 20 kHz. Aby bylo možné vykreslovat na displej na šířku, na poslední chvíli byla použita část knihovny STemWin. Lepší by ovšem bylo využití kompletní knihovny STemWin s GUI například přímo v CubeMX. vylepšením by mohlo být, aby frekvenční spektrum nebylo vykreslováno celé, ale bylo rozděleno na oktávy. Každá oktáva by pak zahrnovala spektrum z okolních frekvencí. Šlo by pak lépe vidět rozložení spektra na nižších frekvencích (cca 20 Hz - 1kHz).

Zdrojové soubory

Zdroje

[1] Real FFT Functions. Keil Embedded Development Tools for Arm, Cortex-M, Cortex-R4, 8051, C166, and 251 processor families. [online]. Dostupné z: https://www.keil.com/pack/doc/CMSIS/DSP/html/group__RealFFT.html
[2] Complex Math Functions. Keil Embedded Development Tools for Arm, Cortex-M, Cortex-R4, 8051, C166, and 251 processor families. [online]. Dostupné z: https://www.keil.com/pack/doc/CMSIS/DSP/html/group__groupCmplxMath.html
[3] STM32F4 FFT example - STM32F4 Discovery. STM32F4 Discovery - Libraries and tutorials for STM32F4 series MCUs by Tilen Majerle. Working with STM32F4xx series and Standard peripheral drivers (STD, SPL) or with STM32F0xx, STM32F4xx or STM32F7xx using Hardware abstraction layer libraries (HAL) from STMicroelectronics. My libraries are built on these 2 packages and are highly optimized compared to them. [online]. Copyright © 2020. All Rights Reserved. [cit. 19.01.2020]. Dostupné z: https://stm32f4-discovery.net/2014/10/stm32f4-fft-example/
[4] STM32F4 Discovery board - Keil 5 IDE with CubeMX: Tutorial 5 ADC TIM Trigger - Updated Nov 2017 - YouTube. YouTube [online]. Dostupné z: https://www.youtube.com/watch?v=uohla8zjzkk
[5] Tutorial on STEmWin on STM32F746G Discovery board. Slemi's webpage | Slemi's web page about electronics, airbrush and woodworking [online]. Copyright © 2016 [cit. 19.01.2020]. Dostupné z: http://slemi.info/2017/06/20/stemwin-touch-screen-stm32f746g-dicovery-part-1/
[6] STM32 F7 - Discovery rotate screen - emWin related - SEGGER - Forum. SEGGER - Forum [online]. Dostupné z: https://forum.segger.com/index.php/Thread/2572-STM32-F7-Discovery-rotate-screen/
[7] NXP Semiconductors | Automotive, Security, IoT [online]. Copyright ©0 [cit. 19.01.2020]. Dostupné z: https://www.nxp.com/docs/en/user-guide/UM03001_emWin5_3.pdf
[8] STM32CubeMX Graphic LCD using STM32F429I-DISCO part1 [1/2] - YouTube. YouTube [online]. Dostupné z: https://www.youtube.com/watch?v=u_TVAudWabI&t=3s

2019/audio-visualizer.txt · Poslední úprava: 2020/01/20 19:42 autor: Dominik Indrák