The starting point for writing audio data from USB to I2S using a Rasperry Pico (RP2040) Microcontroller is the speaker example sketch from my extended TinyUSB library.

We would just do an i2s.write(data, len) in the callback: Unfortunately this did not work and this was locking up the output quite quickly.

In the next trial, I was writing the data to a queue and do the i2s output in the loop(): still no success.

The first success started when I moved the output to the second core by doing the i2s output in loop1() and making sure that the log level was set to Warning!

But now I was running into the next problem: the audio was breaking up badly since the usb and i2s clocks were not matching and we were running into underflows. The built in automatic feed-back logic from TinyUSB did not work as expected.

The final solution relies on switching off the automatic feedback and to use my own custom feedback logic which slows down the data sending when the buffer is starting to be filled and speeds it up when the queue is running low:

#include "Adafruit_TinyUSB.h"
#include "AudioTools.h"
#include "AudioTools/Concurrency/RP2040.h"

AudioInfo info(44100, 2, 16);
Adafruit_USBD_Audio usb;
BufferRP2040 buffer(256, 20); 
QueueStream queue(buffer);
I2SStream i2s;
StreamCopy copier(i2s, queue);

size_t writeCB(const uint8_t* data, size_t len, Adafruit_USBD_Audio& ref) {
  usb.setFeedbackPercent(buffer.size()*100 / buffer.available());
  return queue.write(data, len);
}

void setup() {  
  Serial.begin(115200);
  AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);
  while(!Serial);  // wait for serial
  Serial.println("starting...");
  // start queue
  queue.begin(80);

  // Start USB device as Audio Sink w/o automatic feedback
  usb.setFeedbackMethod(AUDIO_FEEDBACK_METHOD_DISABLED);
  usb.setWriteCallback(writeCB);
  if (!usb.begin(info.sample_rate, info.channels, info.bits_per_sample)){
    Serial.println("USB error");
  }

  // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration
  if (TinyUSBDevice.mounted()) {
    TinyUSBDevice.detach();
    delay(10);
    TinyUSBDevice.attach();
  }
}

void loop() {
  // just for fun: we blink the LED
  usb.updateLED();
}

void setup1(){
  //start i2s
  auto cfg = i2s.defaultConfig(TX_MODE);
  cfg.copyFrom(info);
  cfg.buffer_size = 256;
  cfg.buffer_count = 3;
  if (!i2s.begin(cfg)){
    Serial.print("i2s error");
  }
}

void loop1() {
  copier.copy();
}

Of cause we need to connect the RP2040 to an external I2S DAC: I was using an MAX98357A.

The actual version of this sketch can be found in the examples folder.

Instructions how to install the library can be found in the Wiki of the project.


6 Comments

tt · 13. January 2026 at 17:27

Thank you for your great libraries & examples.
I needed a few tweaks to make it work on my environment (macOS).
For someone who may have the same issue,
– queue.begin(80) → queue.begin() OR add buffer.resize(256 * 20)
– usb.setFeedbackPercent(min(100, buffer.size()*100 / buffer.available()))

Optionally,
– check the TinyUSB Audio volume & mute states on macOS’s Audio & MIDI Setup app
– cfg.pin_bck = 10; cfg.pin_ws = 11; cfg.pin_data = 9; for Pimoroni Pico Audio Pack (DAC)
– copier.copy() → if (queue.available()) copier.copy(); else delay(10); as macOS suspends USB audio streams when inactive.

Performance-wise, I could add one filter on RP2040 if the sample rate is at 22.05kHz but not at 44.1kHz.
Aruduino IDE v2.3.7
arduino audio tools v1.2.1
macOS v15.7.3

tt · 13. January 2026 at 11:59

Thank you for your library and great examples.
I needed a few tweaks to run it successfully on my environment.
Below for other readers who may have the same problem…

1) Change `usb.setFeedbackPercent(buffer.size()*100 / buffer.available())` to `usb.setFeedbackPercent(min(100, buffer.size()*100 / buffer.available()))`
2) Change `queue.begin(80)` to `queue.begin()` or add `buffer.resize(256 * 20)` before.

– Arduino IDE: v2.3.7 on macOS v15.7.3
– audio tools: v1.2.1 (October 2025 release)
– TinyUSB: latest (commit 74a903a) from pschatzmann/Adafruit_TinyUSB_Arduino

Extra tips:
3) If you are using a Pico Audio DAC board from Pimoroni, `cfg.pin_bck = 10; cfg.pin_ws = 11; cfg.pin_data = 9;`
4) If you are using macOS, optionally change `loop1()` to `if (queue.available()) {copier.copy();} else {delay(10);}`
5) If you are using macOS, verify the volume & mute settings for TinyUSB Audio on Audio MIDI Setup.app

Hailun · 12. January 2025 at 4:11

Highly knowledgeable and brilliant mind!
Hello, pschatzmann ! I’d like to ask you a question: CS42448 CS43l22 Included in the d arduino-audio-driver , I need to use CS4272 to RP2040 or Esp32(Maybe the esp32 would be better), Do you think the esp32 can drive the CS4272?(Because CS4272 sounds better!)I very much hope it will happen!I hope I can get your advice!Have a good life!

    pschatzmann · 12. January 2025 at 9:54

    I am not supporting USB Audio on any ESP32.
    The challenge however will be to implement a driver for this chip. I did not find any i2s module which would support this chip.

Deladriere · 10. January 2025 at 14:54

Woaw ! nice start ! can we dream of text to speech ?

    pschatzmann · 10. January 2025 at 15:36

    I don’t see any reason why not: just use one of my many TTS libraries…

Leave a Reply

Avatar placeholder

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