Writing a driver for the audio codec

The audio codec is a peripheral that enables an analog signal to be converted and coded to a digital data stream or conversely the data stream to be decoded and converted back to an analog signal (https://en.wikipedia.org/wiki/Codec). The MCBSTM32F400 evaluation board uses a CS42L52 device that is manufactured by Cirrus Logic (http://www.cirrus.com/en/products/). As, this codec is not yet included in Board Support, and as no CMSIS-compliant device driver is available, we are faced with the task of having to write our own driver.

However, this is not as daunting as it first appears because the code to set up and manage data transfer across the I2C bus can be lifted from the previous recipe (the Touch_STMPE811.c file) and the configuration of the CS42L52 codec is described in the data sheet. The recipe to develop and test this codec driver is called codecDemo_c6v0.

How to do it…

Perform the following steps to write a driver for the audio codec:

  1. Create a new project called codecDemo, and using the Run-Time Environment manager, include Board Support for the Graphic LCD. Remember to configure Software Support for CMSIS and Device as in earlier projects.
  2. Create a new file named codecDemo.c. Add the boilerplate to configure clocks, and so on, and a skeleton main() function:
    int main (void) {
      
      HAL_Init ();   /* Init Hardware Abstraction Layer */
      SystemClock_Config ();           /* Config Clocks */
    }
  3. Add the #include files for the codecDemo.c file:
    #include "stm32f4xx_hal.h"
    #include "cmsis_os.h"
    #include "codec_CS42L52.h"
    #include "GLCD_Config.h"
    #include "Board_GLCD.h"
    #include <stdio.h>
  4. Create a new file called timer.c and add this to the source code group. Add a function named TIM3_Initialize() to this file:
    void TIM3_Initialize (void) {
      const uint16_t ARR_val = 7;
    
      /* enable clock for TIM3 */
      RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; 
    
      TIM3->CCMR1 = 0x00000070;       /* Set PWM Mode 2 */
      TIM3->ARR = ARR_val - 1;       /* set auto-reload */
      TIM3->CCR1 = 3;              /* Duty cycle (~50%) */
      /* Enable capture/compare on Chan 1    */
      TIM3->CCER = 0x000B00001;
      TIM3->CR1 = 0x000B00001;         /* Enable counter */    
    }
  5. Create a new file called codec_CS42L52.c and add this to the source code group. Copy the first 75 lines of the Touch_STMPE811.c file to codec_CS42L52.c, the first part of the file, including the Touch_Read() and Touch_Write() functions.
  6. Change the #include directives in the codec_CS42L52.c file to the following:
    #include "CS42L52.h"
    #include "codec_CS42L52.h"
    #include "stm32f4xx_hal.h"
    #include "Driver_I2C.h"
    #include "timer.h"
  7. Replace any references to TSC_I2C_ADDR with CODEC_I2C_ADDR.
  8. Replace any references to TSC_I2C_PORT with CODEC_I2C_PORT.
  9. Replace TSC_I2C_ADDR with that given in the CS42L52 data sheet, as follows:
    /* 7-bit I2C Address = 1001010b */
    #define CODEC_I2C_ADDR    0x4A 
  10. Rename the Touch_Read() and Touch_Write() functions to Codec_Read() and Codec_Write(), respectively.
  11. Add a global typedef to the codec_CS42L52.c file:
    /* Global TypeDef - Register value */
    typedef struct {
      uint8_t Addr;
      uint8_t Val;
    } REG_VAL;
  12. Add a function named configureCodec() to the codec_CS42L52.c file. The first two statements of configureCodec power the device down and wait for 10 ms. Note #define delay_ms HAL_Delay:
    void configureCodec ( ) {
      Codec_Write(0x02, 0x01);    /* Keep Codec Power-down */  
      delay_ms(10); 
     
      for (i = 0; i < ARR_SZ(CODEC_Config_Init); i++)
        Codec_Write (CODEC_Config_Init[i].Addr,
          CODEC_Config_Init[i].Val);
    
      for (i = 0; i < ARR_SZ(CODEC_Config_Beep); i++)
        Codec_Write (CODEC_Config_Beep[i].Addr,
          CODEC_Config_Beep[i].Val);
    } /* configureCodec */
  13. Include this macro definition to calculate the size of a (const) array, as follows:
    /* Calculate array size */
    #define ARR_SZ(x) (sizeof (x) / sizeof(x[0]))
  14. Define a global array of codec register address/value pairs named CODEC_Config_Init:
    /***
    * CODEC initialization based on p38
    * of CS42L52 data sheet DS680F2
    *****/
    REG_VAL CODEC_Config_Init[] = {     
      {0x00, 0x99},
      {0x3E, 0xBA},
      {0x47, 0x80},
      {0x32, 0x80},
      {0x32, 0x00},
      {0x00, 0x00},
    };
  15. Define a global array of codec register address/value pairs named CODEC_Config_Beep:
    /***
    * CODEC initialization for Beep Generator
    * of CS42L52 (Grant Ashton)
    *****/
    REG_VAL CODEC_Config_Beep[] ={
      /* Set I2S Ser. Mstr Op Only, for Beep Gen */
      {CS42L52_IFACE_CTL1, 0x80},
      /* Speaker Vol B=A, MONO */
      {CS42L52_PB_CTL2, 0x0A},
      /* Set master vol for A */
      {CS42L52_MASTERA_VOL, 0xC0},
      /* Ignore jpr setting */
      {CS42L52_PWRCTL3, 0xAA}
    };
  16. Create a new file named CS42L52.h defining symbolic names (for example, CS42L52_IFACE_CTL1, CS42L52_PB_CTL2, CS42L52_MASTERA_VOL, and so on) for CS42L52 register addresses. For example, as in the following addresses:
    /* Register addresses */
    #define CS42L52_CHIP_ID    0x01
    #define CS42L52_PWRCTL1    0x02
    #define CS42L52_PWRCTL2    0x03
    #define CS42L52_PWRCTL3    0x04
    #define CS42L52_CLK_CTL    0x05
    // etc.
  17. Add a function named genMCLK() to the codec_CS42L52.c file:
    static void genMCLK(void) {
      GPIO_InitTypeDef GPIO_InitStruct;
      
      TIM3_Initialize();
      __GPIOC_CLK_ENABLE();
    
      /* Configure GPIO pin: PC6 */
      GPIO_InitStruct.Pin   = GPIO_PIN_6;
      GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
      GPIO_InitStruct.Pull  = GPIO_PULLUP;
      GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
      GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
      HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
    }
  18. Add a function named codecInitialize() to the codec_CS42L52.c file. Note that the code to configure the I2C bus is identical to the code in Touch_Initialize():
    int32_t codecInitialize() {
      int32_t status;
      /* Configure I2C */
      ptrI2C->Initialize (NULL);
      ptrI2C->PowerControl (ARM_POWER_FULL);
      ptrI2C->Control (ARM_I2C_BUS_SPEED,
                               ARM_I2C_BUS_SPEED_FAST);
      ptrI2C->Control (ARM_I2C_BUS_SPEED,
    
      /* Configure CODEC */
      configureCodec();
      genMCLK();
    
      /* CODEC Power up    */
      status = Codec_Write(CS42L52_PWRCTL1, 0x00); 
      delay_ms(10); /* Wait 10ms */
      
      return status;
    }
  19. Add a function named readCodecChipID() to the codec_CS42L52.c file:
    int32_t readCodecChipID(uint8_t *val) {
      int32_t status = Codec_Read(1, val);
      
      return status;
    }
  20. Add a function named Beep() to the codec_CS42L52.c file:
    void Beep(noteInfo note ) {
      /* Beep off time 1.23s and volume 0dB */
      Codec_Write(CS42L52_BEEP_VOL, 0x00);
      /* Set beep note and    beep duration */ 
      Codec_Write(CS42L52_BEEP_FREQ, 
                    note.pitch | note.duration);
      /* play single beep */    
      Codec_Write(CS42L52_BEEP_TONE_CTL, 0x40);
      /* Disable beep */
      Codec_Write(CS42L52_BEEP_TONE_CTL, 0x00);
    }
  21. Create the timer.h header file containing the timer.c function prototypes.
  22. Create the codec_CS42L52.h header file containing the codec_CS42L52.c function prototypes.
  23. Define symbolic names for the pitch of notes in the codec_CS42L52.h file, for example, as in the following frequencies:
    // Beep note frequency
    #define A5 0x60
    #define A6 0xD0
    #define B5 0x70
    #define B6 0xE0
    // etc.
  24. Define symbolic names for the duration of notes in the codec_CS42L52.h file, for example, as in the following:
    #define TENTH_SECOND     0x00
    #define HALF_SECOND     0x01
    #define ONE_SECOND     0x02
    // etc.
  25. Extend the main() function by adding code to initialize the GLCD and Codec. Define a super-loop that outputs a beep every 0.5 seconds:
    int main (void) {
      noteInfo note = {G5, 0x02};
    
      uint8_t codecID;
      char buffer[128];
      
      HAL_Init ();   /* Init Hardware Abstraction Layer */
      SystemClock_Config ();           /* Config Clocks */
    
      GLCD_Initialize();
      setDisplay();
      
      showStatus(CodecInitialize()); 
      showStatus(readCodecChipID(&codecID));
      sprintf(buffer, "Chip ID: 0x%x", codecID);
      GLCD_DrawString (1*16, 9*24, buffer);
    
      while (1) {    
        Beep(note);                       /* Play the note */  
        
        wait_delay(500);                       /* pause */
      } /* WHILE */
    }
  26. Add a function named setDisplay() (copy the first 12 lines of the similarly-named function used in touchScreenDemo_c6v0).
  27. Add a function named showStatus( ):
    void showStatus(int32_t stat) {
      if (stat==0) GLCD_DrawString (1*16, 8*24,"Codec OK  ");
      else GLCD_DrawString (1*16, 8*24,"Codec FAIL");
    }
  28. Check that the codec_CS42L52.c and timer.c files are added to the project.
  29. Select the Use MicroLIB project option.
  30. Remember to configure the RTE_Device.h and RTX_Conf_CM.c files, as we did for the touchScreenDemo_c6v0 folder from the Setting the RTE for the I2C Peripheral Bus recipe.
  31. Build the project, then download and run the program.

How it works…

A Linux driver for the CS42L52 device has been written by Cirrus Logic (http://lxr.free-electrons.com/source/sound/soc/codecs/cs42l52.c) and is freely distributed under the terms of the GNU General Public License. So, we can use this together with information from the datasheet (http://www.cirrus.com) as a basis for our driver for the MCBSTM32F400 evaluation board. As the audio codec is also connected to the I2C serial bus, the touchscreen driver that we met in the previous section provides a good template for our audio codec driver. Therefore, we will organize the codec driver in three files that mirror those of the touchscreen driver, as follows:

  • CS42L52.h: This defines codec registers
  • Codec_CS42L52.c: This declares functions
  • Codec_CS42L52.h: This declares function prototypes and defines symbolic names for constants

The code in the Codec_CS42L52.c file first defines the I2C port that is used to communicate with the audio codec. The board schematic confirms that the touchscreen and the audio codec are connected to the same I2C port (that is, serial clock SCL = PB8 and SDA = PB9), so we configure the RTE and RTX exactly as touchScreenDemo_c6v0 using I2C port 1 (I2C1). The following preprocessor directives define the port number:

#ifndef CODEC_I2C_PORT
#define CODEC_I2C_PORT    1  /* I2C Port number*/
#endif

The following preprocessor macro ensures that the ptrI2C identifier points to the appropriate I2C driver:

/* I2C Driver */
#define _I2C_Driver_(n)  Driver_I2C##n
#define  I2C_Driver_(n) _I2C_Driver_(n)
extern ARM_DRIVER_I2C    I2C_Driver_(CODEC_I2C_PORT);
#define ptrI2C         (&I2C_Driver_(CODEC_I2C_PORT))

The most-significant 6-bit audio codec's I2C address is shown on the board schematic and the CS42L52 datasheet as 1001012. Bit-0 reflects the logic level of the AD0 pin (that is, 0 V), and the LSB is 0 (for write operations). So, our codec's I2C address is 0x94, that is, the following:

#define CODEC_I2C_ADDR    0x4A /* I2C address */

Note that in practice, all accesses to the codec are writes because the read protocol uses an abortive write cycle first to select the codec register before reading its contents (refer to http://www.cirrus.com for further details).

We declare two functions: Codec_Write( ) and Codec_Read( ), which mirror Touch_Write( ) and Touch_Read( ), which were declared in Touch.c to read and write to the audio codec.

The function named CodecInitialize() performs three tasks. It configures the I2C interface, then it generates the 12 MHz master clock MCLK (codec Pin 37), and finally, it performs the codec's initialization sequence.

The function named genMCLK() configures TIM3 to generate a 12-MHz clock and maps this onto the Alternate Function (AF) GPIO Port C pin 6 output. The initialization for TIM3 is similar to that described in the previous chapter except that we use the PWM mode with the capture/compare register to give an approximate 50% duty cycle. The code to configure the GPIO pin that is used to source MCLK is similar to the one that we saw in the LED_Initialize() function.

The initialization sequence for the audio codec is given on page 38 of the CS42L52 data sheet. The initialization sequence is stored in an array named CODEC_RegInit[]. The array entries are structured as follows:

/* Register value */
typedefstruct {
  uint8_tAddr;
  uint8_t Val;
} REG_VAL;

The register names (for example, MASTERA_VOL, and so on) are defined in the CS42L52.h header file (note that the register names can be copied from the Linux CS42L52 driver). To prevent odd pops and crackles, the data sheet advises that the chip is powered down before initialization and then powered up. This configuration code is included in the configureCodec() function. This function includes a nice example of a macro named ARR_SZ to compute the size of the array:

/* Calculate array size */
#define ARR_SZ(x) (sizeof (x) / sizeof(x[0]))

Note that unlike some languages, such as Java, C doesn't perform any array bounds checking, so it can be quite difficult to track errors due to incorrect array access; because of this, this macro is particularly useful.

In this recipe, we're only using the codec's beep generator (section 4.3 of the data sheet), and the values stored in the CODEC_Config_Beep[] array are concerned with setting the codec up for this task. The remaining functions declared in the codec_CS42L52.c file are concerned with generating beeps and adjusting the volume of the speaker. The beep generator can be configured to produce single, multiple, or continuous beeps, but we only need single beeps to play our tune. The Beep( ) function generates a single beep. This function takes an input parameter that determines the pitch and duration of the beep, and this is combined into one byte and written to the codec register address offset 0x1C in the format shown in the following table:

Bit-7

Bit-6

Bit-5

Bit-4

Bit-3

Bit-2

Bit-1

Bit-0

FREQ3

FREQ2

FREQ1

FREQ0

ONTIME3

ONTIME2

ONTIME1

ONTIME0

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

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