Embedded HandbookApps
All sections › III. Peripherals
STM32F746, STM32L476 · level intermediate · 20 min read
Open in the interactive handbook

18б. SAI and DFSDM: multichannel audio and digital microphones

In section 18а audio went through I2S — the second mode of the SPI module on STM32F4: two channels per frame, one direction per module, and simultaneous recording and playback need the I2Sx and I2Sx_ext pair. That is enough for a stereo codec, but not for four channels, not for several microphones and not for a digital microphone with a one-bit output.

Newer series (F7, L4, H7 and others) have two separate peripherals for audio. SAI is the serial audio interface: two independent sub-blocks, a frame of up to 16 slots (TDM), I2S, PCM and AC'97 with the same hardware. DFSDM is a digital filter for sigma-delta modulators: a PDM microphone is connected to it directly, and the stream of ones and zeros is turned into samples without the processor's involvement.

Sources: reference manuals RM0385 (STM32F75x/F74x, chapter 33 "SAI") and RM0351 (STM32L47x/L48x, chapter 24 "DFSDM") — PDFs from the Keil packs; the board drivers of STM32746G-Discovery (SAI2 and the WM8994 codec) and STM32L476G-Discovery (DFSDM and the MP34DT01TR microphone) and HAL from the same packs. Frequencies, the frame, word widths and the microphone model were calculated by the program _verify/sai18b/sai18b.py on a PC; the register-level setup is in _verify/sai18b/sai18b_f7.c and _verify/sai18b/dfsdm18b_l4.c, built with armclang. Checked by _verify/sai18b_check.py.

Terms used in the documentation

Term Meaning
serial audio interface (SAI) serial audio interface
audio sub-block A / B audio sub-block A / B
slot, time division multiplexing (TDM) slot; several channels in turn within one frame
frame synchronization (FS) the frame start signal
master clock (MCLK), master clock divider (MCKDIV) the clock for the codec and its divider
digital filter for sigma delta modulators (DFSDM) digital filter for sigma-delta modulators
pulse density modulation (PDM) pulse density modulation: one bit per clock
Sinc filter, filter oversampling ratio (FOSR) the Sinc filter and its decimation ratio
integrator, integrator oversampling ratio (IOSR) integrator: the sum of several filter outputs
data right bit shift (DTRBS) right shift of the result
output serial clock (CKOUT) clock output for the microphone

18б.1 Two peripherals instead of an SPI mode

Diagram: SAI2 on STM32F746 with two sub-blocks and a codec on a 4-slot TDM frame, DFSDM on STM32L476 with a PDM microphone, a Sinc3 filter and an integrator

SAI. Inside there are two sub-blocks, A and B. Each has its own clock generator, its own 8-word FIFO and its own choice: master or slave, transmit or receive. The sub-blocks work independently or synchronously: the slave takes the clock and frame start from its neighbour (SYNCEN = 01). This is how the STM32746G-Discovery board writes to and reads from the codec with one SAI2 module: block A is the master transmitter, block B is the slave receiver on the same SCK and FS wires.

What the frame can do (RM0385, 33.2):

DFSDM. STM32L476 has one DFSDM1 module: 8 input channels and 4 filters (RM0351, table 155). A channel receives a bit stream from an external modulator — a PDM microphone — and can itself supply its clock on the CKOUT pin. A filter takes the stream of the selected channel, passes it through a Sinc filter of order 1…5, decimates by FOSR, optionally sums another IOSR outputs in the integrator and returns a signed 24-bit sample.


18б.2 The SAI frame: slots and the FS signal

The frame is set by three registers of a sub-block:

Field What it sets STM32746G-Discovery
FRL in SAI_xFRCR frame length − 1, in SCK clocks 63: a 64-clock frame
FSALL active FS duration − 1 31: FS active for 32 clocks
FSDEF FS = frame start + left/right half 1
FSPOL active level of FS 0: low
FSOFF FS one clock before the first bit of slot 0 1
NBSLOT in SAI_xSLOTR number of slots − 1 3: four slots
SLOTSZ slot size 0: equal to the data size, 16 bits
SLOTEN which slots are enabled A: all four; B: 0 and 2

The program built such a frame for the slots 0xA001, 0xB002, 0xC003, 0xD004, starting from the first bit of slot 0:

fs =0000000000000000000000000000000111111111111111111111111111111110
sd =1010000000000001101100000000001011000000000000111101000000000100

This is TDM: four 16-bit channels in a frame that from outside looks like I2S. For recording, the board driver enables slots 0 and 2 (the codec's first digital microphone) or 1 and 3 (the second).

The registers of both sub-blocks, set register by register — sai18b_f7.c. The fields are taken from the header stm32f746xx.h, and the result is checked with _Static_assert against what HAL writes for the board driver settings:

#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;
}

The result: block A — CR1 = 0x00202080, FRCR = 0x00051F3F, SLOTR = 0x000F0300; block B — CR1 = 0x00000683, SLOTR = 0x00050300. The slave is enabled first — that is what the manual recommends. HAL writes the same MCKDIV = 2 into block B, but the slave's clock generator is off, and the divider has no effect.


18б.3 SAI frame rate

The chain from RM0385 (33.3.8):

For the master the frame length must be a power of two, otherwise an SCK clock will not contain a whole even number of MCLK clocks. Table 210 of the manual: with ker_ck = 256 × 192 kHz dividers 0, 1, 2, 6 and 12 give 192, 96, 48, 16 and 8 kHz; with 256 × 44.1 kHz dividers 0, 1, 2 give 44.1, 22.05 and 11.025 kHz. The program recalculated all eight rows — they matched.

What the board does. The PLL input is 1 MHz (HSE_VALUE / PLL_M, as written in the display driver of the same board). The audio driver takes the SAI2 clock from PLLI2S:

HAL calculates the MCKDIV divider itself: ker_ck · 10 / (fs · 512), and rounds up if the last digit is 9. For 48 kHz this gives 19, that is MCKDIV = 2; without this rounding you would get MCKDIV = 1 and a frequency of 95982.143 Hz — twice as high. What you actually get:

Rate ker_ck, Hz MCKDIV Result, Hz Error
8000 49142857 12 7998.512 −0.0186%
11025 11289473 2 11024.877 −0.0011%
16000 49142857 6 15997.024 −0.0186%
22050 11289473 1 22049.753 −0.0011%
32000 49142857 3 31994.048 −0.0186%
44100 11289473 0 44099.507 −0.0011%
48000 49142857 2 47991.071 −0.0186%
96000 49142857 1 95982.143 −0.0186%

At 48 kHz SCK = 64 × 47991.071 = 3071429 Hz, MCLK = 49142857 / 4 = 12.29 MHz.

Can it be more accurate? The program went through all N (50…432, VCO 100…432 MHz), Q (2…15), DIVQ (1…32) and MCKDIV (0…15) with a 1 MHz input:

But there is one PLL for both rates, and the driver switches it only between two families. Exact 8 kHz would require reconfiguring PLLI2S — and everything clocked from it.


18б.4 DFSDM: the Sinc filter, the integrator and word width

A PDM microphone outputs one bit per clock: the louder the signal in that direction, the more ones in a row. The sound hides in the density of ones, and the quantization noise is pushed up in frequency. The filter's job is to cut off this noise and turn millions of bits per second into tens of thousands of multi-bit samples.

A Sinc filter of order x is x moving sums of length FOSR in a row, after which every FOSR-th sample is kept. FORD sets the order (1…5 or the special FastSinc), FOSR the length and decimation (1…1024), IOSR — how many filter outputs the integrator adds up (1…256).

Word width. The full scale at the output of a Sinc of order x is ±FOSRx, for FastSinc — ±2·FOSR², and the integrator multiplies it by IOSR as well (RM0351, tables 160 and 161). The program recalculated table 160: all 36 cells with numbers matched, and in the 5 cells where the manual says "may overflow", the peak really is larger than a 32-bit signed number. Some of the rows:

FOSR Sinc1 Sinc2 FastSinc Sinc3 Sinc4 Sinc5
32 ±32 ±1024 ±2048 ±32768 ±1048576 ±33554432
64 ±64 ±4096 ±8192 ±262144 ±16777216 ±1073741824
256 ±256 ±65536 ±131072 ±16777216 overflow overflow

All the arithmetic inside is in 32-bit signed registers, hence the manual's condition: FOSRFORD · IOSR ≤ 231. This also gives the FOSR limits: for Sinc1…Sinc3 — up to 1024, for Sinc4 — up to 215, for Sinc5 — up to 73. The program found them by brute force, and the manual gives the same numbers.

Shift. The result register RDATAR holds 24 signed bits (bits 31:8). Extra bits are removed by the right shift DTRBS (0…31) — with rounding to nearest and preserving the sign. Then the channel offset OFFSET is subtracted. The rule is simple: the peak after the shift must stay below 223.

Conversion time (RM0351, 24.4.8): the first one is [FOSR · (IOSR − 1 + FORD) + FORD] input clocks, after that in continuous mode with FAST = 1 — FOSR · IOSR clocks. For Sinc3 and FOSR 64 that is 195 clocks for the first sample and 64 for each following one: 69.1 µs and 22.67 µs at a 2.82 MHz clock.


18б.5 The microphone on STM32L476G-Discovery

The board driver clocks DFSDM from the audio PLL PLLSAI1 (8 MHz input): 11.294 MHz (× 24 / 17) for the 44.1 kHz family and 49.143 MHz (× 43 / 7) for the rest. The microphone is connected to channel 2, the filter is filter zero, Sinc3…Sinc5 and FOSR are chosen for each rate, IOSR = 1:

Rate CKOUT divider Microphone clock, Hz Filter FOSR Result, Hz Error Peak Shift After shift
8000 24 2047619 Sinc3 256 7998.51 −0.019% 224 6 218
11025 4 2823529 Sinc3 256 11029.41 +0.040% 224 6 218
16000 24 2047619 Sinc3 128 15997.02 −0.019% 221 3 218
22050 4 2823529 Sinc3 128 22058.82 +0.040% 221 3 218
32000 24 2047619 Sinc4 64 31994.05 −0.019% 224 6 218
44100 4 2823529 Sinc3 64 44117.65 +0.040% 218 0 218
48000 32 1535714 Sinc4 32 47991.07 −0.019% 220 2 218
96000 32 1535714 Sinc5 16 95982.14 −0.019% 220 0 220

You can see how the shift is chosen: for all rates except 96 kHz the peak after it is exactly 218. The 44.1 kHz family here is noticeably less accurate than with SAI on F746: +0.040% versus −0.0011%.

The driver then shifts the register by 8 (>> 8 keeps the whole 24-bit sample without the service bits) and saturates it to ±32767, that is, to 16 bits. But a peak of 218 is 8 times larger than 32768 — that is 18.1 dB: the upper part of the modulator's scale never reaches the program, and a loud sound gets clipped. For 96 kHz the peak is four times higher still.

The 44.1 kHz setup, register by register — dfsdm18b_l4.c (the SAI1 clock is already configured, DMA — as in section 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;
}

The values: CH0CFGR1 = 0x40030000 (clock output from the audio clock, divider 4), CH2CFGR1 = 0x00000084 after the channel is enabled, FLT0FCR = 0x603F0000 (Sinc3, FOSR 64, IOSR 1), FLT0CR1 = 0x22240000 before DFEN. The _Static_assert checks verify these numbers, the condition FOSR3 · IOSR ≤ 231, and that the result fits in 24 bits.

Reading with saturation to 16 bits, as in the driver:

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) compiled it into a single saturation instruction:

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

SSAT with a shift does both >> 8 and the clipping to 16 bits; the address 0x4001 611C is DFSDM1_FLT0RDATAR.


18б.6 Model: a PDM stream through a Sinc filter

How the order and FOSR affect quality was checked by the program on a model. A 2nd-order sigma-delta modulator turns a half-amplitude sine into a ±1 stream at 3.072 MHz; the stream passes through a Sinc filter in integers, as in DFSDM, with a shift to 24 bits; over 2048 output samples the signal-to-noise ratio (SNR) is calculated over the whole band up to half the sample rate. Scale check: a stream of all ones after Sinc3 with FOSR 64 gives 262144, after Sinc5 — 1073741824, as in table 160.

Filter order at FOSR 64 (48 kHz):

Filter Shift SNR
Sinc1 0 27.5 dB
Sinc2 0 58.8 dB
Sinc3 0 75.2 dB
Sinc4 2 78.6 dB
Sinc5 8 80.9 dB

Sinc1 is almost useless: the noise the modulator pushed up folds back into the audio band during decimation. For a 2nd-order modulator the gain above Sinc3 is a few decibels.

Decimation with Sinc3:

FOSR Sample rate SNR
16 192 kHz 44.5 dB
32 96 kHz 60.1 dB
64 48 kHz 75.2 dB
128 24 kHz 90.4 dB

Each doubling of FOSR gives about 15 dB. At 96 kHz, where FOSR is only 16, the board driver takes the highest order — Sinc5.

Droop in the band. Sinc is not a brick-wall filter; it also attenuates the top of the audio range. At FOSR 64 and 48 kHz: Sinc3 loses 0.02 dB at 1 kHz, 1.89 dB at 10 kHz and 7.92 dB at 20 kHz; Sinc5 — 0.03, 3.15 and 13.20 dB. The model confirms the formula: a 10007.8 Hz sine after Sinc3 is 1.87 dB quieter than a sine at about 1 kHz, and the formula for the same frequencies gives 1.87 dB. If the droop is a problem, it is equalized with your own filter after DFSDM.

A real microphone is built differently: the order of its modulator and its noise are not included in the model, so the SNR numbers are only for comparing settings with each other.


18б.7 Pitfalls

The rate is twice as high. MCKDIV was calculated without rounding: for 48 kHz at 49.14 MHz the ratio ker_ck / (fs · 512) is slightly less than 2 (in the HAL calculation with a factor of 10 — 19), the integer part is 1, and you get 96 kHz. HAL rounds up if the last digit is 9; when calculating by hand you must do the same.

There is SCK but no FS. A master transmitter outputs the clock right after SAIEN, but FS only once data has appeared in the FIFO (RM0385, 33.3.3). Start DMA or write the first word first, then wait for the frame.

A frame of the required length cannot be configured. With NODIV = 0 the master's frame length can only be a power of two: otherwise a bit does not contain a whole even number of MCLK clocks. For a frame of another length use NODIV = 1 and ker_ck equal to SCK.

The slots are mixed up. With FSDEF = 1 the first half of the slots goes while FS is active, the second while it is inactive. With FSPOL = 0 the active level is low — the "left" slots 0 and 1 go with FS = 0.

The receiver reads garbage although the transmitter is fine. The same ClockStrobing setting in HAL gives a different CKSTR bit: 0 for the transmitter and 1 for the receiver. When configuring by registers this is easy to forget.

The slave sub-block misses the beginning. It was enabled after the master: the slave starts a frame only from the nearest FS after SAIEN. The manual recommends enabling the slave first.

DFSDM settings are not applied. CKOUTDIV and CKOUTSRC can be written only with DFSDMEN = 0, SPICKSEL with CHEN = 0, FORD, FOSR, IOSR, FAST and RDMAEN with DFEN = 0. Configure first, then set the enable bit.

Loud sound wheezes. The filter peak is larger than fits in 16 bits, and the DTRBS shift is too small: saturation clips the top of the scale. Choose the shift so that the peak after it fits into the required width.

The result overflowed. FOSRFORD · IOSR is greater than 231: for example, Sinc5 with FOSR above 73 or Sinc4 with FOSR above 215.


18б.8 Summary

Full sources

sai18b_f7.c — SAI2 on STM32F746, register by register: 4-slot TDM, 48 kHz.

/* 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 on STM32L476, register by register: PDM microphone, 44.1 kHz.

/* 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));
}

Next: section 18в "A synthesizer on STM32: a look at the Mutable Instruments modules — codec, sample block and real-time DSP" — how I2S, DMA and block calculation come together in real open-source Eurorack modules.

Open in the interactive handbook