Configuring the audio codec

The STM32F400 evaluation board schematic (http://www.keil.com) shows that a Cirrus Logic CS42L52 codec IC (http://www.cirrus.com) is used, and the I2S bus signals are driven by GPIO port I bits 0, 1, and 3. SDIN and SDOUT are wired together, so the I2S interface must be operated half-duplex. In addition to managing the I2S interface, the microcontroller must also source a Master Clock (MCLK), which clocks the codec's delta-sigma modulators (Note that we described a function to achieve this in Chapter 6, Multimedia Support). A block diagram that summarizes the I2S interface connection is shown, as follows:

Configuring the audio codec

The codec also uses MCLK to power an inverter, which supplies a higher DC voltage to support analog parts of the codec. The codec data sheet explains that MCLK should be instantiated and the codec's registers must be configured while the device is powered down and the power up/down sequence outlined in the data sheet must be carefully followed to ensure the codec operates correctly.

The I2S specification identifies master and slave roles. An I2S bus must include one master (to source SCLK and LRCK), and it may include more than one slave. Normally, the microcontroller is configured as master, and as SDIN and SDOUT are connected together (externally), SDOUT must be switched to a high-impedance (HI-Z) state before SDIN is driven. If we refer to the following table the only option that allows for SDOUT to be HI-Z is to configure the codec as slave:

3ST_SP

Serial port status

Slave mode

Master mode

0

This is when serial port clocks are inputs, and SDOUT is output.

This is when serial port clocks and SDOUT are outputs.

1

This is when serial port clocks are inputs, and SDOUT is HI-Z.

This is when serial port clocks and SDOUT are HI-Z.

The microcontroller's Serial Peripheral Interface (SPI) and I2S interface is described in section 28 of STM's RM0090 Reference Manual (http://www.st.com). The following recipe, codecDemo_c7v0, describes how to configure the codec and output a continuous audio tone.

How to do it…

  1. Clone codecDemo_c6v0 from the Writing a driver for the audio codec recipe in Chapter 6, Multimedia Support to a new folder named codecDemo_c7v0.
  2. Configure the Runtime Environment, as we did for the folder, codecDemo_c6v0 from the Writing a driver for the audio codec recipe in Chapter 6, Multimedia Support, and add support for DeviceSTM32Cube HALI2S, as follows:
    How to do it…

    Tip

    There is no need to select CMSIS DriverSPI (API).

  3. Use the Configuration Wizard tabs in RTE_Device.h and RTX_Conf_CM.c to configure I2C and RTX parameters, as we did in the folder, codecDemo_c6v0 from the Writing a driver for the audio codec recipe in Chapter 6, Multimedia Support.
  4. Create a new file named I2S_audio.c and add this to the project:
    How to do it…
  5. Add a global I2S_HandleTypeDef handle structure in the I2S_audio.c file, as follows:
    /* Global I2S handle structure */
    I2S_HandleTypeDef hi2s;
  6. Define the Set_I2S_GPIO_Pins() function in the I2S_audio.c file, as follows:
    void Set_I2S_GPIO_Pins(void) {
      GPIO_InitTypeDef GPIO_InitStruct;
      
      __GPIOC_CLK_ENABLE();
      __GPIOI_CLK_ENABLE();
      
        /* Configure GPIO pin: PI0,1,3 */
      GPIO_InitStruct.Pin   = GPIO_PIN_0 | 
                              GPIO_PIN_1 | GPIO_PIN_3;
      GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
      GPIO_InitStruct.Pull  = GPIO_NOPULL;
      GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
      GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
      HAL_GPIO_Init(GPIOI, &GPIO_InitStruct);
      
      /* Configure GPIO pin: PC6 */
      GPIO_InitStruct.Pin   = GPIO_PIN_6;
      GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
      GPIO_InitStruct.Pull  = GPIO_NOPULL;
      GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
      GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
      HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
    }
  7. Define the I2S_Audio_Initialize() function (skeleton) in the I2S_audio.c file:
    HAL_StatusTypeDef I2S_Audio_Initialize(void) {
      HAL_StatusTypeDef status;
    
      /* Enable the SPIx interface clock. */
    
      /* Configure I2S Pins */
    
      /* Program the Mode, Standard, Data Format, 
         MCLK Output, Audio frequency and Polarity
         using HAL_I2S_Init() function. */
    }
  8. Add this code to enable the clock in the I2S_Audio_Initialize() function:
    /* Enable the SPIx interface clock. */
    RCC->CR |= RCC_CR_PLLI2SON;  /* Enable the PLLI2S */
                    /* Wait till the main PLL is ready */
    while((RCC->CR & RCC_CR_PLLI2SRDY) == 0)
        {}
    __HAL_RCC_SPI2_CLK_ENABLE();
  9. Call Set_I2S_GPIO_Pins(), as follows:
    /* Configure I2S Pins */
    Set_I2S_GPIO_Pins( );
  10. Set the appropriate fields of the global I2S_HandleTypeDef handle structure and call HAL_I2S_Init( ):
    /* Program the Mode, Standard, Data Format, 
       MCLK Output, Audio frequency and Polarity
       using HAL_I2S_Init() function. */
    
      hi2s.Instance = SPI2;  
      hi2s.State = HAL_I2S_STATE_RESET;    
      hi2s.Init.Mode = I2S_MODE_MASTER_TX;
      hi2s.Init.Standard = I2S_STANDARD_MSB;
      hi2s.Init.DataFormat = I2S_DATAFORMAT_16B;
      hi2s.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;
      hi2s.Init.AudioFreq = I2S_AUDIOFREQ_22K;
      hi2s.Init.CPOL = I2S_CPOL_LOW;
      hi2s.Init.ClockSource = I2S_CLOCK_PLL ;
      hi2s.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_DISABLE;
      
      status = HAL_I2S_Init(&hi2s);
  11. Add the #include files to the I2S_audio.c file:
    #include "codec_CS42L52.h"
    #include "stm32f4xx_hal.h"
    #include "I2S_audio.h"
    #include "stm32f4xx_hal_i2s.h"
  12. Open the codec_45L52.c file and add an array of register or value pairs to configure the codec for sampled audio:
    REG_VAL CODEC_Audio_I2S_Slave[] ={
      /****
       *Configure I2S Interface as Slave, 16bits
       ******/
      {CS42L52_IFACE_CTL1, 0x03},  
      /* SDOUT is HI-Z */
      {CS42L52_IFACE_CTL2, 0x10},
      /* Speaker Vol B=A, MONO */
      {CS42L52_PB_CTL2, 0x0A},
      /* Set master vol for A/B */
      {CS42L52_MASTERA_VOL, 0xC0},
      /* Ignore jpr setting (speaker always ON) */
      {CS42L52_PWRCTL3, 0xAA} 
    }; 
  13. Modify the function named configureCodec ( ) so that we can select an appropriate setup, depending on an input argument named mode:
    static void configureCodec(codecMode mode) {
      uint32_t i;
      
      Codec_Write(0x02, 0x01);   /* Keep Codec Power-down */  
      delay_ms(10); /* Wait 10ms */
      
      for (i = 0; i < ARR_SZ(CODEC_Config_Init); i++)
        Codec_Write (CODEC_Config_Init[i].Addr,
                       CODEC_Config_Init[i].Val);
      
      if (mode == AUDIO_BEEP)
        for (i = 0; i < ARR_SZ(CODEC_Config_Beep); i++)
          Codec_Write (CODEC_Config_Beep[i].Addr,
                         CODEC_Config_Beep[i].Val);
      else
        if (mode == AUDIO_SAMPLED)
          for (i = 0; i < ARR_SZ(CODEC_Audio_I2S_Slave); i++)
            Codec_Write (CODEC_Audio_I2S_Slave[i].Addr,
                           CODEC_Audio_I2S_Slave[i].Val);
    
    }
  14. Use mode to manage calls to configureCodec() and genMCLK() in the codecInitialize() function:
    /* Configure CODEC */
       configureCodec(mode);
      
      /* Configure I2S */
      if (mode == AUDIO_SAMPLED)
        status = I2S_Audio_Initialize();
      else
        if (mode == AUDIO_BEEP)
          genMCLK();
  15. Define mode in codec_42L52.h, as follows:
    typedef enum {
      AUDIO_BEEP,
      AUDIO_SAMPLED
    } codecMode;
  16. Open the codecDemo.c file and add the following:
    #include "I2S_audio.h"
    #include "stm32f4xx_hal_i2s.h"
    
    /* Timeout value fixed to 100 ms */
    #define I2S_TX_TIMEOUT_VALUE ((uint32_t)100) 
    /* Macro to calculate array size */
    #define ARR_SZ(x) (sizeof (x) / sizeof(x[0]))
    /* Global External Vars */
    extern I2S_HandleTypeDef hi2s;
  17. Add a global const array of audio samples to the codecDemo.c file:
    /* 20 left+right channel samples @ 22kHz ~= 1.4 kHz. */
    const int16_t dacLUT [ ] = {       
                        0,      0,    9830,    9830,  19660,  
                    19660,  26214,   26214,   31456,  31456,
                    32767,  32767,   31456,   31456,  26214,
                    26214,  19660,   19660,    9830,   9830,
                        0,      0,   -9830,   -9830, -19661, 
                   -19661, -26214,  -26214,  -31457, -31457, 
                   -32768, -32768,  -31457,  -31457, -26214,
                   -26214, -19661,  -19661,   -9830,  -9830  };                         
  18. Modify the main() function in the codecDemo.c file. Add and initialize the variable mode and pass the value to CodecInitialize(), as follows:
    int main (void) {
      noteInfo note = {G5, 0x02};
      codecMode mode = AUDIO_SAMPLED;
      HAL_StatusTypeDef status;
      
      /* Uncomment for BEEP */
      //mode = AUDIO_BEEP;
      
      HAL_Init( );
       SystemClock_Config( );
       GLCD_Initialize();
        status = CodecInitialize(mode);
       setDisplay( );
    
      // etc.
      
       } 
  19. If required, we can add a function named showCodecI2SInfo() that displays the status (to debug):
    #ifdef __DEBUG
      if (mode == AUDIO_SAMPLED) 
        showCodecI2SInfo(status);
    #endif
  20. Modify the super loop in main() and call HAL_I2S_Transmit(), as follows:
    while (1) {
      if (mode == AUDIO_BEEP) {
        Beep(note);                  /* Play the note */  
        wait_delay(500);                     /* pause */
      }
      else
        if (mode == AUDIO_SAMPLED)     /* Play a tone */
          HAL_I2S_Transmit(&hi2s, (uint16_t *) dacLUT, 
                   ARR_SZ(dacLUT), I2S_TX_TIMEOUT_VALUE );
    } /* WHILE */
  21. Uncomment the mode = AUDIO_BEEP; statement. Build and run the program to confirm that I2C communication with the audio codec is established and the program performs as codecDemo_c6v0 from the Writing a driver for the audio codec recipe in Chapter 6, Multimedia Support.
  22. Reinstate the comment. Build, download, and run the code. We should now hear a shrill tone.

How it works…

Before powering the codec up (by clearing bit 0 of the codec's power control 1 register), we must first ensure that MCLK is established. As we're using the stm32f4xx_hal_i2s.h HAL library to manage the I2S low-level interface, we can take advantage of its ability to generate MCLK rather than configuring a timer as we did in Chapter 6, Multimedia Support. The I2S bus and audio codec channels are configured by a function named I2S_Audio_Initialize(), which, in turn, is called by CodecInitialize(). The I2S_Audio_Initialize() function performs the tasks that are identified in the comment at the start of the stm32f4xx_hal_i2s.c file. This enables the I2S clock, configures the GPIO pins, sets GPIO for I2S Alternate Function (AF), sets the I2S handle struct, and initializes the I2S peripheral (using the HAL device driver). Referring to STM's reference manual, RM0090 (http://www.st.com), we can see that the microcontroller has a number of I2C and SPI peripherals, which begs the question, How do we decide which instance of a peripheral to use? The answer is that, as we're using an evaluation board, the board's designer already made this choice when they laid out the PCB. The board schematic (http://www.keil.com) shows that port pins GPIOB 8 and 9 are used by the I2C interface. Table 9 (Alternate Function Mapping) of the STM32F405xx and STM32F407xx Datasheets (DocID022152 Rev 6) shows that Port B Pins 8 and 9 are used by the AF2/3/4/5/9/11/13/15 alternate functions and AF5 connects instance I2C1. Similarly, the codec connections shown on the schematic and the Alternate Function Mapping (Table 9) mean we must use SPI2 as the I2S peripheral.

Information on sourcing the I2S clock can be found by referring to the clock tree in RM0090 Reference Manual (Doc ID 018909 Rev 6), Figure 21. If the I2S Phase Locked Loop (I2SPLL) is not running or an external I2S clock is not sourced, then we must enable the I2SPLL function, I2S_Audio_Initialize():

RCC->CR |= RCC_CR_PLLI2SON;  /* Enable the PLLI2S */
               /* Wait till the main PLL is ready */
while((RCC->CR & RCC_CR_PLLI2SRDY) == 0) { }

As the SPI2 peripheral uses the APB1Periph clock, we also include the following:

__HAL_RCC_SPI2_CLK_ENABLE();

Configuring the GPIO pins and connecting the SPI2 AF is relatively straightforward; for example, we use GPIO_Initialize() as we did in earlier recipes. Note that we also need GPIO C Pin6 to source MCLK.

The final step is to initialize the I2S handle struct (defined in stm32f4xx_hal_i2s.h) with default values. A pointer to this structure is passed to the function named HAL_I2S_Init() that performs the low-level initialization. An important task within HAL_I2S_Init() is for the I2SPLL clock divider to give the desired I2S SCLK frequency.

The function used to initialize the codec named CodecInitialize ( ) is very similar to the one that was presented in codecDemo_c6v0 in the Writing a driver for the audio codec recipe in Chapter 6, Multimedia Support, but we've added some extra statements to allow this function to be used for either BEEP or SAMPLED audio. Similarly, configureCodec() also selects the appropriate setup.

The main() function super loop uses the function, HAL_I2S_Transmit(), to output audio samples representing a sinusoid. We can reuse the Look-up-table (LUT) that was introduced in dacSinusoid_c5v0 from the Generating a sine wave recipe in Chapter 5, Data Conversion to represent the sampled sinusoid. However, as the I2S serial interface supports 16-bit signed samples, we'll need to convert the LUT to this format.

The I2S interface standard supports two (stereo) channels, and although we're operating the codec in mono (that is, channel A=B), we still need to transmit left and right samples, so each sample is repeated in the LUT array.

We've described the audio initialization in some detail and seem to have done a lot of work to produce very little so far, but judging from the number of posts on associated microcontroller internet forums, many novice embedded-system programmers have difficulty with this topic. Many developers use source code published by STM for their evaluation boards as a starting point, but they all tend to use different codec/microcontroller combinations, so reusing the code isn't always straightforward.

There's more…

Having generated a 'note', the question, what frequency?, arises. The I2S standard (Phillips Semiconductors, 1986) can help us answer this. The timing diagram depicted as follows illustrates an I2S data transmission:

There's more…

As the sinusoid is described by 20 samples and a sample frequency of 22 kHz (Fs), the period will be 20 × 10^(-3)⁄960.909 ms, that is, a frequency of approximately 1.2 kHz. We can confirm this by connecting an oscilloscope to the audio jack.

There's more…

Currently, the main super loop only comprises one function call. We must be mindful that adding further statements in the loop may result in the I2S transmit register being starved.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.170.63