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
SAI. Внутри два подблока, A и B. У каждого свой генератор такта,
своя очередь FIFO на 8 слов и свой выбор: ведущий или ведомый, передача
или приём. Подблоки работают независимо или синхронно: ведомый берёт
такт и начало кадра у соседа (SYNCEN = 01). Так плата STM32746G-Discovery
пишет и читает кодек одним модулем SAI2: блок A — ведущий передатчик,
блок B — ведомый приёмник на тех же проводах SCK и FS.
Что умеет кадр (RM0385, 33.2):
- размер данных — 8, 10, 16, 20, 24 или 32 бита;
- до 16 слотов в кадре, каждый можно включить или пропустить;
- длина кадра, длина и полярность сигнала FS, его сдвиг на бит — настраиваются;
- протоколы I2S, MSB и LSB justified, PCM/DSP, TDM, AC'97, выход SPDIF.
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
- FS низкий 32 такта: 31 такт в этом кадре и такт 63 — начало
следующего: из-за
FSOFF = 1сигнал приходит на бит раньше данных; - при
FSDEF = 1число слотов должно быть чётным: половина — левый канал, половина — правый. Слоты 0 и 1 идут при низком FS, 2 и 3 — при высоком; - программа разобрала поток обратно и получила
0xA001 0xB002 0xC003 0xD004.
Это и есть 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):
- MCLK = ker_ck / (MCKDIV · 2), а при
MCKDIV = 0— MCLK = ker_ck; - отношение MCLK / fs всегда 256;
- SCK = MCLK · (FRL + 1) / 256 — то есть SCK = 64 · fs для кадра в 64 такта.
Ведущему кадр нужен длиной в степень двойки, иначе на такт 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:
- для 11,025, 22,05 и 44,1 кГц: N = 429, Q = 2, DIVQ = 19 — ker_ck = 11 289 473 Гц;
- для остальных: N = 344, Q = 7, DIVQ = 1 — ker_ck = 49 142 857 Гц.
Делитель 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 МГц:
- для 44,1, 22,05 и 11,025 кГц выбор платы — лучший из возможных;
- для 48 и 96 кГц тоже: 0,0186 % — предел при входе 1 МГц; тот же предел получился в 18а у I2S с MCK;
- 8 кГц можно получить точно: N = 256, Q = 5, DIVQ = 25, MCKDIV = 0;
- 16 и 32 кГц — с ошибкой 0,0038 % вместо 0,0186 %: N = 213, Q = 13, DIVQ = 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 Итоги
- SAI — два подблока A и B со своими генераторами и FIFO на 8 слов; кадр до 16 слотов, TDM и I2S одним железом; ведомый подблок синхронно берёт SCK и FS у соседа.
- MCLK = ker_ck / (2 · MCKDIV), MCLK = 256 · fs, SCK = MCLK · (FRL + 1) / 256; у STM32746G-Discovery 48 кГц — это 47 991,071 Гц (−0,0186 %), 44,1 кГц — 44 099,507 Гц (−0,0011 %), и лучше при входе ФАПЧ 1 МГц не бывает.
- Кадр платы: 64 такта, 4 слота по 16 бит, FS низкий 32 такта за бит до
данных; блок A —
0x00202080,0x00051F3F,0x000F0300. - DFSDM: пик Sinc порядка x — ±FOSRx, условие FOSRFORD · IOSR ≤ 231; FOSR до 215 для Sinc4 и до 73 для Sinc5; таблица 160 — 36 из 36 ячеек.
- STM32L476G-Discovery: Sinc3, FOSR 64, такт 2,82 МГц — 44 117,65 Гц (+0,040 %), пик 218 — на 18,1 дБ больше, чем 16 бит после насыщения.
- Модель модулятора 2-го порядка: Sinc3 при FOSR 64 — 75,2 дБ, Sinc1 — 27,5 дБ; каждое удвоение FOSR — около 15 дБ.
Полные исходники
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 с открытым кодом.