I2S wav playback on NRF52840

I've just recieved an XIAO BLE Sense board based on the nrf52840 and am trying to output some audio using I2S. I am close, as I can make out the words spoken into a recording, but they are all distorted and I can barely make them out. I have tried adjusting everything, using 8-bit audio, 16-bit audio, but its all distorted and crappy. I must be missing something simple or else this board is not working correctly. Here is some sample code that reproduces the issue. I have a file on SD card called 'calibrat.wav' and it is 8-bit mono. I also have 16-bit samples that I've tested with 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);
  }
}

If anyone has insight on this that would be fantastic!

Hi,
Did you ever manage to solve the issue with the sound? I have the same board and want to do the same.
Right now I can't try your code as I don't have an SD card module to read from, I'm waiting for the expansion board with which this should be possible.

The main lead I have found so far is this thread from the seeedstudio forum:

Unfortunately, the code from the repo of aovestipaperino doesn't work for me. I don't yet know why, and I'm waiting for a response from him. In fact, testing the code bricks my Xiao, and figuring out how to unbrick it took some time although it's very easy (description is in the git issue, or I'll write you if the same thing happens to you if you test it). His code uses the mbed-enabled core, and for some reason it's screwing up the serial communication of my board.

Do you also use the mbed-enabled core? I would be most interested in the non-mbed core, but getting it to work with the mbed core would be a good start.

I would be most delighted if we figure this out :slight_smile: .

cheers,

No such luck. I bought a second board, the Feather 82450 and get the same results. I managed to get the XIAO recording audio via PDM, but no such luck outputting to I2S.

Also using the MBED enabled core.

Ok, so it seems like there is still no solution, or at least I don't know of any. It's a pity because I really like the XIAO, but if I can't get this to work, I probably have to switch to something else. I'll give it a bit more time and share my findings if I get something to work, ideally with the non-mbed core.

Did you record through the XIAO expansion board? I've seen some examples, but currently waiting for the expansion board.

Yeah I've tried everything I can think of. Even a simple sine wav comes out sounding more like a square wave. Also tested with non-mbed core.

I recorded using my own library AAAudio directly from the onboard PDM microphone and broadcast over radio link to an Arduino Due, so I know I have a good audio input for I2S, but it always comes out distorted.

You are further than me though, I have not achieved any audio output at all. I'm using a DAC/amp board with a speaker and an audio snippet directly defined in the sketch, but for some reason no sound is produced. I couldn't even make it work yet with an I2S testscript on a Pi, so I'm still quite deep in the woods here :thinking:.

Have a look at the link to the seeed forum I sent. The post there describes something similar with the distorted output, and he found the solution through proper pin assignment. I would be curious if this would work also for you, from the post it sounds like he managed to make it work.

Yes I saw that and tested it quickly. I will investigate pin assignments more closely and report back here if I have any info.

Hi,
Some progress on my side.
A friend helped me and we managed to get some distorted sound, but had to change some parameters in your code.
I'm using the Adafruit MAX98357, and there is one mode that requires a BCLK of 256 kHz and an LRCLK of 8 kHz. Your code did not produce this, but changing SWIDTH from 8BIT to 16BIT helped.

Which leads me to the distortion itself. My current best guess is that data formats are not consistent. I convert audio samples to 8k 16bit, but then the smallBuffer has 32 bits, which I believe causes some problems. Your original code was set to 8BIT, but also with a 32 bit smallBuffer, so that could be the same issue?

I'm trying to make everything consistent and report back if I managed something.

How did you run your code with the non-mbed core? Would you have that sketch still available?

Could have sworn I tested it with the non-mbed core, but be damned if I can find the code for it... If I remember its very similar to whats in my AAAudio library, but you need to comment out all the PDM stuff. In any case I think the I2S interface is either broken or it expects a special format or something. I also tried using the I2S interface as input to the I2S output, and got same results, so its not a timing issue, because I2s uses same clock etc for input/output.

Also, as long as the data is packed correctly for the set format, the size of the buffers isn't that important.

Hi,
I decided to give this another go, and I'm starting to understand the topic a bit better. I managed to produce a clean sine wave tone at my desired frequency out of the speaker.

You wrote that you have an 8-bit mono audio file. At what rate is it sampled? Your configuration of 32MDIV63 (0.5 MHz) and RATIO_64X results in LRCK of 8 kHz, so I'm assuming your audio file is also sampled with 8 kHz.

SWIDTH_8BIT should be the correct setting for you. At the moment I think the sample output should be signed, not sure if it works correctly with unsigned values. Did you check this?

Now, you set CHANNELS_LEFT, and this is the part I still need to understand better. In my setup, I have a DAC/amp that takes the I2S input. It itself can be configured to have mono or stereo output. The default is stereo, so it sends (L+R)/2 to the speaker after each completed LRCLK cycle. So if I use CHANNELS_Stereo in the settings, each value of the sine wave must be repeated, otherwise the speaker output is not what it should be. Could this be an issue in your setup? I mean if you send mono data, but your speaker setup interprets it as stereo and mixes adjacent audio values, on paper it could result in distorted output you describe. This should depend on the sampling rate, so I would expect the distortion to be more prominent with low sampling rate such as 8 Khz.

Btw, it seems to me that not all pins on the xiao work for this. Currently, I'm using pins 7, 8 and 9 like this:
#define PIN_SCK 45 //8
#define PIN_LRCK 46 //9
#define PIN_SDOUT 44 //7

Let me know if any of this is helpful. I'll continue, hopefully I'll soon manage to properly convert an audio file and play it.

I successfully managed to play clean sound with my setup!
My settings are a 16-bit signed audio file that I converted from .wav to a CSV file with python's wave module. Before I was using an EncodeAudio.exe converter, but it's output seems more like unsigned 8-bit, but with my own conversion I'm sure about the format. The reading of your file from SD card looks good also, so that should work as well.
My LRCK is also 8 kHz, but I set SWIDTH_16BIT, and CHANNELS_LEFT. Maybe your way of buffering needs MAXCNT to be set differently, but for me I just left it at sizeof(sine_table) / sizeof(uint32_t);.
I set my DAC/amp to mono left output, and voilà, my speaker is happily greeting me with continuous "hello" :stuck_out_tongue:

Wow, nice work! Any chance of posting some sample code?

#include <nrf.h>

#define PIN_MCK    (13)
#define PIN_SCK    45 //8
#define PIN_LRCK   46 //9
#define PIN_SDOUT  44 //7

void setup() {
  //Serial.begin(115200);
  //while (!Serial) { delay(10); }
  
  // Sine table
  //int16_t sine_table[] = {0, 0, 23170, 23170, 32767, 32767, 23170, 23170, 0, 0, -23170, -23170, -32768, -32768, -23170, -23170}; //stereo, produces a 5680 Hz tone with MCK 2.9 MHz and LRCK = MCK/64 = 45 kHz
  //int16_t sine_table[] = {0, 0, 11585, 11585, 23170, 23170, 27967, 27967, 32767, 32767, 27967, 27967, 23170, 23170, 11585, 11585, 0, 0, -11585, -11585, -23170, -23170, -27967, -27967, -32768, -32768, -27967, -27967, -23170, -23170, -11585, -11585}; //stereo, produces a 440 Hz tone with MCK 0.5 MHz and LRCK = MCK/64 = 8 kHz
  int16_t sine_table[] = {0, 11585, 23170, 27967, 32767, 27967, 23170, 11585, 0, -11585, -23170, -27967, -32768, -27967, -23170, -11585}; //mono left, produces a 440 Hz tone with MCK 0.5 MHz and LRCK = MCK/64 = 8 kHz
  
  // 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);
  
  // Master clock frequency
  //NRF_I2S->CONFIG.MCKFREQ = I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV11  << I2S_CONFIG_MCKFREQ_MCKFREQ_Pos; //2.9090909 MHz 
  NRF_I2S->CONFIG.MCKFREQ = I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV63 << I2S_CONFIG_MCKFREQ_MCKFREQ_Pos; //0.5076365 MHz
  
  // LRCK Ratio
  NRF_I2S->CONFIG.RATIO = I2S_CONFIG_RATIO_RATIO_64X << I2S_CONFIG_RATIO_RATIO_Pos; //LRCK = MCK / 64 = 45.45 kHz or 8 kHz
    
  // 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_16BIT << I2S_CONFIG_SWIDTH_SWIDTH_Pos; //this defines BCLK as LRCLK * 32
  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_Stereo << I2S_CONFIG_CHANNELS_CHANNELS_Pos;
  // Use mono left
  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)&sine_table[0];
  NRF_I2S->RXTXD.MAXCNT = sizeof(sine_table) / sizeof(uint32_t);
  
  // Start transmitting I2S data
  NRF_I2S->TASKS_START = 1;
  
  
  // Since we are not updating the TXD pointer, the sine wave will play over and over again.
  // The TXD pointer can be updated after the EVENTS_TXPTRUPD arrives.
  while (1)
  {
    __WFE();
  }
}

void loop() {
  // put your main code here, to run repeatedly:

}

This produces a clean sine wave for me. You'll need to change the values in the sine_table and I2S settings to behave correctly for your 8 bit wav.

Now I would like to run this on the non-mbed core. You were mentioning your AAAudio library, is that the place I should start looking into? I'm not yet sure how to go about this :stuck_out_tongue:

Thanks for that!

I'm still not having much luck, I had it working prior with a sine wave similar to this, but can't seem to get real audio working. Anyway it really helps to know it functions.

I probably won't be much help regarding the non-mbed core. :frowning:

Did you run this exact code on the same pins? Sad to hear that it doesn't work for you... I spent some time with a logic analyzer to understand better how the signals need to look like, together with what the DAC/amp requires, and settled on these parameters.

Ok. Still, if something regarding the non-mbed core comes to your mind, please let me know, I appreciate all sorts of hints :slight_smile:.

So after all this time I took a look at PWM and managed to get audio playing that way lol. Just replying to keep the thread open in case anybody has further info on what is going on with I2S on the 52840. I can manage to get what seems to be a sine wave going, but not audio.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.