Page 1 of 1

I2S output on STM32: small HOWTO

Posted: Tue Dec 17, 2019 10:03 am
by l0wside
While I have got to like ChibiOS and its HAL, setting up I2S to output data was not only fun. All I could find were some quite old threads in the forum, which were only of limited help.So I am sharing my notes how I achieved to get some sound from my STM32L152. Other STM32 devices are likely similar, refer to the reference manual for details.
I am using Sparkfun´s MAX98357A breakout board. The MAX98357 does not require an MCLK, so this part of the setup is not included.

1. Enable I2S in halconf.h

Code: Select all

 * @brief   Enables the I2S subsystem.
#if !defined(HAL_USE_I2S) || defined(__DOXYGEN__)
#define HAL_USE_I2S                         TRUE

2. Assign a SPI channel and a DMA resource. Note that you need to choose a suitable DMA channel, "Summary of DMA1 requests for each channel" in the reference manual tells you which one. I chose SPI2 and therefore DMA1, channel 5.

Code: Select all

 * SPI driver system settings.
#define STM32_SPI_USE_SPI1                  TRUE
#define STM32_SPI_USE_SPI2                  FALSE
#define STM32_I2S_USE_SPI1                  FALSE
#define STM32_I2S_USE_SPI2                  TRUE
#define STM32_I2S_SPI2_MODE                 STM32_I2S_MODE_MASTER | STM32_I2S_MODE_TX
#define STM32_SPI_SPI1_DMA_PRIORITY         1
#define STM32_SPI_SPI2_DMA_PRIORITY         1
#define STM32_SPI_SPI1_IRQ_PRIORITY         10
#define STM32_SPI_SPI2_IRQ_PRIORITY         10
#define STM32_I2S_SPI2_TX_DMA_STREAM        STM32_DMA_STREAM_ID(1, 5)
#define STM32_I2S_SPI2_RX_DMA_STREAM        0
#define STM32_SPI_DMA_ERROR_HOOK(spip)      osalSysHalt("DMA failure")

Note that you actually need to add code to mcuconf.h (I was used to only modifying some TRUE/FALSE entries).

3. Configure the outputs in board.h. On the STM32L152, SPI is AF5, and SPI2 can be mapped to PB12 (LRCLK), PB13 (BCLK), PB15 (data).

Code: Select all

#define GPIOB_I2S_LRCK              12U
#define GPIOB_I2S_CLK               13U
#define GPIOB_I2S_DATA              15U
#define VAL_GPIOB_MODER            ... |          \
                                     PIN_MODE_ALTERNATE(GPIOB_I2S_LRCK) |          \
                                     PIN_MODE_ALTERNATE(GPIOB_I2S_CLK) |          \
                                     PIN_MODE_INPUT(GPIOB_PIN14) |          \

#define VAL_GPIOB_AFRH              .... |         \
                                     PIN_AFIO_AF(GPIOB_I2S_LRCK, 5U) |         \
                                     PIN_AFIO_AF(GPIOB_I2S_CLK, 5U) |         \
                                     PIN_AFIO_AF(GPIOB_PIN14, 0U) |         \
                                     PIN_AFIO_AF(GPIOB_I2S_DATA, 5U))

Set up the I2S configuration. It is important to set IS2DIV (lowest 8 bits of I2SPR) to a proper value, as it defines the clock. I my case, 0x10 resulted in about 32 kHz on LRCLK or about 1 MHz on BCLK. This caused the MAX98357 to finally produce some noise.

Code: Select all

uint8_t tx_bfr[512];

void i2s_callback(I2SDriver *i2sp) {

int main(void) {

   for (uint16_t k=0; k < 512; k++) {
      tx_bfr[k] = rx_bfr[k] = (uint8_t)k;

   I2SConfig i2s_config = {
         .tx_buffer = tx_bfr,
         .rx_buffer = 0,
         .size = sizeof(tx_bfr) >> 1,
         .end_cb = i2s_callback,
         .i2spr = 0x10


In the hope that it will help someone,