Working with STM32 and I2S Part 4: CS43L22 Initialization and sound generation

In the previous guide (here), we were able to transmit data over I2S using DMA, in this guide, we shall initialize the CS43L22 which is stereo DAC.

In this guide, we shall cover the following:

  • Enabling circular mode for DMA.
  • Header file development.
  • Source file development.
  • Main code.
  • Source code download.
  • Results.

18. Enabling Circular Mode for DMA:

In I2S source file, the I2S3_Init function:

In the DMA configuration add the following to the configuration:

DMA_SxCR_CIRC

Hence the configuration as following:

	/*Configure DMA with the following parameters
	 * 1.Channel is 0.
	 * Memory increment mode.
	 * Direction Memory to Peripheral.
	 * Enable Transfer Complete Interrupt.
	 * Enable Circular Mode
	 *
	 * */
	DMA1_Stream7->CR|=(0x00<<DMA_SxCR_CHSEL_Pos)|DMA_SxCR_MINC|DMA_SxCR_DIR_0|DMA_SxCR_TCIE|DMA_SxCR_CIRC;

That all for the DMA section.

19. Header File Development:

Create new header file with name of CS43L22.h

Within the header file:

Include the header guard as following:

#ifndef CS43L22_H_
#define CS43L22_H_


#endif /* CS43L22_H_ */

Within the header file:

Include stdint library as following:

#include "stdint.h"

Declare the following define statements for the CS43L22 registers:

#define POWER_CONTROL1				0x02
#define POWER_CONTROL2				0x04
#define CLOCKING_CONTROL 	  		0x05
#define INTERFACE_CONTROL1			0x06
#define INTERFACE_CONTROL2			0x07
#define PASSTHROUGH_A				0x08
#define PASSTHROUGH_B				0x09
#define MISCELLANEOUS_CONTRLS		0x0E
#define PLAYBACK_CONTROL			0x0F
#define PASSTHROUGH_VOLUME_A		0x14
#define PASSTHROUGH_VOLUME_B		0x15
#define PCM_VOLUME_A				0x1A
#define PCM_VOLUME_B				0x1B
#define CONFIG_00					0x00
#define CONFIG_47					0x47
#define CONFIG_32					0x32

Registers of the volume control:

#define CS43L22_REG_MASTER_A_VOL    0x20
#define CS43L22_REG_MASTER_B_VOL    0x21

Device address:

#define DAC_I2C_ADDR 				(0x94>>1)

Note: The address shifted to right by 1 since the i2c driver will shift the address to left 1.

Macro for mute:

#define CS43_MUTE				 	0x00

Channel selection:

#define CS43_RIGHT					0x01
#define CS43_LEFT				 	0x02
#define CS43_RIGHT_LEFT	 			0x03

Convert volume:

#define VOLUME_CONVERT_A(Volume)    (((Volume) > 100)? 255:((uint8_t)(((Volume) * 255) / 100)))
#define VOLUME_CONVERT_D(Volume)    (((Volume) > 100)? 24:((uint8_t)((((Volume) * 48) / 100) - 24)))

Type of inputs either I2S or analog:

typedef enum
{
	MODE_I2S = 0,
	MODE_ANALOG,
}CS43_MODE;

Declare the following functions:

void CS43_Init(CS43_MODE outputMode);
void CS43_Enable_RightLeft(uint8_t side);
void CS43_SetVolume(uint8_t volume);
void CS43_Start(void);
void CS43_Stop(void);
void CS43L22_Pin_RST_Init(void);
void CS43L22_RST_Pin_Low(void);
void CS43L22_RST_Pin_High(void);

Hence the entire header file as following:

#ifndef CS43L22_H_
#define CS43L22_H_

#include "stdint.h"

#define POWER_CONTROL1				0x02
#define POWER_CONTROL2				0x04
#define CLOCKING_CONTROL 	  		0x05
#define INTERFACE_CONTROL1			0x06
#define INTERFACE_CONTROL2			0x07
#define PASSTHROUGH_A				0x08
#define PASSTHROUGH_B				0x09
#define MISCELLANEOUS_CONTRLS		0x0E
#define PLAYBACK_CONTROL			0x0F
#define PASSTHROUGH_VOLUME_A		0x14
#define PASSTHROUGH_VOLUME_B		0x15
#define PCM_VOLUME_A				0x1A
#define PCM_VOLUME_B				0x1B
#define CONFIG_00					0x00
#define CONFIG_47					0x47
#define CONFIG_32					0x32

#define CS43L22_REG_MASTER_A_VOL    0x20
#define CS43L22_REG_MASTER_B_VOL    0x21

#define DAC_I2C_ADDR 				(0x94>>1)

#define CS43_MUTE				 	0x00

#define CS43_RIGHT					0x01
#define CS43_LEFT				 	0x02
#define CS43_RIGHT_LEFT	 			0x03

#define VOLUME_CONVERT_A(Volume)    (((Volume) > 100)? 255:((uint8_t)(((Volume) * 255) / 100)))
#define VOLUME_CONVERT_D(Volume)    (((Volume) > 100)? 24:((uint8_t)((((Volume) * 48) / 100) - 24)))


typedef enum
{
	MODE_I2S = 0,
	MODE_ANALOG,
}CS43_MODE;




//------------ Public functions ------------//
void CS43_Init(CS43_MODE outputMode);
void CS43_Enable_RightLeft(uint8_t side);
void CS43_SetVolume(uint8_t volume);
void CS43_Start(void);
void CS43_Stop(void);
void CS43L22_Pin_RST_Init(void);
void CS43L22_RST_Pin_Low(void);
void CS43L22_RST_Pin_High(void);

#endif /* CS43L22_H_ */

20. Source File Development:

Create new source file with name of CS43L22.c.

Within the source file:

Include the i2c and and CS43L22 header files:

#include "i2c.h"
#include "CS43L22.h"

Declare an array that hold some variables:

static uint8_t iData[2];

Since we need to read/write from/to chip, we need to declare the following two functions:

// Function(1): Write to register
static void write_register(uint8_t reg, uint8_t *data)
{
	iData[0] = reg;
	iData[1] = data[0];
	i2c_writeByte(DAC_I2C_ADDR, (char)iData[0], (char)iData[1]);

}
// Function(2): Read from register
static void read_register(uint8_t reg, uint8_t *data)
{
	char tempData=0;
	i2c_readByte(DAC_I2C_ADDR,(char)reg, &tempData);
	*data=tempData;
}

First one is to write to certain register and the second one is for reading from a register.

Since the chip features hardware reset which is connected to PD4 as shown in the schematic:

For the pin initialization:

void CS43L22_Pin_RST_Init(void)
{
	RCC->AHB1ENR|=RCC_AHB1ENR_GPIODEN;

	GPIOD->MODER|=GPIO_MODER_MODE4_0;
	GPIOD->MODER&=~GPIO_MODER_MODE4_1;
}

For setting the pin either high or low:

void CS43L22_RST_Pin_Low(void)
{
	GPIOD->BSRR=GPIO_BSRR_BR4;
}

void CS43L22_RST_Pin_High(void)
{
	GPIOD->BSRR=GPIO_BSRR_BS4;
}

Setting the pin low will reset the chip and high will release the chip from reset state.

For the initialization of CS42L22:

void CS43_Init(CS43_MODE outputMode)
{

	CS43L22_RST_Pin_High();

	//(2): Power down
	iData[1] = 0x01;
	write_register(POWER_CONTROL1,iData);

	//(3): Enable Right and Left headphones
	iData[1] =  (2 << 6);  // PDN_HPB[0:1]  = 10 (HP-B always onCon)
	iData[1] |= (2 << 4);  // PDN_HPA[0:1]  = 10 (HP-A always on)
	iData[1] |= (3 << 2);  // PDN_SPKB[0:1] = 11 (Speaker B always off)
	iData[1] |= (3 << 0);  // PDN_SPKA[0:1] = 11 (Speaker A always off)
	write_register(POWER_CONTROL2,&iData[1]);

	//(4): Automatic clock detection
	iData[1] = (1 << 7);
	write_register(CLOCKING_CONTROL,&iData[1]);

	//(5): Interface control 1
	read_register(INTERFACE_CONTROL1, iData);
	iData[1] &= (1 << 5); // Clear all bits except bit 5 which is reserved
	iData[1] &= ~(1 << 7);  // Slave
	iData[1] &= ~(1 << 6);  // Clock polarity: Not inverted
	iData[1] &= ~(1 << 4);  // No DSP mode
	iData[1] &= ~(1 << 2);  // Left justified, up to 24 bit (default)
	iData[1] |= (1 << 2);
	iData[1] |=  (3 << 0);  // 16-bit audio word length for I2S interface
	write_register(INTERFACE_CONTROL1,&iData[1]);


	//(6): Passthrough A settings
	read_register(PASSTHROUGH_A, &iData[1]);
	iData[1] &= 0xF0;      // Bits [4-7] are reserved
	iData[1] |=  (1 << 0); // Use AIN1A as source for passthrough
	write_register(PASSTHROUGH_A,&iData[1]);


	//(7): Passthrough B settings
	read_register(PASSTHROUGH_B, &iData[1]);
	iData[1] &= 0xF0;      // Bits [4-7] are reserved
	iData[1] |=  (1 << 0); // Use AIN1B as source for passthrough
	write_register(PASSTHROUGH_B,&iData[1]);


	//(8): Miscellaneous register settings
	read_register(MISCELLANEOUS_CONTRLS, &iData[1]);
	if(outputMode == MODE_ANALOG)
	{
		iData[1] |=  (1 << 7);   // Enable passthrough for AIN-A
		iData[1] |=  (1 << 6);   // Enable passthrough for AIN-B
		iData[1] &= ~(1 << 5);   // Unmute passthrough on AIN-A
		iData[1] &= ~(1 << 4);   // Unmute passthrough on AIN-B
		iData[1] &= ~(1 << 3);   // Changed settings take affect immediately
	}
	else if(outputMode == MODE_I2S)
	{
		iData[1] = 0x02;
	}
	write_register(MISCELLANEOUS_CONTRLS,&iData[1]);
	//(9): Unmute headphone and speaker

	read_register(PLAYBACK_CONTROL, &iData[1]);
	iData[1] = 0x00;
	write_register(PLAYBACK_CONTROL,&iData[1]);

	//(10): Set volume to default (0dB)
	iData[1] = 0x00;
	write_register(PASSTHROUGH_VOLUME_A,&iData[1]);
	write_register(PASSTHROUGH_VOLUME_B,&iData[1]);
	write_register(PCM_VOLUME_A,&iData[1]);
	write_register(PCM_VOLUME_B,&iData[1]);
}

For selecting left, right or both channels:

// Function(2): Enable Right and Left headphones
void CS43_Enable_RightLeft(uint8_t side)
{
	switch (side)
	{
		case 0:
			iData[1] =  (3 << 6);  // PDN_HPB[0:1]  = 10 (HP-B always onCon)
			iData[1] |= (3 << 4);  // PDN_HPA[0:1]  = 10 (HP-A always on)
			break;
		case 1:
			iData[1] =  (2 << 6);  // PDN_HPB[0:1]  = 10 (HP-B always onCon)
			iData[1] |= (3 << 4);  // PDN_HPA[0:1]  = 10 (HP-A always on)
			break;
		case 2:
			iData[1] =  (3 << 6);  // PDN_HPB[0:1]  = 10 (HP-B always onCon)
			iData[1] |= (2 << 4);  // PDN_HPA[0:1]  = 10 (HP-A always on)
			break;
		case 3:
			iData[1] =  (2 << 6);  // PDN_HPB[0:1]  = 10 (HP-B always onCon)
			iData[1] |= (2 << 4);  // PDN_HPA[0:1]  = 10 (HP-A always on)
			break;
		default:
			break;
	}
	iData[1] |= (3 << 2);  // PDN_SPKB[0:1] = 11 (Speaker B always off)
	iData[1] |= (3 << 0);  // PDN_SPKA[0:1] = 11 (Speaker A always off)
	write_register(POWER_CONTROL2,&iData[1]);
}


For Setting the volume:

// Function(3): Set Volume Level
void CS43_SetVolume(uint8_t volume)
{
	int8_t tempVol = volume - 50;
	tempVol = tempVol*(127/50);
	uint8_t myVolume =  (uint8_t )tempVol;
	iData[1] = myVolume;
	write_register(PASSTHROUGH_VOLUME_A,&iData[1]);
	write_register(PASSTHROUGH_VOLUME_B,&iData[1]);

	iData[1] = VOLUME_CONVERT_D(volume);

	/* Set the Master volume */
	write_register(CS43L22_REG_MASTER_A_VOL,&iData[1]);
	write_register(CS43L22_REG_MASTER_B_VOL,&iData[1]);
}

Start the DAC functionality:

// Function(4): Start the Audio DAC
void CS43_Start(void)
{
	// Write 0x99 to register 0x00.
	iData[1] = 0x99;
	write_register(CONFIG_00,&iData[1]);
	// Write 0x80 to register 0x47.
	iData[1] = 0x80;
	write_register(CONFIG_47,&iData[1]);
	// Write '1'b to bit 7 in register 0x32.
	read_register(CONFIG_32, &iData[1]);
	iData[1] |= 0x80;
	write_register(CONFIG_32,&iData[1]);
	// Write '0'b to bit 7 in register 0x32.
	read_register(CONFIG_32, &iData[1]);
	iData[1] &= ~(0x80);
	write_register(CONFIG_32,&iData[1]);
	// Write 0x00 to register 0x00.
	iData[1] = 0x00;
	write_register(CONFIG_00,&iData[1]);
	//Set the "Power Ctl 1" register (0x02) to 0x9E
	iData[1] = 0x9E;
	write_register(POWER_CONTROL1,&iData[1]);
}

For stoping the DAC:

void CS43_Stop(void)
{
	iData[1] = 0x01;
	write_register(POWER_CONTROL1,&iData[1]);
}

Hence, the source code as following:

#include "i2c.h"
#include "CS43L22.h"


static uint8_t iData[2];



//(1): Functions definitions
//-------------- Static Functions ---------------//
// Function(1): Write to register
static void write_register(uint8_t reg, uint8_t *data)
{
	iData[0] = reg;
	iData[1] = data[0];
	i2c_writeByte(DAC_I2C_ADDR, (char)iData[0], (char)iData[1]);

}
// Function(2): Read from register
static void read_register(uint8_t reg, uint8_t *data)
{
	char tempData=0;
	i2c_readByte(DAC_I2C_ADDR,(char)reg, &tempData);
	*data=tempData;
}


void CS43L22_Pin_RST_Init(void)
{
	RCC->AHB1ENR|=RCC_AHB1ENR_GPIODEN;

	GPIOD->MODER|=GPIO_MODER_MODE4_0;
	GPIOD->MODER&=~GPIO_MODER_MODE4_1;
}

void CS43L22_RST_Pin_Low(void)
{
	GPIOD->BSRR=GPIO_BSRR_BR4;
}

void CS43L22_RST_Pin_High(void)
{
	GPIOD->BSRR=GPIO_BSRR_BS4;
}


//-------------- Public Functions ----------------//
// Function(1): Initialisation

void CS43_Init(CS43_MODE outputMode)
{

	CS43L22_RST_Pin_High();

	//(2): Power down
	iData[1] = 0x01;
	write_register(POWER_CONTROL1,iData);

	//(3): Enable Right and Left headphones
	iData[1] =  (2 << 6);  // PDN_HPB[0:1]  = 10 (HP-B always onCon)
	iData[1] |= (2 << 4);  // PDN_HPA[0:1]  = 10 (HP-A always on)
	iData[1] |= (3 << 2);  // PDN_SPKB[0:1] = 11 (Speaker B always off)
	iData[1] |= (3 << 0);  // PDN_SPKA[0:1] = 11 (Speaker A always off)
	write_register(POWER_CONTROL2,&iData[1]);

	//(4): Automatic clock detection
	iData[1] = (1 << 7);
	write_register(CLOCKING_CONTROL,&iData[1]);

	//(5): Interface control 1
	read_register(INTERFACE_CONTROL1, iData);
	iData[1] &= (1 << 5); // Clear all bits except bit 5 which is reserved
	iData[1] &= ~(1 << 7);  // Slave
	iData[1] &= ~(1 << 6);  // Clock polarity: Not inverted
	iData[1] &= ~(1 << 4);  // No DSP mode
	iData[1] &= ~(1 << 2);  // Left justified, up to 24 bit (default)
	iData[1] |= (1 << 2);
	iData[1] |=  (3 << 0);  // 16-bit audio word length for I2S interface
	write_register(INTERFACE_CONTROL1,&iData[1]);


	//(6): Passthrough A settings
	read_register(PASSTHROUGH_A, &iData[1]);
	iData[1] &= 0xF0;      // Bits [4-7] are reserved
	iData[1] |=  (1 << 0); // Use AIN1A as source for passthrough
	write_register(PASSTHROUGH_A,&iData[1]);


	//(7): Passthrough B settings
	read_register(PASSTHROUGH_B, &iData[1]);
	iData[1] &= 0xF0;      // Bits [4-7] are reserved
	iData[1] |=  (1 << 0); // Use AIN1B as source for passthrough
	write_register(PASSTHROUGH_B,&iData[1]);


	//(8): Miscellaneous register settings
	read_register(MISCELLANEOUS_CONTRLS, &iData[1]);
	if(outputMode == MODE_ANALOG)
	{
		iData[1] |=  (1 << 7);   // Enable passthrough for AIN-A
		iData[1] |=  (1 << 6);   // Enable passthrough for AIN-B
		iData[1] &= ~(1 << 5);   // Unmute passthrough on AIN-A
		iData[1] &= ~(1 << 4);   // Unmute passthrough on AIN-B
		iData[1] &= ~(1 << 3);   // Changed settings take affect immediately
	}
	else if(outputMode == MODE_I2S)
	{
		iData[1] = 0x02;
	}
	write_register(MISCELLANEOUS_CONTRLS,&iData[1]);
	//(9): Unmute headphone and speaker

	read_register(PLAYBACK_CONTROL, &iData[1]);
	iData[1] = 0x00;
	write_register(PLAYBACK_CONTROL,&iData[1]);

	//(10): Set volume to default (0dB)
	iData[1] = 0x00;
	write_register(PASSTHROUGH_VOLUME_A,&iData[1]);
	write_register(PASSTHROUGH_VOLUME_B,&iData[1]);
	write_register(PCM_VOLUME_A,&iData[1]);
	write_register(PCM_VOLUME_B,&iData[1]);
}

// Function(2): Enable Right and Left headphones
void CS43_Enable_RightLeft(uint8_t side)
{
	switch (side)
	{
		case 0:
			iData[1] =  (3 << 6);  // PDN_HPB[0:1]  = 10 (HP-B always onCon)
			iData[1] |= (3 << 4);  // PDN_HPA[0:1]  = 10 (HP-A always on)
			break;
		case 1:
			iData[1] =  (2 << 6);  // PDN_HPB[0:1]  = 10 (HP-B always onCon)
			iData[1] |= (3 << 4);  // PDN_HPA[0:1]  = 10 (HP-A always on)
			break;
		case 2:
			iData[1] =  (3 << 6);  // PDN_HPB[0:1]  = 10 (HP-B always onCon)
			iData[1] |= (2 << 4);  // PDN_HPA[0:1]  = 10 (HP-A always on)
			break;
		case 3:
			iData[1] =  (2 << 6);  // PDN_HPB[0:1]  = 10 (HP-B always onCon)
			iData[1] |= (2 << 4);  // PDN_HPA[0:1]  = 10 (HP-A always on)
			break;
		default:
			break;
	}
	iData[1] |= (3 << 2);  // PDN_SPKB[0:1] = 11 (Speaker B always off)
	iData[1] |= (3 << 0);  // PDN_SPKA[0:1] = 11 (Speaker A always off)
	write_register(POWER_CONTROL2,&iData[1]);
}

// Function(3): Set Volume Level
void CS43_SetVolume(uint8_t volume)
{
	int8_t tempVol = volume - 50;
	tempVol = tempVol*(127/50);
	uint8_t myVolume =  (uint8_t )tempVol;
	iData[1] = myVolume;
	write_register(PASSTHROUGH_VOLUME_A,&iData[1]);
	write_register(PASSTHROUGH_VOLUME_B,&iData[1]);

	iData[1] = VOLUME_CONVERT_D(volume);

	/* Set the Master volume */
	write_register(CS43L22_REG_MASTER_A_VOL,&iData[1]);
	write_register(CS43L22_REG_MASTER_B_VOL,&iData[1]);
}

// Function(4): Start the Audio DAC
void CS43_Start(void)
{
	// Write 0x99 to register 0x00.
	iData[1] = 0x99;
	write_register(CONFIG_00,&iData[1]);
	// Write 0x80 to register 0x47.
	iData[1] = 0x80;
	write_register(CONFIG_47,&iData[1]);
	// Write '1'b to bit 7 in register 0x32.
	read_register(CONFIG_32, &iData[1]);
	iData[1] |= 0x80;
	write_register(CONFIG_32,&iData[1]);
	// Write '0'b to bit 7 in register 0x32.
	read_register(CONFIG_32, &iData[1]);
	iData[1] &= ~(0x80);
	write_register(CONFIG_32,&iData[1]);
	// Write 0x00 to register 0x00.
	iData[1] = 0x00;
	write_register(CONFIG_00,&iData[1]);
	//Set the "Power Ctl 1" register (0x02) to 0x9E
	iData[1] = 0x9E;
	write_register(POWER_CONTROL1,&iData[1]);
}

void CS43_Stop(void)
{
	iData[1] = 0x01;
	write_register(POWER_CONTROL1,&iData[1]);
}

Please refer to the datasheet from here for more details about each register.

21. Main Code:

#include "delay.h"
#include "stdlib.h"
#include "i2s.h"
#include "CS43L22.h"
#include "i2c.h"
#include "stdio.h"
#include "math.h"


#define F_SAMPLE	96000.0f
#define F_OUT		2000.0f
#define PI 			3.14159f


uint16_t dataI2S[100];
float mySinVal;
float sample_dt;
uint16_t sample_N;
uint16_t i_t;




int main(void)
{
	delay_init(168000000);

	i2c_init();

	I2S3_Pins_Init();
	I2S3_Init();


	CS43L22_Pin_RST_Init();
	CS43_Init(MODE_I2S);
	CS43_SetVolume(100);
	CS43_Enable_RightLeft(CS43_RIGHT_LEFT);
	CS43_Start();

	sample_dt = F_OUT/F_SAMPLE;
	sample_N = F_SAMPLE/F_OUT;

	for(uint16_t i=0; i<sample_N; i++)
	{
		mySinVal = sinf(i*2*PI*sample_dt);

		dataI2S[i*2] = (mySinVal )*8000;    //Right data (0 2 4 6 8 10 12)

		dataI2S[i*2 + 1] =(mySinVal )*8000; //Left data  (1 3 5 7 9 11 13)
	}

	I2S_SendData_DMA(dataI2S, 100);

	while(1)
	{


	}


}

22. Source Code Download:

You may download the source code from here.

23. Results:

Since the main data shall generate sinewave with 2KHz, you should hear some loud noise from the headphone.

By probing on of the channels, you should get the following:

As you can see, we got 2KHz sinewave (almost due to approximation).

Happy coding 😉

Add Comment

Your email address will not be published. Required fields are marked *