I am quite new to the nRF5340 Audio DK and used ace.johnny 's project on https://github.com/ace-johnny/nrfadk-hello_codec to get me started.
The overall project goal is to process audio data coming via line-in on the nRF5340 to add filters, effects, etc. and output again via the headphone.
In order to achieve this I transmit the data from the codec via i2s to the MCU, process it there and send it back. However, something in this chain is introducting a lot of noise/clipping and I can't find the reason for this. Right now my processing of the received data is just copying it 1:1 to the transmit buffer - no change whatsoever.
When I directly route the line-in input to the output and not going via i2s to the MCU and back everything is crystal clear.
This is the code I use:
/**
* @file main.c
*
* @brief Audio DK HW_CODEC test using I2S loop and tone/noise generators.
*/
#include <zephyr/kernel.h>
#include <nrf.h>
#include <nrfx_clock.h>
#include "cs47l63_comm.h"
////////////////////////////////////////////////////////////////////////////////
// NRFX_CLOCKS
#define HFCLKAUDIO_12_288_MHZ 0x9BA6
#define ENABLE_LINEIN
#undef ENABLE_MIC
/**
* @brief Initialize the high-frequency clocks and wait for each to start.
*
* @details HFCLK = 128,000,000 Hz
* HFCLKAUDIO = 12,288,000 Hz
*/
static int nrfadk_hfclocks_init(void)
{
nrfx_err_t err;
// HFCLK
err = nrfx_clock_divider_set(NRF_CLOCK_DOMAIN_HFCLK, NRF_CLOCK_HFCLK_DIV_1);
if (err != NRFX_SUCCESS) return (err - NRFX_ERROR_BASE_NUM);
nrfx_clock_start(NRF_CLOCK_DOMAIN_HFCLK);
while (!nrfx_clock_is_running(NRF_CLOCK_DOMAIN_HFCLK, NULL)) k_msleep(1);
// HFCLKAUDIO
nrfx_clock_hfclkaudio_config_set(HFCLKAUDIO_12_288_MHZ);
nrfx_clock_start(NRF_CLOCK_DOMAIN_HFCLKAUDIO);
while (!nrfx_clock_is_running(NRF_CLOCK_DOMAIN_HFCLKAUDIO, NULL)) k_msleep(1);
return 0;
}
////////////////////////////////////////////////////////////////////////////////
// NRF_I2S
#define MCKFREQ_6_144_MHZ 0x66666000
#define I2S_BUFF_SIZE 256 // Define an appropriate buffer size
static int16_t rx_buffer[I2S_BUFF_SIZE]; // Buffer for received data
static int16_t tx_buffer[I2S_BUFF_SIZE];
void process_audio(int16_t *rx, int16_t *tx, size_t size)
{
for (size_t i = 0; i < size; i++) {
tx[i] = rx[i]; // Simple passthrough (modify as needed)
//tx[i] = (int16_t)(rx[i]*0.1); // Volume control
//tx[size-1-i] = rx[i]; // Reverse - doesn't work... -> only outputs silence...?
}
}
/**
* @brief Initialize and start the I2S peripheral using NRF registers.
*
* @details I2S master, 48kHz 16bit, Left mono, TX only.
*/
static int nrfadk_i2s_reg_init(void)
{
// Configure and enable
NRF_I2S0->CONFIG.CLKCONFIG = I2S_CONFIG_CLKCONFIG_CLKSRC_ACLK;
NRF_I2S0->CONFIG.MCKFREQ = MCKFREQ_6_144_MHZ;
NRF_I2S0->CONFIG.RATIO = I2S_CONFIG_RATIO_RATIO_128X;
NRF_I2S0->CONFIG.CHANNELS = I2S_CONFIG_CHANNELS_CHANNELS_Left;
NRF_I2S0->CONFIG.TXEN = I2S_CONFIG_TXEN_TXEN_Enabled; // Send audio samples to the nRF5340
NRF_I2S0->CONFIG.RXEN = I2S_CONFIG_RXEN_RXEN_Enabled; // Enable RX to receive the data from the nRF5340
NRF_I2S0->ENABLE = I2S_ENABLE_ENABLE_Enabled;
// Start TX buffer
NRF_I2S0->RXD.PTR = (uint32_t)rx_buffer;
NRF_I2S0->TXD.PTR = (uint32_t)tx_buffer;
NRF_I2S0->RXTXD.MAXCNT = I2S_BUFF_SIZE / sizeof(uint32_t);
// Clear pending events
NRF_I2S0->EVENTS_RXPTRUPD = 0;
NRF_I2S0->EVENTS_TXPTRUPD = 0;
NRF_I2S0->TASKS_START = I2S_TASKS_START_TASKS_START_Trigger;
return 0;
}
void i2s_polling_loop(void)
{
while (1)
{
// Wait for new RX data
while (NRF_I2S0->EVENTS_RXPTRUPD == 0);
// Clear RX event
NRF_I2S0->EVENTS_RXPTRUPD = 0;
// Process audio data
process_audio(rx_buffer, tx_buffer, I2S_BUFF_SIZE); // For now, just set tx_buffer == rx_buffer
// Ensure we restart I2S for continuous operation
NRF_I2S0->TASKS_START = I2S_TASKS_START_TASKS_START_Trigger;
while (NRF_I2S0->EVENTS_TXPTRUPD == 0);
}
}
////////////////////////////////////////////////////////////////////////////////
// HW_CODEC
/** CS47L63 driver state handle. */
static cs47l63_t cs47l63_driver;
/** CS47L63 subsystems configuration. */
static const uint32_t cs47l63_cfg[][2] =
{
// Audio Serial Port 1 (I2S slave, 48kHz 16bit, Left mono, RX and TX)
{ CS47L63_ASP1_CONTROL2,
(0x10 << CS47L63_ASP1_RX_WIDTH_SHIFT) | // 16bit
(0x10 << CS47L63_ASP1_TX_WIDTH_SHIFT) | // 16bit
(0b010 << CS47L63_ASP1_FMT_SHIFT) // I2S
},
{ CS47L63_ASP1_CONTROL3,
(0b00 << CS47L63_ASP1_DOUT_HIZ_CTRL_SHIFT) // Always 0
},
// Enable the various channels for RX and TX
{ CS47L63_ASP1_ENABLES1,
(0 << CS47L63_ASP1_RX2_EN_SHIFT) | // Disabled
(1 << CS47L63_ASP1_RX1_EN_SHIFT) | // Enabled
(0 << CS47L63_ASP1_TX2_EN_SHIFT) | // Disabled
//(0 << CS47L63_ASP1_TX1_EN_SHIFT) // Disabled
(1 << CS47L63_ASP1_TX1_EN_SHIFT) // Enabled - we want to send something to the nRF5340
},
#ifdef ENABLE_LINEIN
// Enable line-in
{ CS47L63_INPUT2_CONTROL1, 0x00050020 },/* MODE=analog */
{ CS47L63_IN2L_CONTROL1, 0x10000000 }, /* SRC=IN2LP */
{ CS47L63_IN2R_CONTROL1, 0x10000000 }, /* SRC=IN2RP */
{ CS47L63_INPUT_CONTROL, 0x0000000C }, /* IN2_EN=1 */
// Set volume for line-in
{ CS47L63_IN2L_CONTROL2, 0x00800080 }, /* VOL=0dB, MUTE=0 */
{ CS47L63_IN2R_CONTROL2, 0x00800080 }, /* VOL=0dB, MUTE=0 */
{ CS47L63_INPUT_CONTROL3, 0x20000000 }, /* VU=1 */
// Important
/* Route IN2L and IN2R to I2S */
{ CS47L63_ASP1TX1_INPUT1, 0x800012 },
{ CS47L63_ASP1TX2_INPUT1, 0x800013 },
#endif
// Output 1 Left (reduced MIX_VOLs to prevent clipping summed signals)
// this here is only there so we hear also that i2s data is sent
{ CS47L63_OUT1L_INPUT1,
(0x2B << CS47L63_OUT1LMIX_VOL1_SHIFT) | // quite weak
(0x020 << CS47L63_OUT1L_SRC1_SHIFT) // ASP1_RX1 // from MCU (currently a sine wave only)
},
{ CS47L63_OUT1L_INPUT2,
(0x2B << CS47L63_OUT1LMIX_VOL2_SHIFT) | //
(0x021 << CS47L63_OUT1L_SRC2_SHIFT) // ASP1_RX2
},
#undef ENABLE_LINEIN
#ifdef ENABLE_LINEIN
// We need both channels here, even if we only have one output channel
// If we uncomment the next two {} we won´t get any line in pass-through, only i2s
{
CS47L63_OUT1L_INPUT3,
(0x2B << CS47L63_OUT1LMIX_VOL3_SHIFT) |
(0x012 << CS47L63_OUT1L_SRC3_SHIFT) // 0x12=IN2L
},
{
CS47L63_OUT1L_INPUT4,
(0x2B << CS47L63_OUT1LMIX_VOL4_SHIFT) |
(0x013 << CS47L63_OUT1L_SRC4_SHIFT) // 0x13=IN2R
},
#endif
{ CS47L63_OUTPUT_ENABLE_1,
(1 << CS47L63_OUT1L_EN_SHIFT) // Enabled
},
};
/**
* @brief Write a configuration array to multiple CS47L63 registers.
*
* @param[in] config: Array of address/data pairs.
* @param[in] length: Number of registers to write.
*
* @retval `CS47L63_STATUS_OK` The operation was successful.
* @retval `CS47L63_STATUS_FAIL` Writing to the control port failed.
*/
static int nrfadk_hwcodec_config(const uint32_t config[][2], uint32_t length)
{
int ret;
uint32_t addr;
uint32_t data;
for (int i = 0; i < length; i++)
{
addr = config[i][0];
data = config[i][1];
ret = cs47l63_write_reg(&cs47l63_driver, addr, data);
if (ret) return ret;
}
return CS47L63_STATUS_OK;
}
/**
* @brief Initialize the CS47L63, start clocks, and configure subsystems.
*
* @details MCLK1 = 6,144,000 Hz (I2S MCK = CONFIG.MCKFREQ)
* FSYNC = 48,000 Hz (I2S LRCK = MCK / CONFIG.RATIO)
* BCLK = 1,536,000 Hz (I2S SCK = LRCK * CONFIG.SWIDTH * 2)
* FLL1 = 49,152,000 Hz (MCLK1 * 8)
* SYSCLK = 98,304,000 Hz (FLL1 * 2)
*
* @retval `CS47L63_STATUS_OK` The operation was successful.
* @retval `CS47L63_STATUS_FAIL` Initializing the CS47L63 failed.
*
* @note I2S MCK must already be running before calling this function.
*/
static int nrfadk_hwcodec_init(void)
{
int ret = CS47L63_STATUS_OK;
// Initialize driver
ret += cs47l63_comm_init(&cs47l63_driver);
// Start FLL1 and SYSCLK
ret += cs47l63_fll_config(&cs47l63_driver, CS47L63_FLL1,
CS47L63_FLL_SRC_MCLK1, 6144000, 49152000);
ret += cs47l63_fll_enable(&cs47l63_driver, CS47L63_FLL1);
ret += cs47l63_fll_wait_for_lock(&cs47l63_driver, CS47L63_FLL1);
ret += cs47l63_update_reg(&cs47l63_driver, CS47L63_SYSTEM_CLOCK1,
CS47L63_SYSCLK_EN_MASK, CS47L63_SYSCLK_EN);
// Configure subsystems
ret += nrfadk_hwcodec_config(cs47l63_cfg, ARRAY_SIZE(cs47l63_cfg));
return ret;
}
////////////////////////////////////////////////////////////////////////////////
// MAIN
int main(void)
{
// Initialize Audio DK
if (nrfadk_hfclocks_init() ||
nrfadk_i2s_reg_init() ||
nrfadk_hwcodec_init())
{
printk("\nError initializing Audio DK\n");
return -1;
}
printk("\nAudio DK initialized\n");
k_msleep(1250);
// Unmute OUT1L I2S playback and enable NOISE/TONE1 generators
cs47l63_update_reg(&cs47l63_driver, CS47L63_OUT1L_VOLUME_1,
CS47L63_OUT_VU_MASK | CS47L63_OUT1L_MUTE_MASK,
CS47L63_OUT_VU | 0);
printk("\nOUT1L unmuted for 5000ms\n");
k_msleep(5000);
i2s_polling_loop(); // We actually never return from here (testing)
while(1);
cs47l63_update_reg(&cs47l63_driver, CS47L63_OUT1L_VOLUME_1,
CS47L63_OUT_VU_MASK | CS47L63_OUT1L_MUTE_MASK,
CS47L63_OUT_VU | CS47L63_OUT1L_MUTE);
printk("OUT1L muted\n");
k_msleep(1250);
// Shutdown Audio DK (reverse order initialized)
cs47l63_update_reg(&cs47l63_driver, CS47L63_OUTPUT_ENABLE_1,
CS47L63_OUT1L_EN_MASK, 0);
printk("\nOUT1L disabled\n");
k_msleep(250);
cs47l63_update_reg(&cs47l63_driver, CS47L63_SYSTEM_CLOCK1,
CS47L63_SYSCLK_EN_MASK, 0);
printk("SYSCLK disabled\n");
k_msleep(250);
cs47l63_fll_disable(&cs47l63_driver, CS47L63_FLL1);
printk("FLL1 disabled\n");
k_msleep(250);
NRF_I2S0->TASKS_STOP = I2S_TASKS_STOP_TASKS_STOP_Trigger;
NRF_I2S0->ENABLE = I2S_ENABLE_ENABLE_Disabled;
printk("I2S disabled\n");
k_msleep(250);
nrfx_clock_stop(NRF_CLOCK_DOMAIN_HFCLKAUDIO);
while (nrfx_clock_is_running(NRF_CLOCK_DOMAIN_HFCLKAUDIO, NULL)) k_msleep(1);
printk("HFCLKAUDIO stopped\n");
k_msleep(250);
nrfx_clock_stop(NRF_CLOCK_DOMAIN_HFCLK);
while (nrfx_clock_is_running(NRF_CLOCK_DOMAIN_HFCLK, NULL)) k_msleep(1);
printk("HFCLK stopped\n");
k_msleep(250);
printk("\nAudio DK shutdown\n\n");
k_msleep(100);
return 0;
}
process_audio() is the simple function which just sets the TX buffer to the RX buffer's values.
(Here I want eventually to add my processing)

