Using ES9023 as an External DAC
![]() |
| Covia Zeal Edge converted to I2S DAC |
ES9023 I2S-Related Pins
I2S Pin Configuration
I2S2 Pin Setup
Before configuring each block, the clock must be enabled. The following sets up pins PA15, PB3, and PB5 as Alternate Function (I2S2) instead of GPIO. PAxx pins belong to the GPIOA block and PBxx pins belong to the GPIOB block, each requiring separate configuration.
rcu_periph_clock_enable(RCU_AF);
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOB);
gpio_pin_remap_config(GPIO_SWJ_DISABLE_REMAP, ENABLE);
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_15);
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_3 | GPIO_PIN_5);
I2S Initialization
I2S_STD_PHILLIPS is specified as the I2S format. Regarding the I2S_CK polarity, setting it to I2S_CKPL_LOW caused unwanted edges on I2S_SD before transmission started, so I used I2S_CKPL_HIGH instead.
I2S_FRAMEFORMAT_DT24B_CH32B configures 32-bit data per channel, outputting the effective lower 24 bits starting from the MSB. I2S_AUDIOSAMPLE_44K specifies the sampling frequency.
rcu_periph_clock_enable(RCU_SPI2);
i2s_init(SPI2, I2S_MODE_MASTERTX, I2S_STD_PHILLIPS, I2S_CKPL_HIGH);
i2s_psc_config(SPI2, I2S_AUDIOSAMPLE_44K, I2S_FRAMEFORMAT_DT24B_CH32B, I2S_MCKOUT_DISABLE);
i2s_enable(SPI2);![]() |
| I2S Clock Path (excerpt from documentation) |
These settings also determine the I2S_CK frequency.
- I2S_CK frequency = 108 (MHz) / 38 = 2.842 (MHz)
- Sampling frequency = 108 (MHz) / (64 x 38) = 44.41 (KHz)
However, the error between 44.41 KHz and the target 44.1 KHz is too large, so this configuration is problematic as is.
To achieve a more accurate sampling frequency, the clock path is revised as follows. Instead of 108 MHz, a 96 MHz clock is supplied to the I2S block. In this case, if the divider ratio within the I2S block is set to 34:
- I2S_CK frequency = 96 (MHz) / 34 = 2.824 (MHz)
- Sampling frequency = 96 (MHz) / (64 x 34) = 44.118 (KHz)
which should give us the desired result.
![]() |
| I2S Clock Path Revised (excerpt from documentation) |
The additional settings required for this are as follows:
RCU_CTL &= ~(RCU_CTL_PLL1EN | RCU_CTL_PLL2EN);
rcu_predv1_config(RCU_PREDV1_DIV2);
rcu_pll2_config(RCU_PLL2_MUL12);
rcu_i2s2_clock_config(RCU_I2S2SRC_CKPLL2_MUL2);
RCU_CTL |= (RCU_CTL_PLL1EN | RCU_CTL_PLL2EN);DMA Data Transfer
- Memory-to-peripheral transfer
- Peripheral address is fixed: SPI_DATA(SPI2)
- Memory side uses 16-bit transfer, peripheral side uses 32-bit transfer
- DMA1 CH1 is used
void init_dma_i2s2(uint32_t memory_addr, uint32_t trans_number)
{
rcu_periph_clock_enable(RCU_DMA1);
dma_struct_para_init(&dma_param);
dma_param.periph_addr = &SPI_DATA(SPI2);
dma_param.periph_width = DMA_PERIPHERAL_WIDTH_32BIT;
dma_param.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_param.memory_addr = memory_addr;
dma_param.memory_width = DMA_MEMORY_WIDTH_16BIT;
dma_param.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_param.direction = DMA_MEMORY_TO_PERIPHERAL;
dma_param.number = trans_number;
dma_param.priority = DMA_PRIORITY_HIGH;
dma_init(DMA1, DMA_CH1, &dma_param);
}
#define SIZE_OF_SAMPLES 512 // samples for 2ch
int32_t audio_buf0[SIZE_OF_SAMPLES];
int32_t audio_buf1[SIZE_OF_SAMPLES];
init_i2s2();
init_dma_i2s2(audio_buf0, SIZE_OF_SAMPLES*2);
spi_dma_enable(SPI2, SPI_DMA_TRANSMIT);
dma_channel_enable(DMA1, DMA_CH1);
int count = 0;
while (1) {
if (SET == dma_flag_get(DMA1, DMA_CH1, DMA_FLAG_FTF)) {
dma_flag_clear(DMA1, DMA_CH1, DMA_FLAG_FTF);
dma_channel_disable(DMA1, DMA_CH1);
if (count % 2 == 0) {
init_dma_i2s2(audio_buf1, SIZE_OF_SAMPLES*2);
} else {
init_dma_i2s2(audio_buf0, SIZE_OF_SAMPLES*2);
}
dma_channel_enable(DMA1, DMA_CH1);
// Prepare the next audio_buf here
count++;
}
}Code Example
#include "gd32vf103_rcu.h"
#include "gd32vf103_gpio.h"
#include "gd32vf103_spi.h"
#include "gd32vf103_dma.h"
#define SIZE_OF_SAMPLES 512 // samples for 2ch
#define SAMPLE_RATE (44100)
#define WAVE_FREQ_HZ (440)
#define PI (3.14159265)
#define SAMPLE_PER_CYCLE (SAMPLE_RATE/WAVE_FREQ_HZ)
#define DELTA 2.0*PI*WAVE_FREQ_HZ/SAMPLE_RATE
int32_t audio_buf0[SIZE_OF_SAMPLES];
int32_t audio_buf1[SIZE_OF_SAMPLES];
static double ang = 0;
static int count = 0;
static double triangle_float = 0.0;
union U {
uint32_t i;
uint16_t s[2];
} u;
// 16-bit swap
uint32_t swap16b(uint32_t in_val)
{
u.i = in_val;
return ((uint32_t) u.s[0] << 16) | ((uint32_t) u.s[1]);
}
double _square_wave(void)
{
double dval;
if (ang >= 2.0*PI) {
ang -= 2.0*PI;
triangle_float = -(double) pow(2, 22);
}
if (ang < PI) {
dval = 1.0;
} else {
dval = -1.0;
}
return dval;
}
// Triangle wave generation
void setup_triangle_sine_waves(int32_t *samples_data)
{
unsigned int i;
double square_float;
double triangle_step = (double) pow(2, 23) / SAMPLE_PER_CYCLE;
for(i = 0; i < SIZE_OF_SAMPLES/2; i++) {
square_float = _square_wave();
ang += DELTA;
if (square_float >= 0) {
triangle_float += triangle_step;
} else {
triangle_float -= triangle_step;
}
square_float *= (pow(2, 23) - 1);
samples_data[i*2+0] = swap16b((int) square_float * 256);
samples_data[i*2+1] = swap16b((int) triangle_float * 256);
}
}
void prepare_audio_buf(void)
{
setup_triangle_sine_waves(audio_buf0);
setup_triangle_sine_waves(audio_buf1);
init_i2s2();
init_dma_i2s2(audio_buf0, SIZE_OF_SAMPLES*2);
spi_dma_enable(SPI2, SPI_DMA_TRANSMIT);
dma_channel_enable(DMA1, DMA_CH1);
count = 0;
}
void run_audio_buf(void)
{
if (SET == dma_flag_get(DMA1, DMA_CH1, DMA_FLAG_FTF)) {
dma_flag_clear(DMA1, DMA_CH1, DMA_FLAG_FTF);
dma_channel_disable(DMA1, DMA_CH1);
if (count % 2 == 0) {
init_dma_i2s2(audio_buf1, SIZE_OF_SAMPLES*2);
} else {
init_dma_i2s2(audio_buf0, SIZE_OF_SAMPLES*2);
}
dma_channel_enable(DMA1, DMA_CH1);
if (count % 2 == 0) {
setup_triangle_sine_waves(audio_buf0);
} else {
setup_triangle_sine_waves(audio_buf1);
}
count++;
}
}
dma_parameter_struct dma_param;
void init_dma_i2s2(uint32_t memory_addr, uint32_t trans_number)
{
rcu_periph_clock_enable(RCU_DMA1);
dma_struct_para_init(&dma_param);
dma_param.periph_addr = &SPI_DATA(SPI2);
dma_param.periph_width = DMA_PERIPHERAL_WIDTH_32BIT;
dma_param.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_param.memory_addr = memory_addr;
dma_param.memory_width = DMA_MEMORY_WIDTH_16BIT;
dma_param.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_param.direction = DMA_MEMORY_TO_PERIPHERAL;
dma_param.number = trans_number;
dma_param.priority = DMA_PRIORITY_HIGH;
dma_init(DMA1, DMA_CH1, &dma_param);
}
void init_i2s2(void)
{
rcu_periph_clock_enable(RCU_AF);
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_SPI2);
RCU_CTL &= ~(RCU_CTL_PLL1EN | RCU_CTL_PLL2EN);
rcu_predv1_config(RCU_PREDV1_DIV2);
rcu_pll2_config(RCU_PLL2_MUL12);
rcu_i2s2_clock_config(RCU_I2S2SRC_CKPLL2_MUL2);
RCU_CTL |= (RCU_CTL_PLL1EN | RCU_CTL_PLL2EN);
gpio_pin_remap_config(GPIO_SWJ_DISABLE_REMAP, ENABLE);
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_15);
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_3 | GPIO_PIN_5);
i2s_init(SPI2, I2S_MODE_MASTERTX, I2S_STD_PHILLIPS, I2S_CKPL_HIGH);
i2s_psc_config(SPI2, I2S_AUDIOSAMPLE_44K, I2S_FRAMEFORMAT_DT24B_CH32B, I2S_MCKOUT_DISABLE);
i2s_enable(SPI2);
}
int main(void)
{
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOC);
gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);
gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1|GPIO_PIN_2);
prepare_audio_buf();
while (1) {
run_audio_buf();
}
return 0;
}





No comments:
Post a Comment