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.
0 Comments