KNOWLEDGEEMDEDDED

เปลี่ยนโค้ดให้กลายเป็นเสียงจริง! สร้าง Audio Generator ด้วย STM32F407 และ CS43L22 DAC

รู้จักกับ CS43L22 Audio DAC

CS43L22 คือชิป Stereo Audio DAC พร้อมภาคขยายเสียงสำหรับหูฟังและลำโพง ที่ออกแบบโดย Cirrus Logic โดยเน้นการใช้พลังงานต่ำและการรวมฟังก์ชันต่าง ๆ ไว้ภายในชิปเดียว

บนบอร์ด STM32F407 Discovery ชิปตัวนี้ทำหน้าที่เป็นสะพานเชื่อมระหว่างอัลกอริทึมประมวลผลเสียงในซอฟต์แวร์กับโลกของสัญญาณแอนะล็อก โดยรับข้อมูลเสียงในรูปแบบดิจิทัลจากไมโครคอนโทรลเลอร์ แล้วแปลงให้เป็นคลื่นเสียงที่สามารถรับฟังได้ผ่านช่องเสียบหูฟัง 3.5 มม. (สีน้ำเงิน) ที่ติดตั้งอยู่บนบอร์ด

ภายใน CS43L22 ยังมาพร้อมกับชุดประมวลผลสัญญาณดิจิทัล (Digital Signal Processing: DSP), วงจร Multi-bit Delta-Sigma Modulator, ไดรเวอร์ลำโพงแบบ Class-D และภาคขยายเสียงสำหรับหูฟังแบบสเตอริโอ ช่วยให้สามารถดำเนินกระบวนการแปลงสัญญาณดิจิทัลเป็นแอนะล็อกได้อย่างมีประสิทธิภาพ พร้อมลดความจำเป็นในการใช้อุปกรณ์ภายนอกเพิ่มเติม


ในบทความนี้ เราจะครอบคลุมหัวข้อต่อไปนี้

  • บทนำ (Introduction)
  • การตั้งค่า STM32CubeMX
  • การพัฒนาเฟิร์มแวร์ (Firmware Development)
  • ผลลัพธ์การทดสอบ (Results)

การประยุกต์ใช้งาน (Real-World Applications)

การเชื่อมต่อ I2S DAC ภายนอกเข้ากับไมโครคอนโทรลเลอร์ STM32 ช่วยเปิดโอกาสให้สามารถพัฒนาระบบเสียงแบบ Embedded Audio ได้อย่างหลากหลาย ไม่ว่าจะเป็นงานสร้างสัญญาณ การเล่นไฟล์เสียง หรือการประมวลผลเสียงแบบ Real-Time ตัวอย่างการใช้งานที่สำคัญ ได้แก่

  • Waveform & Function Generators :
    สร้างสัญญาณ analog ที่มีความแม่นยำและสามารถปรับแต่งได้ เช่น Sine Wave, Square Wave, Sawtooth Wave และ Triangle Wave เหมาะสำหรับเครื่องมือทดสอบในห้องปฏิบัติการ อุปกรณ์สอบเทียบ (Calibration) หรือเครื่องดนตรีอิเล็กทรอนิกส์และซินธิไซเซอร์
  • Embedded Audio Players :
    อ่านไฟล์เสียงทั้งแบบบีบอัดและไม่บีบอัด เช่น .wav หรือ .mp3 จากการ์ด SD Card หรือหน่วยความจำแฟลชภายนอก แล้วส่งข้อมูลเสียงผ่านอินเทอร์เฟซ I2S ไปยังหูฟังหรือลำโพงได้โดยตรง
  • Voice Alerts & Industrial Telemetry :
    เพิ่มความสามารถในการเล่นข้อความเสียงแจ้งเตือน เสียงสัญญาณเตือน และเสียงแสดงสถานะคุณภาพสูงให้กับอุปกรณ์อุตสาหกรรม เครื่องใช้ไฟฟ้าอัจฉริยะ (Smart Home) หรืออุปกรณ์ทางการแพทย์ เพื่อให้ผู้ใช้งานรับรู้สถานะของระบบได้อย่างชัดเจน
  • Real-Time Digital Signal Processing (DSP) :
    พัฒนาระบบประมวลผลเสียงแบบเรียลไทม์ เช่น การกรองสัญญาณดิจิทัล (Digital Filtering) การปรับแต่งย่านความถี่ (Equalization) การสร้างเอฟเฟกต์เสียง เช่น Reverb, Delay และ Pitch Shifting รวมถึงการมิกซ์สัญญาณเสียงจากหลายแชนเนลให้รวมเป็นสัญญาณเอาต์พุตเพียงช่องทางเดียวได้อย่างมีประสิทธิภาพ

2. การตั้งค่า STM32CubeMX

เปิดไฟล์โปรเจกต์นามสกุล .ioc ด้วยโปรแกรม STM32CubeMX

จากนั้น เราจำเป็นต้องตรวจสอบว่าชิป CS43L22 Audio DAC เชื่อมต่อกับขา I2C ของไมโครคอนโทรลเลอร์ผ่านขาใดบ้าง โดยสามารถดูข้อมูลได้จากแผนผังวงจร (Schematic) ของบอร์ด ซึ่งจะพบว่ามีการเชื่อมต่อดังนี้

  • PB9 ทำหน้าที่เป็น SDA (Serial Data)
  • PB6 ทำหน้าที่เป็น SCL (Serial Clock)

นอกจากนี้ ควรจด Address ของ I2C ที่ระบุไว้ใน Schematic ด้วย เนื่องจากจะต้องนำไปใช้ในการตั้งค่าและสื่อสารกับชิป DAC ในขั้นตอนถัดไปของบทความ

STM32 I2S

จากนั้น ให้เปิด STM32CubeMX แล้วกำหนดขา PB6 และ PB9 ให้ทำงานในโหมด I2C1 จากนั้นเปิดใช้งาน (Enable) โมดูล I2C1 ดังแสดงในรูปด้านล่าง

STM32CubeMX แล้วกำหนดขา PB6 และ PB9

คงค่า parameters ทั้งหมดไว้ที่ค่าเริ่มต้น (Default) เนื่องจากเราจะทำการกำหนดค่าเริ่มต้น (Initialize) ให้กับชิป DAC เพียงครั้งเดียวในช่วงเริ่มต้นของโปรแกรม

จากนั้น ไปที่ส่วนการตั้งค่า I2S แล้วปรับลดค่า Audio Frequency จาก 192 kHz เหลือ 96 kHz ก่อนสร้างโค้ด (Generate Code) ดังแสดงในรูปต่อไปนี้

ส่วนการตั้งค่า I2S แล้วปรับลดค่า Audio Frequency

การตั้งค่าเสร็จเสร็จเรียบร้อยแล้ว

3. การพัฒนาเฟิร์มแวร์ (Firmware Development)

เปิดโปรเจกต์ด้วยโปรแกรม STM32CubeIDE

เริ่มต้นด้วยการสร้างไฟล์ Header และ Source ใหม่สำหรับจัดการการทำงานของชิป CS43L22 Audio DAC โดยตั้งชื่อไฟล์ดังนี้

  • CS43L22.h (Header File)
  • CS43L22.c (Source File)
  • main.c

สำหรับการสร้างไฟล์ Source ให้ดำเนินการตามขั้นตอนดังต่อไปนี้

สำหรับการสร้างไฟล์ Source

สำหรับการสร้างไฟล์ Header ให้ดำเนินการตามขั้นตอนดังต่อไปนี้

การสร้างไฟล์ Header

จากนั้น ตั้งชื่อไฟล์ตามต้องการ แล้วคลิก Finish เพื่อสร้างไฟล์ใหม่

ตั้งชื่อไฟล์ตามต้องการ

เมื่อสร้างไฟล์ Header และ Source เรียบร้อยแล้ว ให้เปิดไฟล์ Header (CS43L22.h) เพื่อ Declarations ส่วนต่าง ๆ ที่จำเป็นสำหรับการใช้งานชิป CS43L22 ต่อไป

การ Include และ define DAC_I2C_ADDR

C
#include "stdint.h"
#define DAC_I2C_ADDR  (0x94) /* declare the address of the DAC */


การกำหนดค่าใน Header

C
#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 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)))

จากนั้น ประกาศตัวแปรชนิด Enumeration (enum) สำหรับเก็บโหมดการทำงานของระบบ

C
typedef enum
{
	CS43L22_MODE_I2S = 0,
	CS43L22_M
}CS43_MODE;

ประกาศฟังก์ชันใน Header

C
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_RST_Pin_Low(void);
void CS43L22_RST_Pin_High(void);

เมื่อเพิ่มโค้ดทั้งหมดเรียบร้อยแล้ว จะได้ไฟล์ CS43L22.h ฉบับสมบูรณ์ดังต่อไปนี้

C
#ifndef INC_CS43L22_H_
#define INC_CS43L22_H_
#include "stdint.h"
#define DAC_I2C_ADDR 				    (0x94)
#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 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
{
	CS43L22_MODE_I2S = 0,
	CS43L22_MODE_ANALOG,
}CS43_MODE;

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_RST_Pin_Low(void);
void CS43L22_RST_Pin_High(void);

#endif /* INC_CS43L22_H_ */


Include Header ไฟล์ตามนี้

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

ประกาศ อาร์เรย์ (Array) สำหรับเก็บข้อมูลการตั้งค่า (Configuration Data)

C
static uint8_t iData[2];

จากนั้น ให้สร้างฟังก์ชันสำหรับใช้ในการเขียนข้อมูลลงในรีจิสเตอร์ (Register Write) และอ่านค่าข้อมูลจากรีจิสเตอร์ (Register Read) ของชิป DAC ดังตัวอย่างต่อไปนี้:

C
// Function(1): Write to register
static void write_register(uint8_t reg, uint8_t *data)
{
	iData[0] = reg;
	HAL_I2C_Mem_Write(&hi2c1, DAC_I2C_ADDR, reg, 1, data, 1, 10);



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

ถัดไป ให้สร้าง Helper Function สำหรับใช้กำหนดสถานะของขา RST (Reset Pin) ของชิป DAC ดังตัวอย่างต่อไปนี้

C
void CS43L22_RST_Pin_Low(void)
{
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_4, RESET);
}

void CS43L22_RST_Pin_High(void)
{
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_4, SET);
}

ถัดไป เป็นขั้นตอนการเริ่มต้นการทำงานของชิป (Initialization):

C
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 == CS43L22_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 == CS43L22_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]);
}

จากนั้น ให้เลือกช่องสัญญาณเสียงที่ต้องการใช้งาน ได้แก่ ช่องซ้าย (Left), ช่องขวา (Right) หรือใช้งานทั้งสองช่อง (Both):

C
// 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]);
}

ตั้งค่าความดังของเสียง (Volume)

C
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 และ Stop การทำงาน DAC

C
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]);
}

เมื่อเพิ่มโค้ดทั้งหมดเรียบร้อยแล้ว จะได้ไฟล์ CS43L22.c ฉบับสมบูรณ์ดังต่อไปนี้

C
#include "CS43L22.h"
#include "i2c.h"
static uint8_t iData[2];

// Function(1): Write to register
static void write_register(uint8_t reg, uint8_t *data)
{
	iData[0] = reg;
	HAL_I2C_Mem_Write(&hi2c1, DAC_I2C_ADDR, reg, 1, data, 1, 10);
}
// Function(2): Read from register
static void read_register(uint8_t reg, uint8_t *data)
{
	uint8_t tempData=0;
	HAL_I2C_Mem_Read(&hi2c1, DAC_I2C_ADDR, reg, 1, &tempData, 1, 10);
	*data=tempData;
}

void CS43L22_RST_Pin_Low(void)
{
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_4, RESET);
}

void CS43L22_RST_Pin_High(void)
{
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_4, SET);
}

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 == CS43L22_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 == CS43L22_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]);
}

เพิ่มไฟล์ Header ต่อไปนี้ในไฟล์ main.c

C
#include "math.h"
#include "CS43L22.h"

ภายในส่วน user code begin PD ให้เพิ่มโค้ดดังต่อไปนี้:

C
#define F_SAMPLE	96000.0f
#define F_OUT		4000.0f
#define PI 			3.14159f

#define SAMPLES_PER_CYCLE	(F_SAMPLE / F_OUT)  // 24.0
#define NUM_CYCLES			10
#define BUFFER_SIZE			((int)(SAMPLES_PER_CYCLE * NUM_CYCLES) * 2)  // *2 for stereo

uint16_t dataI2S[BUFFER_SIZE];  // 480 elements for 10 cycles

float phase_increment = 2.0f * PI / SAMPLES_PER_CYCLE;
float current_phase = 0.0f;

ภายในส่วน user code begin PD 2 ของฟังก์ชัน main() ให้เพิ่มโค้ดสำหรับดำเนินการดังต่อไปนี้

  • เริ่มต้นการทำงานของ DAC
  • กำหนดค่าเริ่มต้นให้กับ DAC ในโหมด I2S
  • ตั้งค่าระดับเสียง (Volume) เป็นค่าสูงสุด
  • เลือกใช้งานทั้งช่องสัญญาณซ้ายและขวา (Stereo)
C
CS43_Init(CS43L22_MODE_I2S);
CS43_SetVolume(100);
CS43_Enable_RightLeft(CS43_RIGHT_LEFT);
CS43_Start();

จากนั้น กำหนดข้อมูลให้กับอาร์เรย์ ดังตัวอย่างต่อไปนี้

C
float phase_increment = 2.0f * PI / SAMPLES_PER_CYCLE;
float current_phase = 0.0f;

for(uint16_t i=0; i<BUFFER_SIZE/2; i++)  // 240 stereo pairs
{
  float mySinVal = sinf(current_phase);

  // Scale to 16-bit signed range
  int16_t right_value = (int16_t)(mySinVal * 32000.0f);
  int16_t left_value = -right_value;  // Inverted signal

  dataI2S[i*2] = right_value;      // Right channel
  dataI2S[i*2 + 1] = left_value;   // Left channel (inverted)

  current_phase += phase_increment;

  // Keep phase in 0 to 2*PI range to prevent floating point drift
  if(current_phase >= 2.0f * PI) {
    current_phase -= 2.0f * PI;
  }
}

จากนั้น ส่งข้อมูลผ่าน DMA ดังนี้:

C
HAL_I2S_Transmit_DMA(&hi2s3, dataI2S, BUFFER_SIZE);

เพียงเท่านี้ก็เป็นอันเสร็จสิ้นขั้นตอนการพัฒนาเฟิร์มแวร์

จากนั้น บันทึกโปรเจกต์ (Save) คอมไพล์โปรเจกต์ (Build) และดาวน์โหลดโปรแกรมลงบนบอร์ดเพื่อทดสอบการทำงาน

save and build

เมื่อตรวจวัดสัญญาณจากทั้งสองช่องสัญญาณ จะได้ผลลัพธ์ดังนี้

STM32 I2S DMA SIGNAL

เพียงเท่านี้ก็เป็นอันเสร็จสิ้นการเชื่อมต่อและใช้งาน CS43L22 Audio DAC บนบอร์ด STM32F407 Discovery โดยใช้ I2C สำหรับกำหนดค่าการทำงานของ DAC และ I2S ร่วมกับ DMA สำหรับส่งข้อมูลเสียงอย่างต่อเนื่อง ส่งผลให้ไมโครคอนโทรลเลอร์สามารถสร้างและส่งสัญญาณเสียงรูปคลื่นต่าง ๆ ออกทางช่องเสียบหูฟัง 3.5 มม. ได้อย่างมีประสิทธิภาพ ซึ่งเป็นพื้นฐานสำคัญสำหรับการพัฒนาแอปพลิเคชันด้านเสียงบนระบบ Embedded ต่อไป


Source : STM32F407 Discovery Audio: I2S DAC
Code : GITHUB

my avatar

Pawinphat

Founder of Bunpajarn.com • Electronics & Embedded Systems

Leave a Reply

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