LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

Analizator widma z FFT na STM32 z Cortex-M4

Odbiór sygnału audio z mikrofonu

Kiedy mamy pewność, że wyznaczanie widma częstotliwościowego sygnału działa poprawnie to możemy iść dalej i zamiast przygotowanego wcześniej sygnału wykorzystać sygnał odbierany z mikrofonu. Przedtem jednak konfigurujemy wykorzystywane peryferia:

static void RCC_Configure(void){
  /********/
  /* AHB1 */
  /********/
  // Włączenie sygnału taktującego układ CRC jest wymagane przez bibliotekę PDM
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOC |
                         RCC_AHB1Periph_CRC, ENABLE);

  /********/
  /* APB1 */
  /********/
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

  // Włączenie sygnału taktującego dla układu I2S
  RCC_PLLI2SCmd(ENABLE);
}

static void NVIC_Configure(void){
  NVIC_InitTypeDef NVIC_InitStructure;

  // Konfiguracja grupy priorytetów
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

  // Konfiguracja przerwań od SPI2 (w tym również dla I2S2)
  NVIC_InitStructure.NVIC_IRQChannel = SPI2_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  NVIC_Init(&NVIC_InitStructure);
}

static void GPIO_Configure(void){
  GPIO_InitTypeDef GPIO_InitStructure;

  // Konfiguracja linii PB10 podłączonej do CLK układu MP45DT02 – funkcja I2S2_CLK
  GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_10;
  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;
  GPIO_Init(GPIOB, &GPIO_InitStructure);

  // Konfiguracja linii PC3 podłączonej do DOUT układu MP45DT02 - funkcja I2S2_DATA
  GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_3;
  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;
  GPIO_Init(GPIOC, &GPIO_InitStructure);

  GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_SPI2);  // Podłączenie PB10 do SPI2
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource3, GPIO_AF_SPI2);   // Podłączenie PC3 do SPI2
}

static void I2S_Configure(void){
  I2S_InitTypeDef I2S_InitStructure;

  SPI_I2S_DeInit(SPI2);
  I2S_InitStructure.I2S_AudioFreq = OUT_FREQ*2;
  I2S_InitStructure.I2S_Standard = I2S_Standard_LSB;
  I2S_InitStructure.I2S_DataFormat = I2S_DataFormat_16b;
  I2S_InitStructure.I2S_CPOL = I2S_CPOL_High;
  I2S_InitStructure.I2S_Mode = I2S_Mode_MasterRx;
  I2S_InitStructure.I2S_MCLKOutput = I2S_MCLKOutput_Disable;
  I2S_Init(SPI2, &I2S_InitStructure);

  // Włączenie przerwań od zapełnienia bufora odbiorczego I2S2
  SPI_I2S_ITConfig(SPI2, SPI_I2S_IT_RXNE, ENABLE);
}

Po skonfigurowaniu peryferii można wykonać inicjalizację biblioteki PDM:

Filter.Fs = OUT_FREQ;       // Częstotliwość próbkowania
Filter.HP_HZ = 10;          // Częstotliwość odcięcia filtru górnoprzepustowego
Filter.LP_HZ = 16000;       // Częstotliwość odcięcia filtru dolnoprzepustowego
Filter.In_MicChannels = 1;  // Liczba kanałów wejściowych
Filter.Out_MicChannels = 1; // Liczba kanałów wyjściowych
PDM_Filter_Init(&Filter);   // Inicjalizacja biblioteki PDM

W pliku main.h zdefiniowane są stałe wykorzystywane w programie:

#define DECIMATION_FACTOR       64
#define OUT_FREQ                32000
#define PDM_Input_Buffer_SIZE   ((OUT_FREQ/1000)*DECIMATION_FACTOR/8)
#define PCM_Output_Buffer_SIZE  (OUT_FREQ/1000)

#define SPECTRUM_BG_COLOR       Black
#define SPECTRUM_FREQ_S_kHz     32.0
#define SPECTRUM_HEIGHT         150
#define SPECTRUM_NR_SAMPLES     512
#define SPECTRUM_X_LABEL        "[kHz]"

Warto zwrócić uwagę na konfigurację układu I2S oraz biblioteki PDM pod względem wartości częstotliwości. Ponieważ biblioteka PDM oferuje cztery funkcje konwertujące sygnał z postaci PDM na PCM (współczynnik decymacji 64 lub 80, dane w kolejności MSB lub LSB; rysunek 14):

int32_t PDM_Filter_64_MSB(uint8_t* data, uint16_t* dataOut, uint16_t MicGain,  PDMFilter_InitStruct * Filter);
int32_t PDM_Filter_80_MSB(uint8_t* data, uint16_t* dataOut, uint16_t MicGain,  PDMFilter_InitStruct * Filter);
int32_t PDM_Filter_64_LSB(uint8_t* data, uint16_t* dataOut, uint16_t MicGain,  PDMFilter_InitStruct * Filter);
int32_t PDM_Filter_80_LSB(uint8_t* data, uint16_t* dataOut, uint16_t MicGain,  PDMFilter_InitStruct * Filter);

to wymaganym jest aby częstotliwość na linii CLK układu MP45DT02 była równa:

FCLK = DecimatorFactor · FS

Tak więc, jeśli chcemy uzyskać częstotliwość próbkowania 32 kHz oraz wykorzystać współczynnik decymacji o wartości 64 to częstotliwość na linii CLK powinna wynosić 2048 kHz. Aby ustawić taką wartość w układzie I2S należy wiedzieć, że układ w trybie normalnej pracy odbiera po 16 bitów danych (najmniejsza możliwa wartość pola I2S_DataFormat) na przemian z kanału pierwszego oraz drugiego. A ponieważ w danej sytuacji będzie on cały czas odbierał dane z jednego źródła to na odbiór „próbki z dwóch kanałów” przeznaczone są 32 bity, czyli 2048 kHz / 32 = 64 kHz – taka też wartość jest ustawiana w polu I2S_AudioFreq.

 

Rys. 14. Proces obróbki sygnału przez bibliotekę PDM

Rys. 14. Proces obróbki sygnału przez bibliotekę PDM

 

Po włączeniu odbioru danych przez układ I2S komendą I2S_Cmd(SPI2, ENABLE) dane będą najpierw kopiowane do rejestru odbiorczego, a następnie w przerwaniu zostaną przeniesione do bufora PDM_Input_Buffer. Gdy bufor ten zostanie całkowicie zapełniony to wykona się funkcja PDM_Filter_64_LSB() konwertująca sygnał z postaci PDM do postaci PCM (plik stm32f4xx_it.c):

uint32_t InternalBufferSize = 0;
uint32_t Data_Status = 0;

void SPI2_IRQHandler(void){
  extern PDMFilter_InitStruct Filter;
  extern uint8_t  PDM_Input_Buffer[];
  extern uint16_t PCM_Output_Buffer[];

  u16 volume;
  u16 app;

  // Sprawdź czy są dostępne nowe dane
  if (SPI_GetITStatus(SPI2, SPI_I2S_IT_RXNE) != RESET){
    // Odczytaj dane i zapisz do bufora – najpierw młodszy potem starszy bajt
    app = SPI_I2S_ReceiveData(SPI2);
    PDM_Input_Buffer[InternalBufferSize++] = (uint8_t)app;
    PDM_Input_Buffer[InternalBufferSize++] = (uint8_t)HTONS(app);

    // Sprawdź czy bufor jest pełny
    if (InternalBufferSize >= PDM_Input_Buffer_SIZE){
      InternalBufferSize = 0;

      volume = 50;

      PDM_Filter_64_LSB(PDM_Input_Buffer, PCM_Output_Buffer, volume, &Filter);
      Data_Status = 1;
    }
  }
}

 

Autor: Jan Szemiet