Embedded HandbookПриложения
Все разделы › III. Периферия
STM32F746, STM32L476 · уровень средний · 20 мин чтения
Открыть в интерактивном справочнике

18б. SAI и DFSDM: многоканальный звук и цифровые микрофоны

В разделе 18а звук шёл через I2S — второй режим модуля SPI на STM32F4: два канала в кадре, одно направление на модуль, для одновременных записи и воспроизведения нужна пара I2Sx и I2Sx_ext. Этого хватает для стереокодека, но не для четырёх каналов, не для нескольких микрофонов и не для цифрового микрофона с выходом в один бит.

В более новых сериях (F7, L4, H7 и других) для звука есть два отдельных блока. SAI — последовательный звуковой интерфейс: два независимых подблока, кадр до 16 слотов (TDM), I2S, PCM и AC'97 одним и тем же железом. DFSDM — цифровой фильтр для сигма-дельта модуляторов: к нему напрямую подключают PDM-микрофон, а поток единиц и нулей превращается в отсчёты без участия процессора.

Опоры: справочное руководство RM0385 (STM32F75x/F74x, глава 33 «SAI») и RM0351 (STM32L47x/L48x, глава 24 «DFSDM») — PDF из пакетов Keil; драйверы платы STM32746G-Discovery (SAI2 и кодек WM8994) и STM32L476G-Discovery (DFSDM и микрофон MP34DT01TR) и HAL из тех же пакетов. Частоты, кадр, разрядности и модель микрофона посчитаны программой _verify/sai18b/sai18b.py на компьютере; настройка по регистрам — _verify/sai18b/sai18b_f7.c и _verify/sai18b/dfsdm18b_l4.c, собраны armclang. Сверяет _verify/sai18b_check.py.

Как это называется в документации

Оригинал Перевод
serial audio interface (SAI) последовательный звуковой интерфейс
audio sub-block A / B звуковой подблок A / B
slot, time division multiplexing (TDM) слот; несколько каналов по очереди в одном кадре
frame synchronization (FS) сигнал начала кадра
master clock (MCLK), master clock divider (MCKDIV) тактовая частота для кодека и её делитель
digital filter for sigma delta modulators (DFSDM) цифровой фильтр для сигма-дельта модуляторов
pulse density modulation (PDM) импульсно-плотностная модуляция: один бит на такт
Sinc filter, filter oversampling ratio (FOSR) фильтр Sinc и его коэффициент прореживания
integrator, integrator oversampling ratio (IOSR) интегратор: сумма нескольких выходов фильтра
data right bit shift (DTRBS) сдвиг результата вправо
output serial clock (CKOUT) выход такта для микрофона

18б.1 Два блока вместо режима SPI

Схема: SAI2 на STM32F746 с двумя подблоками и кодеком по кадру TDM на 4 слота, DFSDM на STM32L476 с PDM-микрофоном, фильтром Sinc3 и интегратором

SAI. Внутри два подблока, A и B. У каждого свой генератор такта, своя очередь FIFO на 8 слов и свой выбор: ведущий или ведомый, передача или приём. Подблоки работают независимо или синхронно: ведомый берёт такт и начало кадра у соседа (SYNCEN = 01). Так плата STM32746G-Discovery пишет и читает кодек одним модулем SAI2: блок A — ведущий передатчик, блок B — ведомый приёмник на тех же проводах SCK и FS.

Что умеет кадр (RM0385, 33.2):

DFSDM. У STM32L476 один модуль DFSDM1: 8 входных каналов и 4 фильтра (RM0351, таблица 155). Канал принимает поток битов от внешнего модулятора — PDM-микрофона — и может сам выдавать ему такт на вывод CKOUT. Фильтр берёт поток выбранного канала, проводит через Sinc-фильтр порядка 1…5, прореживает в FOSR раз, при желании суммирует ещё IOSR выходов в интеграторе и отдаёт 24-битный отсчёт со знаком.


18б.2 Кадр SAI: слоты и сигнал FS

Кадр задают три регистра подблока:

Поле Что задаёт STM32746G-Discovery
FRL в SAI_xFRCR длина кадра − 1, в тактах SCK 63: кадр 64 такта
FSALL длительность активного FS − 1 31: FS активен 32 такта
FSDEF FS = начало кадра + левая/правая половина 1
FSPOL активный уровень FS 0: низкий
FSOFF FS за такт до первого бита слота 0 1
NBSLOT в SAI_xSLOTR число слотов − 1 3: четыре слота
SLOTSZ размер слота 0: равен размеру данных, 16 бит
SLOTEN какие слоты включены A: все четыре; B: 0 и 2

Программа построила такой кадр для слотов 0xA001, 0xB002, 0xC003, 0xD004, начиная с первого бита слота 0:

fs =0000000000000000000000000000000111111111111111111111111111111110
sd =1010000000000001101100000000001011000000000000111101000000000100

Это и есть TDM: четыре 16-битных канала в кадре, который снаружи выглядит как I2S. Драйвер платы включает для записи слоты 0 и 2 (первый цифровой микрофон кодека) или 1 и 3 (второй).

Регистры обоих подблоков по регистрам — sai18b_f7.c. Поля берутся из заголовка stm32f746xx.h, итог сверен _Static_assert с тем, что пишет HAL для настроек драйвера платы:

#define SAI_A_CR1   ((4u << SAI_xCR1_DS_Pos) | SAI_xCR1_OUTDRIV | (MCKDIV << SAI_xCR1_MCKDIV_Pos))
#define SAI_B_CR1   (SAI_xCR1_MODE_1 | SAI_xCR1_MODE_0 | (4u << SAI_xCR1_DS_Pos) | SAI_xCR1_CKSTR | (1u << SAI_xCR1_SYNCEN_Pos))
#define SAI_FRCR    ((64u - 1u) | ((32u - 1u) << SAI_xFRCR_FSALL_Pos) | SAI_xFRCR_FSDEF | SAI_xFRCR_FSOFF)
#define SAI_A_SLOTR (((4u - 1u) << SAI_xSLOTR_NBSLOT_Pos) | (0xFu << SAI_xSLOTR_SLOTEN_Pos))
#define SAI_B_SLOTR (((4u - 1u) << SAI_xSLOTR_NBSLOT_Pos) | (0x5u << SAI_xSLOTR_SLOTEN_Pos))
void sai2_tdm4(void)
{
    SAI2_Block_B->CR1 = SAI_B_CR1;      /* приёмник: SCK и FS берёт у блока A */
    SAI2_Block_B->FRCR = SAI_FRCR;
    SAI2_Block_B->SLOTR = SAI_B_SLOTR;
    SAI2_Block_B->CR2 = SAI_xCR2_FTH_0; /* порог FIFO — четверть */

    SAI2_Block_A->CR1 = SAI_A_CR1;      /* передатчик и ведущий: выдаёт MCLK, SCK, FS */
    SAI2_Block_A->FRCR = SAI_FRCR;
    SAI2_Block_A->SLOTR = SAI_A_SLOTR;
    SAI2_Block_A->CR2 = SAI_xCR2_FTH_0;

    SAI2_Block_B->CR1 |= SAI_xCR1_SAIEN;  /* ведомый — первым */
    SAI2_Block_A->CR1 |= SAI_xCR1_SAIEN;
}

Итог: блок A — CR1 = 0x00202080, FRCR = 0x00051F3F, SLOTR = 0x000F0300; блок B — CR1 = 0x00000683, SLOTR = 0x00050300. Ведомый включают первым — так советует руководство. HAL записывает в блок B тот же MCKDIV = 2, но у ведомого генератор такта выключен, и делитель ни на что не влияет.


18б.3 Частота кадра SAI

Цепочка из RM0385 (33.3.8):

Ведущему кадр нужен длиной в степень двойки, иначе на такт SCK не придётся целое чётное число тактов MCLK. Таблица 210 руководства: при ker_ck = 256 × 192 кГц делители 0, 1, 2, 6 и 12 дают 192, 96, 48, 16 и 8 кГц; при 256 × 44,1 кГц делители 0, 1, 2 — 44,1, 22,05 и 11,025 кГц. Программа пересчитала все восемь строк — совпало.

Как делает плата. Вход ФАПЧ — 1 МГц (HSE_VALUE / PLL_M, как написано в драйвере экрана той же платы). Драйвер звука берёт такт SAI2 от PLLI2S:

Делитель MCKDIV HAL считает сам: ker_ck · 10 / (fs · 512), и округляет вверх, если последняя цифра — 9. Для 48 кГц выходит 19, то есть MCKDIV = 2; без этого округления получилось бы MCKDIV = 1 и частота 95 982,143 Гц — вдвое выше. Что получается на самом деле:

Частота ker_ck, Гц MCKDIV Получается, Гц Ошибка
8 000 49 142 857 12 7 998,512 −0,0186 %
11 025 11 289 473 2 11 024,877 −0,0011 %
16 000 49 142 857 6 15 997,024 −0,0186 %
22 050 11 289 473 1 22 049,753 −0,0011 %
32 000 49 142 857 3 31 994,048 −0,0186 %
44 100 11 289 473 0 44 099,507 −0,0011 %
48 000 49 142 857 2 47 991,071 −0,0186 %
96 000 49 142 857 1 95 982,143 −0,0186 %

При 48 кГц SCK = 64 × 47 991,071 = 3 071 429 Гц, MCLK = 49 142 857 / 4 = 12,29 МГц.

Можно ли точнее. Программа перебрала все N (50…432, генератор 100…432 МГц), Q (2…15), DIVQ (1…32) и MCKDIV (0…15) при входе 1 МГц:

Но ФАПЧ одна на обе частоты, а драйвер переключает её только между двумя семействами. Ради точных 8 кГц пришлось бы перенастраивать PLLI2S — и всё, что от неё тактуется.


18б.4 DFSDM: фильтр Sinc, интегратор и разрядность

PDM-микрофон на каждый такт выдаёт один бит: чем громче в эту сторону, тем больше единиц подряд. Звук прячется в плотности единиц, шум квантования вытолкнут вверх по частоте. Задача фильтра — отрезать этот шум и сделать из миллионов битов в секунду десятки тысяч многоразрядных отсчётов.

Фильтр Sinc порядка x — x скользящих сумм длиной FOSR подряд, после чего остаётся каждый FOSR-й отсчёт. FORD задаёт порядок (1…5 или особый FastSinc), FOSR — длину и прореживание (1…1024), IOSR — сколько выходов фильтра сложит интегратор (1…256).

Разрядность. Полная шкала на выходе Sinc порядка x — ±FOSRx, у FastSinc — ±2·FOSR², интегратор умножает её ещё на IOSR (RM0351, таблицы 160 и 161). Программа посчитала таблицу 160 заново: совпали все 36 ячеек с числами, а в 5 ячейках, где руководство пишет «может переполниться», пик действительно больше 32-битного знакового числа. Некоторые строки:

FOSR Sinc1 Sinc2 FastSinc Sinc3 Sinc4 Sinc5
32 ±32 ±1 024 ±2 048 ±32 768 ±1 048 576 ±33 554 432
64 ±64 ±4 096 ±8 192 ±262 144 ±16 777 216 ±1 073 741 824
256 ±256 ±65 536 ±131 072 ±16 777 216 переполнение переполнение

Весь счёт внутри — в 32-битных знаковых регистрах, поэтому условие руководства: FOSRFORD · IOSR ≤ 231. Отсюда и пределы FOSR: для Sinc1…Sinc3 — до 1024, для Sinc4 — до 215, для Sinc5 — до 73. Программа нашла их перебором, руководство даёт те же числа.

Сдвиг. Регистр результата RDATAR хранит 24 бита со знаком (биты 31:8). Лишние разряды убирает сдвиг вправо DTRBS (0…31) — с округлением к ближайшему и с сохранением знака. Затем вычитается смещение канала OFFSET. Правило простое: пик после сдвига должен оставаться меньше 223.

Время преобразования (RM0351, 24.4.8): первое — [FOSR · (IOSR − 1 + FORD) + FORD] тактов входа, дальше в непрерывном режиме с FAST = 1 — FOSR · IOSR тактов. Для Sinc3 и FOSR 64 это 195 тактов на первый отсчёт и по 64 на каждый следующий: 69,1 мкс и 22,67 мкс при такте 2,82 МГц.


18б.5 Микрофон на STM32L476G-Discovery

Драйвер платы тактирует DFSDM от звуковой ФАПЧ PLLSAI1 (вход 8 МГц): 11,294 МГц (× 24 / 17) для семейства 44,1 кГц и 49,143 МГц (× 43 / 7) для остальных. Микрофон подключён к каналу 2, фильтр — нулевой, Sinc3…Sinc5 и FOSR подобраны под каждую частоту, IOSR = 1:

Частота Делитель CKOUT Такт микрофона, Гц Фильтр FOSR Получается, Гц Ошибка Пик Сдвиг После сдвига
8 000 24 2 047 619 Sinc3 256 7 998,51 −0,019 % 224 6 218
11 025 4 2 823 529 Sinc3 256 11 029,41 +0,040 % 224 6 218
16 000 24 2 047 619 Sinc3 128 15 997,02 −0,019 % 221 3 218
22 050 4 2 823 529 Sinc3 128 22 058,82 +0,040 % 221 3 218
32 000 24 2 047 619 Sinc4 64 31 994,05 −0,019 % 224 6 218
44 100 4 2 823 529 Sinc3 64 44 117,65 +0,040 % 218 0 218
48 000 32 1 535 714 Sinc4 32 47 991,07 −0,019 % 220 2 218
96 000 32 1 535 714 Sinc5 16 95 982,14 −0,019 % 220 0 220

Видно, как подобран сдвиг: для всех частот, кроме 96 кГц, пик после него — ровно 218. Семейство 44,1 кГц здесь заметно менее точное, чем у SAI на F746: +0,040 % против −0,0011 %.

Дальше драйвер сдвигает регистр на 8 (>> 8 оставляет весь 24-битный отсчёт без служебных бит) и насыщает до ±32 767, то есть до 16 бит. Но пик 218 в 8 раз больше 32 768 — это 18,1 дБ: верхняя часть шкалы модулятора до программы не доходит, громкий звук обрежется. Для 96 кГц пик ещё вчетверо выше.

Настройка 44,1 кГц по регистрам — dfsdm18b_l4.c (такт SAI1 уже настроен, DMA — как в разделе 9):

#define CH0_CFGR1 (DFSDM_CHCFGR1_CKOUTSRC | ((CKOUT_DIV - 1u) << DFSDM_CHCFGR1_CKOUTDIV_Pos))
#define CH2_CFGR1 (1u << DFSDM_CHCFGR1_SPICKSEL_Pos)   /* SITP = 00: SPI, данные по фронту */
#define FLT_FCR   ((FORD << DFSDM_FLTFCR_FORD_Pos) | ((FOSR - 1u) << DFSDM_FLTFCR_FOSR_Pos) | (IOSR - 1u))
#define FLT_CR1   (DFSDM_FLTCR1_FAST | (2u << DFSDM_FLTCR1_RCH_Pos) | DFSDM_FLTCR1_RDMAEN | DFSDM_FLTCR1_RCONT)
void dfsdm_mic_44k(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_DFSDM1EN;
    DFSDM1_Channel0->CHCFGR1 = CH0_CFGR1;           /* CKOUT задаётся при DFSDMEN = 0 */
    DFSDM1_Channel2->CHCFGR2 = SHIFT << DFSDM_CHCFGR2_DTRBS_Pos;
    DFSDM1_Channel2->CHCFGR1 = CH2_CFGR1;           /* SPICKSEL — при CHEN = 0 */
    DFSDM1_Channel2->CHCFGR1 |= DFSDM_CHCFGR1_CHEN;
    DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN;

    DFSDM1_Filter0->FLTFCR = FLT_FCR;               /* пишется при DFEN = 0 */
    DFSDM1_Filter0->FLTCR1 = FLT_CR1;
    DFSDM1_Filter0->FLTCR1 |= DFSDM_FLTCR1_DFEN;
    DFSDM1_Filter0->FLTCR1 |= DFSDM_FLTCR1_RSWSTART;
}

Значения: CH0CFGR1 = 0x40030000 (выход такта от звуковой частоты, делитель 4), CH2CFGR1 = 0x00000084 после включения канала, FLT0FCR = 0x603F0000 (Sinc3, FOSR 64, IOSR 1), FLT0CR1 = 0x22240000 до DFEN. _Static_assert проверяют и эти числа, и условие FOSR3 · IOSR ≤ 231, и то, что результат влезает в 24 бита.

Чтение с насыщением до 16 бит, как в драйвере:

int16_t dfsdm_read16(void)
{
    int32_t v = (int32_t)DFSDM1_Filter0->FLTRDATAR >> 8;
    return (int16_t)(v > 32767 ? 32767 : (v < -32768 ? -32768 : v));
}

armclang (-O2) собрал из него одну команду насыщения:

MOV      r0,#0x611c
MOVT     r0,#0x4001
LDR      r0,[r0,#0]
SSAT     r0,#16,r0,ASR #8
BX       lr

SSAT со сдвигом делает и >> 8, и обрезку до 16 бит; адрес 0x4001 611C — DFSDM1_FLT0RDATAR.


18б.6 Модель: PDM-поток через Sinc-фильтр

Как влияют порядок и FOSR на качество, программа проверила на модели. Сигма-дельта модулятор 2-го порядка превращает синус половинной амплитуды в поток ±1 с частотой 3,072 МГц; поток проходит Sinc-фильтр в целых числах, как в DFSDM, со сдвигом до 24 бит; по 2048 выходным отсчётам считается отношение сигнал/шум (SNR) во всей полосе до половины частоты отсчётов. Проверка шкалы: поток из одних единиц после Sinc3 при FOSR 64 даёт 262 144, после Sinc5 — 1 073 741 824, как в таблице 160.

Порядок фильтра при FOSR 64 (48 кГц):

Фильтр Сдвиг SNR
Sinc1 0 27,5 дБ
Sinc2 0 58,8 дБ
Sinc3 0 75,2 дБ
Sinc4 2 78,6 дБ
Sinc5 8 80,9 дБ

Sinc1 почти бесполезен: шум, вытолкнутый модулятором вверх, при прореживании складывается обратно в звуковую полосу. Для модулятора 2-го порядка выше Sinc3 прибавка — единицы децибел.

Прореживание при Sinc3:

FOSR Частота отсчётов SNR
16 192 кГц 44,5 дБ
32 96 кГц 60,1 дБ
64 48 кГц 75,2 дБ
128 24 кГц 90,4 дБ

Каждое удвоение FOSR даёт около 15 дБ. Драйвер платы при 96 кГц, где FOSR всего 16, берёт самый высокий порядок — Sinc5.

Завал в полосе. Sinc — не прямоугольный фильтр, он ослабляет и верх звукового диапазона. При FOSR 64 и 48 кГц: Sinc3 теряет 0,02 дБ на 1 кГц, 1,89 дБ на 10 кГц и 7,92 дБ на 20 кГц; Sinc5 — 0,03, 3,15 и 13,20 дБ. Модель подтверждает формулу: синус 10 007,8 Гц после Sinc3 тише синуса около 1 кГц на 1,87 дБ, формула для тех же частот — 1,87 дБ. Если завал мешает, его выравнивают своим фильтром уже после DFSDM.

Реальный микрофон устроен иначе: порядок его модулятора и шумы в модели не учтены, числа SNR — только для сравнения настроек между собой.


18б.7 Грабли

Частота вдвое выше. MCKDIV посчитан без округления: для 48 кГц при 49,14 МГц отношение ker_ck / (fs · 512) чуть меньше 2 (в счёте HAL с множителем 10 — 19), целая часть — 1, и выходят 96 кГц. HAL округляет вверх, если последняя цифра — 9; при расчёте вручную нужно так же.

SCK есть, а FS нет. Ведущий передатчик выдаёт такт сразу после SAIEN, но FS — только когда в FIFO появились данные (RM0385, 33.3.3). Сначала запускают DMA или пишут первое слово, потом ждут кадр.

Кадр нужной длины не настраивается. При NODIV = 0 длина кадра ведущего — только степень двойки: иначе на бит не приходится целое чётное число тактов MCLK. Для кадра другой длины — NODIV = 1 и ker_ck, равный SCK.

Слоты перепутаны. При FSDEF = 1 первая половина слотов идёт при активном FS, вторая — при неактивном. С FSPOL = 0 активен низкий уровень — «левые» слоты 0 и 1 идут при FS = 0.

Приёмник читает мусор, хотя передатчик в порядке. Одна и та же настройка ClockStrobing в HAL даёт разный бит CKSTR: 0 для передатчика и 1 для приёмника. При настройке по регистрам это легко забыть.

Ведомый блок пропускает начало. Его включили после ведущего: ведомый начинает кадр только с ближайшего FS после SAIEN. Руководство советует включать ведомый первым.

Настройки DFSDM не применяются. CKOUTDIV и CKOUTSRC пишутся только при DFSDMEN = 0, SPICKSEL — при CHEN = 0, FORD, FOSR, IOSR, FAST и RDMAEN — при DFEN = 0. Сначала настройка, потом бит включения.

Громкий звук хрипит. Пик фильтра больше, чем помещается в 16 бит, а сдвиг DTRBS мал: насыщение режет верх шкалы. Сдвиг подбирают так, чтобы пик после него вписался в нужную разрядность.

Результат переполнился. FOSRFORD · IOSR больше 231: например, Sinc5 при FOSR больше 73 или Sinc4 при FOSR больше 215.


18б.8 Итоги

Полные исходники

sai18b_f7.c — SAI2 на STM32F746 по регистрам: TDM 4 слота, 48 кГц.

/* 18б: SAI2 на STM32F746 по регистрам — TDM 4 слота по 16 бит, 48 кГц, как в BSP STM32746G-Discovery.
   Собирается armclang: -DSTM32F746xx. Вход PLLI2S — 1 МГц (HSE_VALUE / PLL_M, как в BSP платы). */
#include <stdint.h>

#define I2SN   344u                     /* VCO 344 МГц */
#define I2SQ   7u                       /* 344 / 7 = 49,142857 МГц */
#define MCKDIV 2u                       /* MCLK = 49,142857 / 4 = 12,285714 МГц = 256 x 47 991 Гц */

#define SAI_A_CR1   ((4u << SAI_xCR1_DS_Pos) | SAI_xCR1_OUTDRIV | (MCKDIV << SAI_xCR1_MCKDIV_Pos))
#define SAI_B_CR1   (SAI_xCR1_MODE_1 | SAI_xCR1_MODE_0 | (4u << SAI_xCR1_DS_Pos) | SAI_xCR1_CKSTR | (1u << SAI_xCR1_SYNCEN_Pos))
#define SAI_FRCR    ((64u - 1u) | ((32u - 1u) << SAI_xFRCR_FSALL_Pos) | SAI_xFRCR_FSDEF | SAI_xFRCR_FSOFF)
#define SAI_A_SLOTR (((4u - 1u) << SAI_xSLOTR_NBSLOT_Pos) | (0xFu << SAI_xSLOTR_SLOTEN_Pos))
#define SAI_B_SLOTR (((4u - 1u) << SAI_xSLOTR_NBSLOT_Pos) | (0x5u << SAI_xSLOTR_SLOTEN_Pos))

_Static_assert(SAI_A_CR1 == 0x00202080u, "A: ведущий передатчик, 16 бит, MCKDIV = 2");
_Static_assert(SAI_B_CR1 == 0x00000683u, "B: ведомый приёмник, синхронно с A");
_Static_assert(SAI_FRCR == 0x00051F3Fu, "кадр 64 такта, FS 32 такта, FSDEF, FSOFF, FS активен низким");
_Static_assert(SAI_A_SLOTR == 0x000F0300u && SAI_B_SLOTR == 0x00050300u, "4 слота; A — все, B — 0 и 2");
_Static_assert(RCC_PLLI2SCFGR_PLLI2SN_Pos == 6 && RCC_PLLI2SCFGR_PLLI2SQ_Pos == 24, "поля PLLI2S");

void sai_clock_48k(void)
{
    RCC->CR &= ~RCC_CR_PLLI2SON;
    while (RCC->CR & RCC_CR_PLLI2SRDY) {
    }
    RCC->PLLI2SCFGR = (I2SN << RCC_PLLI2SCFGR_PLLI2SN_Pos) | (I2SQ << RCC_PLLI2SCFGR_PLLI2SQ_Pos)
                    | (2u << RCC_PLLI2SCFGR_PLLI2SR_Pos);
    RCC->DCKCFGR1 = (RCC->DCKCFGR1 & ~(RCC_DCKCFGR1_PLLI2SDIVQ | RCC_DCKCFGR1_SAI2SEL))
                  | (0u << RCC_DCKCFGR1_PLLI2SDIVQ_Pos)      /* DIVQ = 1 */
                  | (1u << RCC_DCKCFGR1_SAI2SEL_Pos);        /* SAI2 от PLLI2S */
    RCC->CR |= RCC_CR_PLLI2SON;
    while (!(RCC->CR & RCC_CR_PLLI2SRDY)) {
    }
    RCC->APB2ENR |= RCC_APB2ENR_SAI2EN;
}

void sai2_tdm4(void)
{
    SAI2_Block_B->CR1 = SAI_B_CR1;      /* приёмник: SCK и FS берёт у блока A */
    SAI2_Block_B->FRCR = SAI_FRCR;
    SAI2_Block_B->SLOTR = SAI_B_SLOTR;
    SAI2_Block_B->CR2 = SAI_xCR2_FTH_0; /* порог FIFO — четверть */

    SAI2_Block_A->CR1 = SAI_A_CR1;      /* передатчик и ведущий: выдаёт MCLK, SCK, FS */
    SAI2_Block_A->FRCR = SAI_FRCR;
    SAI2_Block_A->SLOTR = SAI_A_SLOTR;
    SAI2_Block_A->CR2 = SAI_xCR2_FTH_0;

    SAI2_Block_B->CR1 |= SAI_xCR1_SAIEN;  /* ведомый — первым */
    SAI2_Block_A->CR1 |= SAI_xCR1_SAIEN;
}

dfsdm18b_l4.c — DFSDM на STM32L476 по регистрам: PDM-микрофон, 44,1 кГц.

/* 18б: DFSDM на STM32L476 по регистрам — PDM-микрофон на канале 2, фильтр 0, 44,1 кГц,
   как в BSP STM32L476G-Discovery. Собирается armclang: -DSTM32L476xx.
   Такт SAI1 = 11,294 МГц (PLLSAI1: 8 МГц x 24 / 17) настроен заранее. */
#include <stdint.h>

#define CKOUT_DIV 4u                    /* CKOUT = 11,294 / 4 = 2,8235 МГц — такт микрофона */
#define FORD      3u                    /* Sinc3 */
#define FOSR      64u                   /* 2,8235 МГц / 64 = 44 117,6 Гц */
#define IOSR      1u                    /* интегратор не нужен */
#define SHIFT     0u                    /* пик 64^3 = 2^18 и так влезает в 24 бита */

#define CH0_CFGR1 (DFSDM_CHCFGR1_CKOUTSRC | ((CKOUT_DIV - 1u) << DFSDM_CHCFGR1_CKOUTDIV_Pos))
#define CH2_CFGR1 (1u << DFSDM_CHCFGR1_SPICKSEL_Pos)   /* SITP = 00: SPI, данные по фронту */
#define FLT_FCR   ((FORD << DFSDM_FLTFCR_FORD_Pos) | ((FOSR - 1u) << DFSDM_FLTFCR_FOSR_Pos) | (IOSR - 1u))
#define FLT_CR1   (DFSDM_FLTCR1_FAST | (2u << DFSDM_FLTCR1_RCH_Pos) | DFSDM_FLTCR1_RDMAEN | DFSDM_FLTCR1_RCONT)

_Static_assert(CH0_CFGR1 == 0x40030000u, "CKOUT от звукового такта, делитель 4");
_Static_assert((CH2_CFGR1 | DFSDM_CHCFGR1_CHEN) == 0x00000084u, "канал 2: SPI по фронту, такт — CKOUT");
_Static_assert(FLT_FCR == 0x603F0000u, "Sinc3, FOSR 64, IOSR 1");
_Static_assert(FLT_CR1 == 0x22240000u, "FAST, RCH = 2, DMA, непрерывно");
_Static_assert(FOSR * FOSR * FOSR * IOSR <= (1u << 31), "FOSR^FORD * IOSR <= 2^31 (RM0351)");
_Static_assert(((FOSR * FOSR * FOSR * IOSR) >> SHIFT) < (1u << 23), "результат влезает в 24 бита со знаком");

void dfsdm_mic_44k(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_DFSDM1EN;
    DFSDM1_Channel0->CHCFGR1 = CH0_CFGR1;           /* CKOUT задаётся при DFSDMEN = 0 */
    DFSDM1_Channel2->CHCFGR2 = SHIFT << DFSDM_CHCFGR2_DTRBS_Pos;
    DFSDM1_Channel2->CHCFGR1 = CH2_CFGR1;           /* SPICKSEL — при CHEN = 0 */
    DFSDM1_Channel2->CHCFGR1 |= DFSDM_CHCFGR1_CHEN;
    DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN;

    DFSDM1_Filter0->FLTFCR = FLT_FCR;               /* пишется при DFEN = 0 */
    DFSDM1_Filter0->FLTCR1 = FLT_CR1;
    DFSDM1_Filter0->FLTCR1 |= DFSDM_FLTCR1_DFEN;
    DFSDM1_Filter0->FLTCR1 |= DFSDM_FLTCR1_RSWSTART;
}

/* 24 бита со знаком лежат в RDATAR[31:8]; до 16 бит — с насыщением, как в BSP */
int16_t dfsdm_read16(void)
{
    int32_t v = (int32_t)DFSDM1_Filter0->FLTRDATAR >> 8;
    return (int16_t)(v > 32767 ? 32767 : (v < -32768 ? -32768 : v));
}

Дальше: раздел 18в «Синтезатор на STM32: разбор модулей Mutable Instruments — кодек, блок сэмплов и DSP в реальном времени» — как I2S, DMA и расчёт блока собраны в настоящих модулях Eurorack с открытым кодом.

Открыть в интерактивном справочнике