I2S Audio out on XIAO NRF52840 Sense distorted

I’ve been struggling for a long time now, trying to get I2S working properly on a XIAO Sense 52840, and the closest I’ve gotten is some distorted audio that can hardly be made out.

I’ve tried various pin combinations, libraries, MBED vs non-MBED cores, and writing my own library/code.

What I get seems close to the proper output, but it is all distorted and non-useable. I have however, been able to get it working with PWM output and PDM microphone.

My code is the GitHub - TMRh20/AutoAnalogAudio: Create a wide range of sampling and audio related applications with simple API for onboard DAC (or PWM), ADC, DMA & Timers on Arduino devices (AVR & SAM) library (need to open src/NRF52840/AutoAnalogAudio.cpp and un-comment #define USE_I2s )

Here is some old code that I believe should work as well:

#include <hal/nrf_pdm.h>
#include <hal/nrf_i2s.h>
#include <SD.h>
File myFile;

#define PIN_MCK (13)
#define PIN_SCK (14)
#define PIN_LRCK (15)
#define PIN_SDOUT (2)

uint8_t bigBuffer[16000 * 5];
uint8_t smallBuffer[32];

void setup() {

  Serial.begin(115200);
  while (!Serial) { delay(10); }
  delay(5000);
  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    while (1)
      ;
  } else {
    Serial.println("SD initialization success!");
  }
  myFile = SD.open("calibrat.wav");
  if (myFile) {
    Serial.print("FOK");
  }
  myFile.seek(44);

  Serial.println("reading into buffer");
  uint32_t counter = 0;
  while (myFile.available()) {
    bigBuffer[counter++] = myFile.read();
    if (counter > 16000 * 5) { break; }
  }
  Serial.println("finished loading buffer");
  myFile.close();

  // Enable transmission
  NRF_I2S->CONFIG.TXEN = (I2S_CONFIG_TXEN_TXEN_ENABLE << I2S_CONFIG_TXEN_TXEN_Pos);

  // Enable MCK generator
  NRF_I2S->CONFIG.MCKEN = (I2S_CONFIG_MCKEN_MCKEN_ENABLE << I2S_CONFIG_MCKEN_MCKEN_Pos);

  // MCKFREQ = 4 MHz
  //NRF_I2S->CONFIG.MCKFREQ = I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV11  << I2S_CONFIG_MCKFREQ_MCKFREQ_Pos;
  NRF_I2S->CONFIG.MCKFREQ = I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV63 << I2S_CONFIG_MCKFREQ_MCKFREQ_Pos;

  // Ratio = 64
  NRF_I2S->CONFIG.RATIO = I2S_CONFIG_RATIO_RATIO_64X << I2S_CONFIG_RATIO_RATIO_Pos;
  //NRF_I2S->CONFIG.RATIO = I2S_CONFIG_RATIO_RATIO_256X << I2S_CONFIG_RATIO_RATIO_Pos;

  // Master mode, 16Bit, left aligned
  NRF_I2S->CONFIG.MODE = I2S_CONFIG_MODE_MODE_MASTER << I2S_CONFIG_MODE_MODE_Pos;
  NRF_I2S->CONFIG.SWIDTH = I2S_CONFIG_SWIDTH_SWIDTH_8BIT << I2S_CONFIG_SWIDTH_SWIDTH_Pos;
  NRF_I2S->CONFIG.ALIGN = I2S_CONFIG_ALIGN_ALIGN_LEFT << I2S_CONFIG_ALIGN_ALIGN_Pos;

  // Format = I2S
  NRF_I2S->CONFIG.FORMAT = I2S_CONFIG_FORMAT_FORMAT_I2S << I2S_CONFIG_FORMAT_FORMAT_Pos;

  // Use stereo
  NRF_I2S->CONFIG.CHANNELS = I2S_CONFIG_CHANNELS_CHANNELS_LEFT << I2S_CONFIG_CHANNELS_CHANNELS_Pos;

  // Configure pins
  NRF_I2S->PSEL.MCK = (PIN_MCK << I2S_PSEL_MCK_PIN_Pos);
  NRF_I2S->PSEL.SCK = (PIN_SCK << I2S_PSEL_SCK_PIN_Pos);
  NRF_I2S->PSEL.LRCK = (PIN_LRCK << I2S_PSEL_LRCK_PIN_Pos);
  NRF_I2S->PSEL.SDOUT = (PIN_SDOUT << I2S_PSEL_SDOUT_PIN_Pos);

  NRF_I2S->ENABLE = 1;

  // Configure data pointer
  NRF_I2S->TXD.PTR = (uint32_t)&smallBuffer[0];
  NRF_I2S->RXTXD.MAXCNT = 32 / sizeof(uint32_t);
  //NRF_I2S->TXD.PTR = (uint32_t)&sine_table[0];
  //NRF_I2S->RXTXD.MAXCNT = sizeof(sine_table) / sizeof(uint32_t);

  // Start transmitting I2S data
  NRF_I2S->TASKS_START = 1;
}

uint32_t sampleCounter = 0;

void loop() {

  if (sampleCounter < 16000 * 5) {
    while (!nrf_i2s_event_check(NRF_I2S, NRF_I2S_EVENT_TXPTRUPD));
    for (int i = 0; i < 32; i++) {
      smallBuffer[i] = bigBuffer[sampleCounter++];
    }

    NRF_I2S->RXTXD.MAXCNT = 32 / sizeof(uint32_t);
    nrf_i2s_event_clear(NRF_I2S, NRF_I2S_EVENT_TXPTRUPD);
  }
}

It seems others are having success, so I’m just wondering what I’m possibly doing wrong here…

I’ve seen the thread at XIAO nrf52840 and I2S and still can’t get it working properly. Even bought an I2s amplifier, but that seems to have been a waste of money at this point.

This is on a barebones XIAO Sense 52840 connected to a grove shield, breadboard or other, it doesn’t matter, still doesn’t work quite properly.

Would really love to have this working for higher quality output than PWM can provide. Any advice is welcome!

Hi there,
So I have seen this b4 , definitely check out the Threads on here for sure, one is on the I2S amplifier if I recall correctly. Also is a great Youtube video of a Rest Room Key Fob with the Nrf52840 using I2S and speaker installed(there’s more than one}. So no one walks away with the door key. https://youtu.be/VC8PvGddNOE?si=u7Rp1VXOuwdlJDDh
covers I2S and BLE…
HTH
GL :slight_smile: PJ :v:

One worthy note is the Power supply has got to be of sufficient quality with very little or no ripple o get good High quality sound from it.

Thanks for the tips.

It seems like I’ve tried everything at this point, pin combinations etc, but will test with a good battery soon to see if power supply makes the difference.

Still no luck testing from battery.

One odd behaviour is that it gets better with higher quality audio. With 44khz samples, I can almost make out the music/sounds. With 16khz samples its just pure distortion.

Really not sure where to go on this one, I get similar results from all the code I’ve tried…

Hi there,
Can you post a picture of how it is connected and the exact code you are trying?
Which BSP are you using and could you post the compiler output (first 3 lines & last 10 or so) before the upload.
You’ll need to have verbose mode checked in the prefrences.
HTH
GL :slight_smile: PJ :v:

Well I’ve tried a lot of different code.
I’ve tried:

  1. Input direct from PDM mic. This is working with my Analog Audio library with PWM output, I just switch to I2s and it is a big fail.
  2. Input from SD Card. This is also working with my Analog Audio library with PWM output, I just switch to I2s and it is also a big fail.
  3. Using the radio as an audio source. This is also working with my analog Audio library via PWM output.

To switch to I2S I just un-comment #define USE_I2s in /src/NRF52840/AutoAnalogAudio.cpp

Essentially I can do this with 0 components connected to the XIAO, just an amp, with the XIAO connect to a grove shield etc, so a picture is kind of moot. It is very unlikely to have anything to do with the wiring etc.

I am just using either the MBED or non-MBED core for Arduino via the Arduino IDE v2.3.3. It should work either way.

Exact code I am using: (Just threw this together tonight)


#include <SPI.h>
#include <SD.h>
#include <AutoAnalogAudio.h>

AutoAnalog aaAudio;

/*********************************************************/

/*********************************************************/
uint8_t bigBuffer[16000 * 5];
uint16_t sampleCounter = 0;
bool started = 0;

void setup() {

  Serial.begin(115200);


  Serial.print("Init SD card...");
  if (!SD.begin(4)) {
    Serial.println("init failed!");
    return;
  }
  Serial.println("init ok");
  Serial.println("Analog Audio Begin");

  playAudio("calibrat.wav");
}

void loop() {

  loadBuffer();
}

/*********************************************************/
/* A simple function to handle playing audio files
/*********************************************************/

File myFile;

void playAudio(char *audioFile) {

  if (myFile) {
    myFile.close();
  }
  //Open the designated file
  myFile = SD.open(audioFile);

  //Skip past the WAV header
  myFile.seek(44);

  for (int i = 0; i < (16000 * 5); i++) {
    bigBuffer[i] = myFile.read();
  }
  sampleCounter = 0;
  loadBuffer();

  if (myFile) {
    myFile.close();
  }
}

void loadBuffer() {

  if (sampleCounter < (16000 * 5)) {
    for (int i = 0; i < 32; i++) {
      aaAudio.dacBuffer[i] = bigBuffer[sampleCounter];
      sampleCounter++;
    }

    if (!started) {
      aaAudio.begin(0, 1);  //Setup aaAudio using DAC
      aaAudio.autoAdjust = 0;

      //Setup for audio at 8-bit, 16khz, mono
      aaAudio.dacBitsPerSample = 8;
      aaAudio.setSampleRate(16000);
      started = true;
    }

    aaAudio.feedDAC(0, 32);
  }
}
FQBN: Seeeduino:nrf52:xiaonRF52840Sense
Using board 'xiaonRF52840Sense' from platform in folder: C:\Users\Owner\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.8
Using core 'nRF5' from platform in folder: C:\Users\Owner\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.8
Using library SPI at version 1.0 in folder: C:\Users\Owner\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.8\libraries\SPI 
Using library SD at version 1.2.4 in folder: C:\Users\Owner\AppData\Local\Arduino15\libraries\SD 
Using library AutoAnalogAudio at version 1.50.0 in folder: C:\Users\Owner\OneDrive\Documents\Arduino\libraries\AutoAnalogAudio 
Using library Adafruit TinyUSB Library at version 1.7.0 in folder: C:\Users\Owner\AppData\Local\Arduino15\packages\Seeeduino\hardware\nrf52\1.1.8\libraries\Adafruit_TinyUSB_Arduino 
"C:\\Users\\Owner\\AppData\\Local\\Arduino15\\packages\\Seeeduino\\tools\\arm-none-eabi-gcc\\9-2019q4/bin/arm-none-eabi-size" -A "C:\\Users\\Owner\\AppData\\Local\\Temp\\arduino\\sketches\\1A7D6B52BE3303E647113F8158A60093/XIAO-I2s-Oct2024.ino.elf"
Sketch uses 53048 bytes (6%) of program storage space. Maximum is 811008 bytes.
Global variables use 110408 bytes (46%) of dynamic memory, leaving 127160 bytes for local variables. Maximum is 237568 bytes.
Upgrading target on COM30 with DFU package C:\Users\Owner\AppData\Local\Temp\arduino\sketches\1A7D6B52BE3303E647113F8158A60093\XIAO-I2s-Oct2024.ino.zip. Flow control is disabled, Single bank, Touch disabled
########################################
########################################
########################
Activating new firmware
Device programmed.

Also tested with the following code using the nRF52Audio library with similar non-working results.

#include "Arduino.h"
#include <nRF52Audio.h>

#define PIN_I2S_MCK 14
#define PIN_I2S_BCLK 13 // A4
#define PIN_I2S_LRCK 15 // A5
#define PIN_I2S_DIN 11 // A6
#define PIN_I2S_SD  5 // 27
#define PIN_SPI_CS  4
#define PIN_SCL 16
#define PIN_SDA 15

//Plays a single wave file until it ends
void PlayWavFile()
{
	//Wav files to play. Change "22CANT1.WAV" to match the name of
	//whatever file you are playing.
	SDWavFile* lpWavFile1 = new SDWavFile("M16b24kS.WAV");

	//Create a new I2S Player
	I2SWavPlayer* lpPlayer = new I2SWavPlayer(PIN_I2S_MCK,
			  	  	  	  	  	  	  	  	  PIN_I2S_BCLK,
											  PIN_I2S_LRCK,
											  PIN_I2S_DIN,
											  PIN_I2S_SD);

	lpPlayer->Init();                      //Initializes I2S playback hardware

	lpPlayer->Configure_I2S_Speed(ee2205); //Set I2S clock speed (sample rate of the file)
	                                       //ee2205 = 22.05KHz
	                                       //ee4410 = 44.1KHz

	lpPlayer->SetWavFile(lpWavFile1);      //Set file object to play
	lpPlayer->SetVolume(0.5);              //set master volume, 0.0 (mute) to 1.0 (full volume)
	lpPlayer->StartPlayback();             //Begin playing the wave file

	Serial.println("Playback started.");

	//Keep playing as long as playback hasn't ended
	while(false == lpPlayer->ContinuePlayback())
	{
		//Wait for playback to end
	}

	//Cleanup code
	Serial.println("Playback ended.");
	lpPlayer->StopPlayback();
	lpWavFile1->Close();

}

//The setup function is called once at startup of the sketch
void setup()
{
	delay(1000);
	Serial.begin(115200);

	//Initialize SD card. Make sure to do this before creating any SDWavFile objects or
	//trying to play anything or we won't be able to read the data from the SD card

}

// The loop function is called in an endless loop
void loop()
{
  Serial.println("ok");
  delay(1000);
  
  if(Serial.available()){
    char c = Serial.read();
    if(c == 'p'){
	if(!SD.begin(8000000, PIN_SPI_CS))
	{
		Serial.println("SD init failed.");
		//return; //Punt. We can't work without SD card
	}
	Serial.println("SD init completed.");

	PlayWavFile();
    }
  }


}

Here is an example just using the PDM microphone:

#include <AutoAnalogAudio.h>

AutoAnalog aaAudio;


void setup() {
  Serial.begin(115200);
  aaAudio.begin(1, 1);  //Setup aaAudio using DAC
  aaAudio.autoAdjust = 0;
  aaAudio.adcBitsPerSample = 16;
  aaAudio.dacBitsPerSample = 16;
  aaAudio.setSampleRate(16000);
}

void loop() {
  
  aaAudio.getADC(32);

  for(int i=0; i<32; i++){
    aaAudio.dacBuffer16[i] = aaAudio.adcBuffer16[i];
  }
  
  aaAudio.feedDAC(0,32);
}

Hi there,
LOL, If I had a dollar for the many times I’ve heard that statement. OK we’ll go with your web cam is broken. :face_with_peeking_eye:
The code posted compiles with mbed 2.9.2
I see your example is using the Non-Mbed 1.1.8 so given that and only that! I would say have you tried rolling it back to the BSP that was used in the original example your using? I would try 1.1.1 in Non-Mbed and the 2.8.1 of Mbed BSP’s and work forward, You should pick a code set and troubleshoot it , as you see the Throw everything you know at once at it, isn’t the way to go and yield the results your currently getting.

Start with the pin Numbers. Use the GPIO pin names. Are you using the dev board or bare chip and amp ? which? Stay with the I2S pins and code leave the PWM stuff alone that’s not what your wanting, correct?

Take a look at this video… The I2S portion will show you some stuff.

he walks through the code which will give you a better understanding of the tasks execution.

HTH
GL :slight_smile: PJ :v:
try that stuff and or wiring and let us know.
:+1:

Can you attach a zip file with the WAV file or Audio you are attempting to play ?

Hi TMRh20Projects,

nRF52_SimpleWavPlayer.ino from the nRF52Audio library works fine with the MAX98357A breakout board. BSP is mbed 2.9.2 or non-mbed 1.1.8.
Please define the pins as follows.

//                                          XIAO pin --> MAX98357A pin
#define PIN_I2S_MCK   2   // D0 P0_02  2      D0           not use
#define PIN_I2S_BCLK  3   // D1 P0_03  3      D1           BCLK
#define PIN_I2S_LRCK  29  // D3 P0_29  29     D3           LRC
#define PIN_I2S_DIN   4   // D4 P0_04  4      D4           DIN
#define PIN_I2S_SD    5   // D5 P0_05  5      D5           SD

EDIT:
It certainly seems to distort in the louder parts of the sound as you describe.

1 Like

@msfujino After all that time it now works! Could have sworn I tried these pins before, but now I even got it working with my own library via SD playback as well as microphone!

I knew it had to be something simple, but wow! Thanks for the help folks!

1 Like

Cha_ching…Glad you got it going a picture is always worth a thousand words. :v:
GL :slight_smile: PJ :+1:

Hehe, I knew the ‘I told you so’ was coming as soon as I got it working. Thx again for the help!

I found at least a part of my problem was using too small of buffer sizes. This just added to the distortion:
I didn’t realize, but with I2S I had to increase my default buffer size to 6400 (12800 bytes in 16-bit mode) from 1600 (3200 bytes in 16-bit mode) in order to get things working properly.
It also seems that 24kHz samples @ 16-bits is about the maximum the system can handle when reading from SD card (Standard SD lib & FAT16).

In the end I’m choosing to keep PWM as the default output method since it requires only small buffer sizes, and is good enough for voice over radio. I2S can be toggled on/off by advanced users by adding to the begin(0,1,1); method with the third item specifying whether I2S output is enabled or not. This will allow extension of I2S to include microphones as well eventually by specifying a 2 or 3 for the 3rd option.

My AutoAnalogAudio lib now supports 8 or 16-bit samples @ anywhere from 16khz, 24khz, 32khz or 44khz with I2S! As usual, I published the code I have, but won’t do a release until its ready so peeps need to install from ZIP to try I2S functionality. I will probably put together a couple examples for the 52840 also!

1 Like

Hi there,
Nice Work LOL :grin:, thank you for contributing too… :+1:
We all benefit by it.
GL :slight_smile: PJ :v:

1 Like

Hi TMRh20Projects,

I downloaded and installed “AutoAnalogAudio v1.51.0” from Github and tried “NRF52_SD_Playback_Auto.ino".

I have XIAO nRF52840 connected to MAX98357A breakout board and am trying to playback the attached wav file.
When use volumeControl = 0.2; all I get is a loud noise.
If I use ‘=’ and increase the volume to 0.8, the sound comes out normally. Then, once the sound comes out normally, using ‘-’ to lower the volume will also produce normal sound.
Is there a solution?

WavFile.zip (2.0 MB)

1 Like

Yes!
I messed up on the volume control in that sketch, per nRF52: Fix volume control in example · TMRh20/AutoAnalogAudio@1717717 · GitHub

At the very bottom of the sketch, change the following:

        aaAudio.dacBuffer16[i] += 0x8000;
        aaAudio.dacBuffer16[i] *= volumeControl;
        aaAudio.dacBuffer16[i] -= 0x8000;

to

        int16_t sample = aaAudio.dacBuffer16[i];
        sample *= volumeControl;
        aaAudio.dacBuffer16[i] = (uint16_t)sample;

There are a couple other quirks I’ve worked out since the latest release, you can see here Comparing v1.51.0...master · TMRh20/AutoAnalogAudio · GitHub
Installing from ZIP provides the latest changes.

Edit to add:
I noticed your wav file seems to be 16-bit, 16khz, Stereo, which is probably a little too high quality for reading via SD card per my tests. 16-bit, 16kHz, mono seems to give the best results for me. You would think the XIAO would have faster SPI speeds, but SD card reading doesn’t seem to be the fastest.

1 Like

The latest ZIP file is installed. I can now play it without problem. Thank you very much.
I also tried 16bit 22kHz stereo, checking with a digital oscilloscope to see if there is enough room to read data from SD, but it seems to be in time. The BSP used is non-mbed and the SPI clock is 32 MHz (waveform confirmed).

Aha, thank you for testing! The non-mbed BSP seems to be much faster! Did not realize that. This allows for much higher quality audio which is very nice!

If you use mbed for BSP, the SPI clock is only 8MHz even if you set SD.begin(SD_CS, 32000000).
Please also refer to the following link.

Good info. Too bad tho, I am liking the standard Arduino BLE library which only seems to work with the mbed core. Will have to play around more with this stuff and see what is possible!

Hi there,
Lurking hard on this thread, but I’m pretty sure you can make the jump to the blufruit BLE or even NimBLE and get the same or better features. You could consider the NRF_SDK also that is PHAT when it comes to everything. The learning your way around is a small curve but it has a lot to offer also.
@msfujino has the receipts when it comes to power and the cores who’s what! a.k.a. the PowerNinja :ninja: I saved a major improvement and extended battery life (a day to weeks) switching. I was a PITCA but worth every minute (PainInTheChairA$$)

Meanwhile I grabbed a stereo version DAC/amp and will be testing with this.
# CJMCU-1334 DAC Module
CJMCU-1334 UDA1334A I2S DAC Audio Stereo Decoder Module Board for 3.3V - 5V

Hat tip to both of you. :bowing_man:

GL :slight_smile: PJ :v: