Individální projekty MPOA

Mikroprocesory s architekturou ARM

Uživatelské nástroje

Nástroje pro tento web


2019:audio-visualizer

Toto je starší verze dokumentu!


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++).

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 Volty 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    //volts
#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. Diskrétní FFT), poslední parametr 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ýslednoá amplituda se převede na decibely a z vypočítaných hodnot se funkcí DrawSpectrum() vykreslí frekvenční spektrum.

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, maxValue, scale_y_inc3db, dB_per_px);
 
  //New data for FFT can be saved
  fft_data_ready = false;
}

Makro definující velikost FFT.

#define     FFT_SIZE              512

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 rozlišením a je k ní přičítán offset. Měřítko spektra 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ů.

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

Závěr

2019/audio-visualizer.1579437817.txt.gz · Poslední úprava: 2020/01/19 13:43 autor: Dominik Indrák