STM32CubeIDE Enviornment printf Support

Sometimes when working with an embedded system sending information to a serial port is useful. It can be used to provide system telemetry data, user feedback, or to aid with debugging a program. With most embedded development environments there is support for the old C standard printf(). Generally to use printf() a user needs to provide the putchar() routine that directs character output to your serial port. The STM32CubeIDE is no different and has printf() support. DigiKey provides a nice tutorial on the subject including enabling the floating point support.

There are only two steps needed to provide basic printf() support. First create a UART project instance in STM32CubeIDE. Use the Connectivity Category and select the UART you want to use. This assigns the processor GPIO pins for the TX and RX signals and creates a UART instance and handle. Under Parameter Settings set the desired basic settings such as baud rate, word length, parity, and stop bits.

The next step is to redirect printf() output to use the UART instance. First include the standard IO (stdio.h) header file. Based on your complier, the second part creates the interface to output a single character to the UART instance using the STM32 hardware abstraction layer (HAL) function. With the STM32 environment, you need the UART handle from the created UART instance (e.g., hlpuart1 in the sample code). The necessary code segment is shown below and is placed in the Private Function Prototypes (PFP) code area.

One issue with using printf() is that the CPU is involved with moving each formatted character to the UART. The printf() function actually performs two activities. First a character string is created based on the formatting string and arguments in the printf() function call. The second activity is moving that character string to the UART one byte at a time.

One way to improve the printf() CPU I/O efficiency is create the output formatted character string but use DMA to move the character string bytes to the UART. Using a DMA channel eliminates the need for the CPU to move the individual character string bytes to the UART.

In the STM32 IDE when configuring the UART you can assign a DMA channel to the UART transmitter. For an out going message the DMA transfer is setup as memory to peripheral (transmit) with a byte data width.

When you are ready to send a message first use sprintf() to create the formatted character string in a memory buffer. Once the string has been created start the transmit DMA using the HAL DMA api call (HAL_UART_Transmit_DMA). A small code segment is shown below using sprintf() and the HAL DMA api.

The STM32 IDE programming environment and HAL api greatly simplifies using a DMA channel to transmit formatted character strings without involving the CPU. Hopefully this example gets you started using DMA for your data transfers.

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.