STM32 SAI и микрофон INMP441

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Представим, что у нас есть STM32L4 серии и на нем мы пытаемся подключить микрофон INMP441 через интерфейс SAI. Данный микрофон выводит данные сразу в PCM коде и имеет хорошие звуковые характеристики для своего ценового диапазона.

Быстрым гуглением мы можем найти три основные ссылки по данному вопросу:

  • Общие принципы подключения I2S микрофонов к контроллеру с очень общими словами, но с правильным посылом, которая является переводом и адаптацией AN5027 (ссылка)

  • Сайт товарища, который сделал USB-микрофон используя пример от STM под USB-микрофон и обернул основную программу в C++ (ссылка). Есть гитхаб и видео. В его случае используется интерфейс I2S, который менее гибкий в настройке и, соответственно, который легче сконфигурировать, т.к. примеров в сети очень много.

  • Презентация по SAI интерфейсу STM32L4. В этой статье есть постоянная ссылка, если эта ссылка отвалится. (ссылка)

Допустим, у нас подключение микрофона в режиме моно и активен только левый канал. Подключение имеет следующий вид:

Открываем даташит на микрофон INMP441 и смотрим, что там по таймингам в его протоколе

INMP441
INMP441

https://invensense.tdk.com/wp-content/uploads/2015/02/INMP441.pdf

Ссылка на даташит

Видим, что один полный цикл требует 64 тика на линии SCK. На один слот отведено 32 тика в течении которых передаются 24 бита. В слотах MSB-бит является старшим. WS(Word Select) работает в режиме идентификации каналов. Данные имеют смещение от WS (FS) в 1 бит. Слотов максимум два. Строб (синхронизация) SCK и WS идет по спадающему фронту.

Это все, что нам нужно знать, чтобы сконфигурировать SAI интерфейс на нашем контроллере.

https://programel.ru/files/en.STM32L4_Peripheral_SAI.pdf

На случай, если интернет забудет эту презентацию

Смотрим на картинку таймингов из презентации от ST. Легко видеть, что нам нужно выставить FSPOL = 1, FSOFF = 1 и SCKSTR = 1. С последним у меня возникли ментальные сложности, т.к. я ассоциировал этот регистр с “защелкиванием” данных как, например, в любом другом интерфейсе вроде SPI, I2C, USART и т.д. Сыграл свою роль даташит с таймингами от микрофона с указанием восходящего фронта в середине бита. Я не понимал, почему не показана середина бита в слоте при “защелкивании”, а показано его начало и конец – списал на ошибку в презентации. В данном случае, SCKSTR выполняет роль настройки именно строба синхронизации с WS (FS). Данные уже читаются в нужный момент при правильной настройке строба.

Преступим к настройке самого интерфейса, когда уже известно чего от него хочется

Стоит обратить внимание на строку с Real Audio Frequency. Название говорящее, комментарии излишни.

Есть некоторая сложность с тем, чтобы выбрать правильные настройки PLLSAI1P, подходящие под выбранную частоту семплирования. В AN5027 есть некоторые предлагаемые настройки с самыми ходовыми частотами семплирования. У меня вышло вот так, для выбранной частоты 11025 Гц.

Добавляем ДМА в кольцевом режиме с увеличением адреса в памяти. Ширину слова я поставил в слово (32 бита). По желанию можно выставить в 16 бит (половина слова), тогда результат чтения будет в записываться в две ячейки памяти.

Включаем ОБА прерывания в настройках прерываний. Обработчик прерывания от SAI косвенно связан с прерываниями от DMA, если он включен.

Генерируем проект. Что осталось?

Есть один момент, связанный с порядком инициализации тактирования DMA в SAI. Нужно инициализировать тактирование DMA ДО инициализации SAI, хоть этот код и содержится в библиотеке HAL, она не отрабатыват так как необходимо. Поэтому в main.c ДО инициализации SAI, но ПОСЛЕ инициализаии HAL добавим следующее:

  /* USER CODE BEGIN SysInit */
  __HAL_RCC_DMA2_CLK_ENABLE();
  /* USER CODE END SysInit */

Где именно это записать – ориентируйтесь по комментариям, которые генерирует Cube.

Завести SAI в режиме DMA и складывать данные из DMA-буффера. Нужно организовать некий промежуточный буффер, т.к. данные в изначальном буффере будут перезаписываться по мере работы DMA. Данные складываются так – в прерывании о середине заполнения буффера считываем буффер с начала и до середины. В прерывании об полной передаче считываем данные с середины и уже до конца. Все действия производятся в main.c файле

Старт DMA после инициализации переферии.

 /* USER CODE BEGIN WHILE */  
HAL_SAI_Receive_DMA(&hsai_BlockA1, (uint8_t*)pAudBuf, AUDIO_BUFF_SIZE);

Используемые в вызове функции параметры определены следующим образом

#define AUDIO_BUFF_SIZE 120
static volatile uint32_t pAudBuf[AUDIO_BUFF_SIZE];

Переопределяем в main.c следующие функции

void HAL_SAI_RxHalfCpltCallback(SAI_HandleTypeDef * hsai) {
  for (int i = 0; i < AUDIO_BUFF_SIZE / 2; i++) {
    audio_out_buffer[i * 3 + 2] = (uint8_t)(pAudBuf[i] >> 16);
    audio_out_buffer[i * 3 + 1] = (uint8_t)(pAudBuf[i] >> 8);
    audio_out_buffer[i * 3] = (uint8_t)(pAudBuf[i]);
  }
}

void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef * hsai) {
  for (int i = AUDIO_BUFF_SIZE / 2; i < AUDIO_BUFF_SIZE; i++) {
    audio_out_buffer[i * 3 + 2] = (uint8_t)(pAudBuf[i] >> 16);
    audio_out_buffer[i * 3 + 1] = (uint8_t)(pAudBuf[i] >> 8);
    audio_out_buffer[i * 3] = (uint8_t)(pAudBuf[i]);
  }
}

audio_out_buffer объявлен следующим образом

static volatile uint8_t audio_out_buffer[AUDIO_BUFF_SIZE*3];

Собственно, это все. Дальше с полученным 24 битным звуком можно делать что угодно. Если 24 бита не очень удобны для работы можно просто отбросить младшие разряды и код будет следующим

void HAL_SAI_RxHalfCpltCallback(SAI_HandleTypeDef * hsai) {
  for (int i = 0; i < AUDIO_BUFF_SIZE / 2; i++) {
    audio_out_buffer[i * 2 + 1] = (uint8_t)(pAudBuf[i] >> 16);
    audio_out_buffer[i * 2] = (uint8_t)(pAudBuf[i] >> 8);
  }
}

void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef * hsai) {
  for (int i = AUDIO_BUFF_SIZE / 2; i < AUDIO_BUFF_SIZE; i++) {
    audio_out_buffer[i * 2 + 1] = (uint8_t)(pAudBuf[i] >> 16);
    audio_out_buffer[i * 2] = (uint8_t)(pAudBuf[i] >> 8);
  }
}

Усилить громкость звука, можно также простым смещением. Но тут стоит внимательно отнестить к старшему биту, т.к. именно он определяем знак закодированной в ИКМ синусоиды.

void HAL_SAI_RxHalfCpltCallback(SAI_HandleTypeDef * hsai) {
  for (int i = 0; i < AUDIO_BUFF_SIZE / 2; i++) {
    if (pAudBuf[i] & 0x800000) {
      pAudBuf[i] = (pAudBuf[i] << 2) | 0x800000;
    } else {
      pAudBuf[i] = pAudBuf[i] << 2;
    }
    audio_out_buffer[i * 2 + 1] = (uint8_t)(pAudBuf[i] >> 16);
    audio_out_buffer[i * 2] = (uint8_t)(pAudBuf[i] >> 8);
  }
}
void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef * hsai) {
  for (int i = AUDIO_BUFF_SIZE / 2; i < AUDIO_BUFF_SIZE; i++) {
    if (pAudBuf[i] & 0x800000) {
      pAudBuf[i] = (pAudBuf[i] << 2) | 0x800000;
    } else {
      pAudBuf[i] = pAudBuf[i] << 2;
    }
    audio_out_buffer[i * 2 + 1] = (uint8_t)(pAudBuf[i] >> 16);
    audio_out_buffer[i * 2] = (uint8_t)(pAudBuf[i] >> 8);
  }
}

На этом точно все. Удачного звучания вашим платам

Источник: https://habr.com/ru/articles/789208/


Интересные статьи

Интересные статьи

Купил на пробу светодиодов со встроенным драйвером сразу двух вариантов, в варианте отдельного драйвера и в варианте светодиода. Понимая что радиолюбитель не мигавший красиво диодиками не радиолюбител...
Сравнительно недавно Raspberry Pi Foundation выпустила плату Raspberry Pi Pico, основанную на микроконтроллере (Micro Controller Unit, MCU) RP2040. Эта плата привлекла большое внимание чл...
В этой статье я хочу поделиться опытом разработки электронной книги с использованием недорогого контроллера STM32H750VB, распространенных дискретных компонентов и относительно недорогог...
Привет, Хабр! В этой публикации речь пойдет про мой опыт создания робота для соревнований, а именно следование по линии. Постараюсь рассказать все этапы от проектирования схемотехники и заказа...
Всем привет. Продолжаю развивать свой проект по визуализации трендов данных, добавил возможность просмотра в браузере в реальном времени. Кому интересно прошу.