Pico 2 W running the Audio Library

Just tinkering around with stuff, and found how to enable and read the CPU cycle counter on the Pico 2:
C++:
    // To enable:
    m33_hw->dwt_ctrl |= M33_DWT_CTRL_CYCCNTENA_BITS;
    m33_hw->demcr    |= M33_DEMCR_TRCENA_BITS;

    // To read:
    uint32_t now = m33_hw->dwt_cyccnt;
That should allow you, with judicious use of macros, to get an idea of the CPU load in the ported audio library, same as you can on Teensy. Probably just need to define ARM_DWT_CYCCNT ... haven't got that far yet.

Doesn't seem to need any extra #includes, though I had to scrobble through the source to find the items that are actually relevant - there appears to be a lot of cruft for a BSP that's supposed to be Pi-Pico-specific!
 
No worries.

Yes, noticed that you’d been hard at work when I synced my fork up the other day. I did a bit of work aligning the cores files with the Teensy ones, to try to see what changes are essential for the Pico. It looks as if your IDE might be quite keen on “fixing” the layout so braces end up in a different place, which makes it look as if there are more changes than is actually the case :confused:
 
Ah I'm using the Arduino IDE only but I run the Auto Format (Cmd+T) sometimes and I might have done that here.
I will try to come back on those files make sure they keep the original formatting so it's easier to diff.
I do very little modifications really, and maybe there are that I did initially that could be undone because I might not have understood some things.

On another note, I tried pushing Pico to its limit, with the PolySynth example I increased the voices and increased the release time in the voice object to 4 seconds. When playing "normally" it works fine with up to 32 voices. But it starts to be a problem when sending 29 NoteOn together, I think the audio processing blocks everything else so the MIDI does not receive the NoteOff after that and in some cases I even don't get audio anymore unless I restart the Pico. (I did increase the audio memory to 200)

Maybe that's where the limit is, if that's the case it's still good as this test means it can handle 56 audio objects actives at the same time and it can handle 64 objects even in a more normal use (each voice has an envelope and oscillator).
 
That sounds about right. On my quick test sketch with just two AudioWaveformSine objects I got just over 7% CPU usage, and a bit less when I switched to AudioSynthWaveform.

I was thinking of trying to move the audio processing to the second CPU, leaving just the I2S and MIDI code on the first one. It won’t give much extra capacity, but should mean the whole thing doesn’t need rebooting if you overwhelm it.
 
I've done a PR, though you might want to cherry-pick the useful bits as it has conflicts.
  • aligned better with Teensy original to make diffing easier
  • macros added to AudioStream.h to emulate Teensyduino + T4.x - reduces requirement for code changes, I hope
  • CPU load monitoring enabled
  • I²S transmission much more efficient, at the cost of a 2kB temporary buffer
  • deleted the ARM maths library C source, it's not needed, just the headers
I have had some success getting Core 1 running the audio, though it's hard to gauge the effectiveness. In a separate core1.ino tab, put something like
C++:
#include <pico-audio.h>

void setup1(void)
{
  AudioMemory(20); // so we get the cycle counter started
  delay(3000);
  Serial.print("PCM5102...");
  PCM5102.begin();
  Serial.println("started");
}


void loop1()
{
  asm("wfi");
}
(The delay and prints are obviously optional!)

The setup1() ensures its cycle counter is started and that it "owns" the various audio-related interrupts, and will thus run the update code. There is of course no CPU synchronization done here, so Core 0 could make changes to the audio objects while the update is running on Core 1; something should probably be done about that :)
 
Thanks, I'm looking at the PR, but it looks a little odd because some of the changes that it claims the PR is doing are actually already in the repo. For example in https://github.com/ghostintranslation/pico-audio/blob/main/AudioOutputI2S.h line 30 and 32, or in https://github.com/ghostintranslation/pico-audio/blob/main/effect_envelope.h line 31 .

I think it might be because you didn't sync first (it shows you are a few commits ahead but also behind on your repo).

See this post about how to sync with the original repo of a fork: https://stackoverflow.com/a/3903835 I haven't done a fork in a while so I'm only guessing here.

Other than these few odd changes, the improvements look great.

Regarding your dual core example, is PCM5102 supposed to be the AudioOutputI2S object instantiated somewhere? What do you mean by "In a separate core1.ino tab" , in a project we can only have 1 .ino file no? I thought that for the dual core we would just have a setup() and a loop() and a setup1() and loop1() in the same .ino file?
And while I'm asking about dual core, is it ok to instantiate objects globally (so outside a setup() or setup1() ) and then using them in one of the setup and loop would make the corresponding core actually processing them? Especially where we call AudioMemory?
 
Gah, I thought I'd got that right. I always try to keep all the upstream branches in sync, and any PRs are at least aligned with the main one, but clearly not so much this time. I've done a sync and merge, and it looks much better now :)

I'll post a bit later with my "working example" for using both cores - need to tidy it up.
 
OK, here we go...

The Arduino IDE can indeed cope with multiple files in a sketch. It has some weird algorithm for simply concatenating them into one big source file and compiling that, but it does make it fractionally easier to develop and re-use code. Here's what my example looks like, with the "New Tab" menu open and ready to create one.
1742290319470.png


So, the tab contents are:
C++:
#include <Adafruit_TinyUSB.h>
#include <pico-audio.h>

// Audio Processing Nodes
AudioSynthWaveform              wav1; //xy=650,165
AudioSynthWaveform              wav1b; //xy=650,210
AudioSynthWaveform              wav2; //xy=645,350
AudioSynthWaveform              wav2b; //xy=645,395
AudioMixer4                     mixerR; //xy=810,210
AudioMixer4                     mixerL; //xy=815,370
AudioOutputI2S  PCM5102; //xy=961,273 // hand-edited!

// Audio Connections (all connections (aka wires or links))
AudioConnection        patchCord1(wav1, 0, mixerR, 0);
AudioConnection        patchCord2(wav1b, 0, mixerR, 1);
AudioConnection        patchCord3(wav2, 0, mixerL, 0);
AudioConnection        patchCord4(wav2b, 0, mixerL, 1);
AudioConnection        patchCord5(mixerR, 0, PCM5102, 0);
AudioConnection        patchCord6(mixerL, 0, PCM5102, 1);

// Control Nodes (all control nodes (no inputs or outputs))

// TeensyAudioDesign: end automatically generated code


// PCM5102 connections:
//        Teensy 4    T3    Pico 2
//       I2S1  I2S2
// BCK    21     4     9    20
// DIN     7     2    22    22
// LCK    20     3    23    21

void setup() {
  delay(2000);
  Serial.println("\n\nRunning"); 
  pinMode(LED_BUILTIN,OUTPUT); 

  wav1.begin(0.9,100.0,WAVEFORM_SINE);
  wav2.begin(0.9, 50.05,WAVEFORM_SINE);
  wav2.phase(90);
 
  wav1b.begin(0.05,1001.1,WAVEFORM_SINE);
  wav2b.begin(0.06,1001.1,WAVEFORM_SINE);
}


void printStuff(void)
{
  Serial.printf("Usage: CPU %.2f%%; wav1 %.2f%%; I²S %.2f%%\n",
                AudioProcessorUsage(),
                wav1.processorUsage(),
                PCM5102.processorUsage());
  Serial.flush();
}

bool led;
uint32_t togTime;

void loop()
{
  if (millis() >= togTime)
  {
    togTime = millis() + 250;
    digitalWrite(LED_BUILTIN,led);
    led = !led;

    printStuff();
  }
}

and
C++:
#include <pico-audio.h>

void setup1(void)
{
  AudioMemory(20); // so we get the cycle counter started
  delay(2500); // after Core0's message
  Serial.print("PCM5102...");
  PCM5102.begin(); // updates start only after this runs
  Serial.println("started");
}


void loop1()
{
  asm("wfi");
}

Because everything is concatenated into one big file, the global variables are accessible to both cores, as you say. Since it's Core 1 that calls AudioMemory() (which enables its cycle counter) and PCM5102.begin() (which creates the I²S and audio update interrupts), it will run the audio engine, and its cycle counts will be used for CPU usage metrics. Core 0 is just reading these and doing the output, which doesn't load Core 1.

As written this is a great example of terrible practice! There's absolutely no effort made to prevent "simultaneous" access to global variables by both cores; for example, if there was an envelope object, and Core 0 called noteOff() to change its state exactly when Core 1 was making a transition from decay to sustain, it's anyone's guess as to which would "win". Dealing with that is beyond the scope of this simple demo, and definitely application-dependent...
 
I should have tested before merging, I'm getting an error.

First actually in AudioStream.h we need to do this:
Code:
#if defined(ARDUINO_RASPBERRY_PI_PICO_2) || defined(ARDUINO_RASPBERRY_PI_PICO_2W)

After that the error I get is:
Code:
AudioStream.h:47:27: error: 'void interrupts()' was declared 'extern' and later 'static' [-fpermissive]

And same for noInterrupts(). But I'm not quite sure why it sees it already declared, if I comment them then it says they are undefined.
 
Good point about ARDUINO_RASPBERRY_PI_PICO_2W, I'd just looked at my verbose compiler output to find the define (I expect it's documented ... somewhere), but forgotten about the 2W.

I don't get that error, for some reason. [no]interrupts() are declared in Arduino.h, and defined in wiring_private.cpp. They're not declared in anything we're working on, quite rightly; I haven't done so, just defined a macro to replace Teensy-land __[dis/en]able_irq() with the corresponding Pico-land equivalents. Does the compilation state where the static declaration is?
 
Thinking further about these, for the dual-core option disabling interrupts is completely hopeless, because they only get disabled on the calling core, not the one running the audio engine. If you look at the envelope, it's trying to ensure its internal state is fully consistent by disabling interrupts to lock out the audio update during noteOn() and noteOff() calls. This works fine on a single-core system, but provides no protection for a dual-core one.
 
Good point about ARDUINO_RASPBERRY_PI_PICO_2W, I'd just looked at my verbose compiler output to find the define (I expect it's documented ... somewhere), but forgotten about the 2W.

I don't get that error, for some reason. [no]interrupts() are declared in Arduino.h, and defined in wiring_private.cpp. They're not declared in anything we're working on, quite rightly; I haven't done so, just defined a macro to replace Teensy-land __[dis/en]able_irq() with the corresponding Pico-land equivalents. Does the compilation state where the static declaration is?

Yes actually it says:
Code:
Library/Arduino15/packages/rp2040/hardware/rp2040/4.3.1/cores/rp2040/Arduino.h:58:6: note: previous declaration of 'void interrupts()'

So that's where it is declared first yes, I don't know why it considers the macro as a redeclaration...
 
All I have is this:

Code:
In file included from /Users/xxx/Documents/Arduino/libraries/pico-audio/pico-audio.h:3,
                 from /private/var/folders/2q/_7wknds1183db0mj84jg13500000gn/T/.arduinoIDE-unsaved2025220-85016-1iiu3oj.d0wh/PolySynth/PolySynth.ino:15:
/Users/xxx/Documents/Arduino/libraries/pico-audio/AudioStream.h:47:30: error: 'void interrupts()' was declared 'extern' and later 'static' [-fpermissive]
   47 | #define __enable_irq(...)    interrupts()
      |                              ^~~~~~~~~~
In file included from /Users/xxx/Library/Caches/arduino/sketches/35D67EA7F893FBFF90A2CE5B324DE452/sketch/PolySynth.ino.cpp:1:
/Users/xxx/Library/Arduino15/packages/rp2040/hardware/rp2040/4.5.1/cores/rp2040/Arduino.h:58:6: note: previous declaration of 'void interrupts()'
   58 | void interrupts();
      |      ^~~~~~~~~~
/Users/xxx/Documents/Arduino/libraries/pico-audio/AudioStream.h:46:28: error: 'void noInterrupts()' was declared 'extern' and later 'static' [-fpermissive]
   46 | #define __disable_irq(...) noInterrupts()
      |                            ^~~~~~~~~~~~
/Users/xxx/Library/Arduino15/packages/rp2040/hardware/rp2040/4.5.1/cores/rp2040/Arduino.h:59:6: note: previous declaration of 'void noInterrupts()'
   59 | void noInterrupts();
      |      ^~~~~~~~~~~~
 
That looks as if you don’t have verbose compilation turned on:
1742541724689.jpeg

(image copied from interweb, some parts may be irrelevant)
 
I’m going to be AFK for a couple of days … but a thought occurred. Can you remove my macros, and instead put in proper functions for __enable_irq() and __disable_irq(), which just call the Pico ones? I’d expect the optimiser to inline them automatically, but either way it shouldn’t matter for a quick test.
 
Back
Top