STM32 I2S Interface With A MEMS Microphone

I’ve started using STMicroelectronics STM32 microcontrollers in my client’s embedded applications. Recently I needed to interface an STM32G0x0 MCU with a Knowles SPH0645LM4H-1 MEMS microphone using I2S interface.

The STM32G0x0 MCU is an entry level, 32-bit Arm® Cortext®-M0+ MCU that runs up to 64MHz. This MCU series is cost effective and part of the ST STM32 value line. My client selected the STM32G030F6, which has 32Kbytes of Flash and 8Kbytes of RAM packaged in a 20-pin TSSOP. This MCU has I2C, USART, SPI/I2S communication interfaces, a 12-bit ADC, multiple GPIOs, timers, DMA, and RTC.

The Knowles SPH0645LM4H-1 is a miniature, low power, bottom ported MEMS microphone with a 24-bit I2S standard digital interface. The microphone supports 16kHz to 64kHz sampling. This microphone can be configured for either a right or left channel by an external signal. The key microphone feature is the output is already in PCM format so no additional CODEC hardware or PDM firmware conversion is needed in this application.

I2S (Inter-IC Sound) is a serial bus communication standard used for communicating with digital audio devices. Philip Semiconductor introduced the standard in 1986. The interface has three signals, serial clock (SCLK), word select (WS) and serial data (SD). SCLK is a continuous signal and is 32 or 64 times the audio sample rate based on word size. For example working with 32-bit data frames for a 16KHz audio, SCLK is 2 (audio words left and right) x 32-bits (size of each audio word) x 16kHz (sample rate) = 1.024MHz. WS is used to select left (low) and right (high) channel. SD is the audio data that is either 16 or 32 bit data word. Below is the I2S SCLK/WS/SD relationships from the Knowles datasheet. The Knowles data is 24-bit left shifted in a 32-bit word format.

STM32G030F6 I2S Setup

The STMicroelectronics development environment, STM32CubeIDE, has a configuration tool to setup the STM32 I/O as well as to set the clock configuration. Once you setup the hardware components this tool generates the support code and data structures.

For this application the Multimedia I2S1 interface to configuration for half-duplex master mode. This has the STM32 send the SCLK and WS signals and can either send or received data (half-duplex).

The I2S parameter setup configurations the transmission mode, communication standard, data and frame format, audio frequency, and the clock polarity. In our application the transmission mode is Mode Master Receive since the processor is the master and the microphone is the data source.

For the communication standard the selections include I2S Philips, MSB First, LSB First, and a couple of PCM framing selections. Originally I used the MSB first standard but discovered that the data was under shifted (MSB was bit 30 not 31 as expected). A look at the STM32 reference manual showed why.

The STMicroelectronics RM0454 Reference Manual (rev 5) shows waveforms for both the I2S Philips standard the MSB justified formats. Both the I2S Philips and the MSB justified framing sets WS on the falling clock edge. In the I2S Philips standard data changes on the next rising SCLK edge and read on the falling edge creating a null 1/2 bit time before the first data bit. With MSB justified format there is no gap. Data changes on the SCLK falling edge along with WS and read on the next rising edge. By using the MSB justified format the STM32 read the first bit before being transmitted by the microphone.

Below is the I2S interface measured with an oscilloscope (channel 1 = SCLK, channel 2 = WS, channel 3 = SD). It is clear that WS changes to the left channel on the falling edge of SCLK and the first data bit from the microphone appears on the next rising edge.

Next is to configure the Data and Frame Format. The SPH0645LM4H-1 data format is 24-bits in a 32-bit frame. Of the 24-bits only the most significant 18-bits are valid and the remaining bits (lowest 6-bits of the 24-bit word) are set to zero by the microphone. The remaining data byte (8-bits) are tri-stated and with a 100K pulldown resistors are read as zero by the STM32. Our choice must include a 32 Bits Frame since that is the output of the microphone, but we can select 16, 24, or 32 data bits.

The 24 and 32-bit choices requires two reads from the receive data register while using 16-bits of data in a 32-bit frame requires only 1 read and the second 16-bits are automatically set to zero. For this test I selected 16-bits in a 32-bit frame.

The code is very simple when using the provide blocking call to receive I2S data. The call is made to HAL_I2S_Receive, which takes the I2S handle pointer created by the auto code generation tools when configuring the processor, a pointer to a data buffer, the number of samples, and a timeout value. Since the I2S interface is designed for a left and right channel, 2 samples are selected.

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	volatile HAL_StatusTypeDef result = HAL_I2S_Receive(&hi2s1, (uint16_t *)&dataIn[0], 2, 100);
	if(result != HAL_OK) {
		printf("Error Code %08lX\n", HAL_I2S_GetError(&hi2s1));
	}
  }
  /* USER CODE END 3 */

Below is a screen shot showing the Live Expressions window for the data buffer. Note that dataIn[0] is our left channel and dataIn[1] is our right channel and is always 0 since we don’t have a another sensor attached.

From the single blocking read, there isn’t much time to processed the received I2S data. With two channels at 16kHz there is only 15.625 microseconds (16kHz x 4 16-bit words = 64kHz) before the next 16-bit data word is ready at the input SPI/I2S register. That is just 1,000 CPU clock cycles at 64MHz maximum clock rate. Just trying to convert the read integer to a string caused data overflow errors (sprintf is a very, very slow function). With an overflow error you miss some real-time data and the data may shift within the data buffer (i.e., left channel moved to dataIn[1]).

A safer approach to processing incoming audio stream data is to use DMA where data is read from the microphone and placed directly into memory without the CPU. The STM32 programming environment provides support for a continuous circular buffer with interrupt callbacks for buffer half full and completely full. The image below shows the DMA data buffer. Note that all right channel data is zero.

For the I2S DMA test I counted each buffer related interrupt and performed a timing analysis to verify continuous data was being read from the MEMS microphone at the realized audio sampling rate (slightly different than the desired audio rate due to processor clock tree).

Overall the STMicroelectronics STM32CubeIDE tool make the developer’s job easier with the hardware configuration interface and auto hardware abstraction layer (HAL) code and data structures generation.

MEMS Accelerometer Free Fall Detection

Micro Electro Mechanical Systems (MEMS) accelerometers have been available since the 1990’s. MEMS creates a microscopic mechanical sensing structures that is coupled with microelectronic circuits to measure physical parameters such as acceleration. One of the first applications of the silicon based MEMS accelerometer was to protect hard drives (HDD) media when being dropped while rotating. When free fall was detected the HDD head would be moved to a safe zone to protect the media.

When a MEMS accelerometer is in free fall all acceleration values move towards zero. The figures below help to understand why this is true (Figures from Physical StackExchange). When in free fall the frame and mass are both affected by the same acceleration so the force acting on the spring nears zero. For a MEMS accelerometer the measured acceleration nears zero.

An inexpensive MEMS accelerometer is the LIS3DH. This unit is a 3-axis accelerometer with scalable ranges (±2g/±4g/±8g/±16g), supports multiple data rates, is low power, has three 10-bit ADCs, and interfaces over I2C or SPI. This accelerometer can also generate interrupts for tap, double-tap, orientation changes, and freefall detection. These accelerometers are available on breakout boards from multiple vendor such as Adafruit and Sparkfun.

To configure the LIS3DH for free fall detection a threshold level in g’s and duration are programmed in device registers. The design tip DT0100 shows an example of setting the level to ~350mg with a duration of 30ms. For the interrupt to occur a level of 350mg or less must be detected on all three axis accelerometers for a minimum of 30ms before the interrupt occurs as shown in the figure below from the ST design note.

For this example I used an Adafruit LIS3DH breakout board. Adafruit does a nice job of providing libraries for their products. The Adafruit LIS3DH library unfortunately does not provide a free fall interrupt method. Normally I would inherit the library object and create my own methods but the library made key communication variables private, so this wasn’t an option without modifying the library. My client’s application did not required any other processing so I created code to replicate the free fall interrupt function in the Arduino loop().

The code uses the Adafruit sensors_event_t type, which returns the accelerometer values in m/s2. To match the design tip the free fall threshold would be set to 3.432 m/s2 (1g = 9.08665 m/s2). With a free fall threshold of 350mg, I found it too easy to enter free fall so I reduced the value to about 50mg, which worked well. When all three axis meet the free fall detection threshold, the free fall duration is checked. If the amount of time exceeds the free fall duration then a LED is turned on (the client turned a servo motor) indicating the unit is in free fall. If the free fall threshold isn’t met, then the duration time is updated to the current time using millis().

Below is the sample code emulating the LIS3DH free fall detection.

// Free Fall Detection
// Adafruit LIS3DH Accelerometer with SPI
// Adapted from Adafruit library acceldemo.ino

#include <SPI.h>
#include <Adafruit_LIS3DH.h>
#include <Adafruit_Sensor.h>

// Hardware Pins
#define LIS3DH_CS 10  // SPI device select
#define LED_PIN   4   // LED output pin to indicate freefall active high

// Operating Parameters
#define FREEFALL_ACCEL_THRESHOLD  0.5 // freefall threshold in m/s^2, 1g = 9.80665 m/s^2
#define FREEFALL_TIME_THRESHOLD   30  // freefall time threshold in ms

// hardware SPI for accelerometer
Adafruit_LIS3DH lis = Adafruit_LIS3DH(LIS3DH_CS); // accel object
sensors_event_t event;                            // sensor data to evaluate for freefall

// timer for free fall
uint32_t freeFallTime;           // timer for consecutive freefall detections

void setup(void) {
  Serial.begin(115200);
  while (!Serial) delay(10);     // will pause Zero, Leonardo, etc until serial console opens

  // setup output control pin for LED
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  
  if (! lis.begin()) {
    Serial.println("Couldnt start");
    while (1) yield();
  }

  lis.setRange(LIS3DH_RANGE_2_G);           // set to 2G range
  lis.setDataRate(LIS3DH_DATARATE_400_HZ);  // sample at fastest rate (2.5ms)

  Serial.println("Free Fall Detection with LIS3DH");

  freeFallTime = millis();
}

void loop() {

  lis.getEvent(&event);   // get latest data

  // detect free fall
  if(abs(event.acceleration.x) <= FREEFALL_ACCEL_THRESHOLD &&
     abs(event.acceleration.y) <= FREEFALL_ACCEL_THRESHOLD &&
     abs(event.acceleration.z) <= FREEFALL_ACCEL_THRESHOLD) {

    if((millis() - freeFallTime) >= FREEFALL_TIME_THRESHOLD) {
      digitalWrite(LED_PIN, HIGH);
    }
  }
  else { // not in freefall reset timer
    freeFallTime = millis();
  }
}

Update

The freefall detection code and hardware was used successfully as part of the Rogers Park Space Program high altitude balloon project when the “HELEN II” paper plane was released at 34,800′ above sea level. You can watch a cool video on YouTube of the entire flight that reached over 112,000′ on 5/28/2023.