Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion AudioConfigRP2040.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,51 @@
#error This header should be included for RP2040, only
#endif


// AUDIO output modes
#define PWM_VIA_BARE_CHIP 1 // output using one of the gpio of the board
#define EXTERNAL_DAC_VIA_I2S 2 // output via external DAC connected to I2S (PT8211 or similar)

//******* BEGIN: These are the defines you may want to change. Best not to touch anything outside this range. ************/
#define RP2040_AUDIO_OUT_MODE PWM_VIA_BARE_CHIP
//******* END: These are the defines you may want to change. Best not to touch anything outside this range. ************/


#if (RP2040_AUDIO_OUT_MODE == PWM_VIA_BARE_CHIP)
#define AUDIO_CHANNEL_1_PIN 0
#if (AUDIO_CHANNELS > 1)
// Audio channel pins for stereo or HIFI must be on the same PWM slice (which is the case for the pairs (0,1), (2,3), (4,5), etc.
# define AUDIO_CHANNEL_2_PIN 1
#define AUDIO_CHANNEL_2_PIN 1
#endif

// The more audio bits you use, the slower the carrier frequency of the PWM signal. 11 bits yields ~ 60kHz on a 133Mhz CPU (which appears to be a reasonable compromise)
#define AUDIO_BITS 11
#endif

#if (RP2040_AUDIO_OUT_MODE == EXTERNAL_DAC_VIA_I2S)
// ****** BEGIN: These are define you may want to change. Best not to touch anything outside this range. ************/
#define BCLK_PIN 20
#define WS_PIN (pBCLK+1) // CANNOT BE CHANGED, HAS TO BE NEXT TO pBCLK
#define DOUT_PIN 22
#define LSBJ_FORMAT true // some DAC, like the PT8211, use a variant of I2S data format called LSBJ
// set this to true to use this kind of DAC or false for plain I2S.
#define AUDIO_BITS 16 // available values are 8, 16, 24 (LEFT ALIGN in 32 bits type!!) and 32 bits
// ****** END: These are define you may want to change. Best not to touch anything outside this range. ************/

#define BYPASS_MOZZI_OUTPUT_BUFFER true

// Configuration of the I2S port, especially DMA. Set in stone here as default of the library when this want written.
// Probably do not change if you are not sure of what you are doing
#define BUFFERS 8 // number of DMA buffers used
#if (AUDIO_BITS == 32)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if AUDIO_BITS > 16?

Copy link
Collaborator Author

@tomcombriat tomcombriat Jan 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think so, at least the default values in the I2S libraries are only different for 32 bits, cf line 153 of I2S.cpp:
_bufferWords = 16 * (_bps == 32 ? 2 : 1);

I prefer to stick on what is implemented there as I think he has a better understanding of the hardware behind than me, even though I agree that it would be logical to have 24 and 32 in the same batch. So let me know what you prefer ;)

#define BUFFER_WORDS 32 // number of words in the buffers
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to put total buffer sample size, here (i.e. 256 samples), and move the calculation what that means in terms of words to the implementation.

Copy link
Collaborator Author

@tomcombriat tomcombriat Jan 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure because of the previous comment actually, the case of 24bits being ill-posed. This is the closest to the library for sure but I agree that would be more understandable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the end I agree. I think the case of 24 samples is a small mistake in the core I was inspired by.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(see latest commit)

#else
#define BUFFER_WORDS 16
#endif

#endif


#define AUDIO_BITS_PER_CHANNEL AUDIO_BITS

#define AUDIO_BIAS ((uint16_t) 1<<(AUDIO_BITS-1))
Expand Down
75 changes: 70 additions & 5 deletions MozziGuts_impl_RP2040.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ void setupMozziADC(int8_t speed) {
);

// we want notification, when a sample has arrived
dma_channel_set_irq0_enabled(dma_chan, true);
irq_set_exclusive_handler(DMA_IRQ_0, rp2040_adc_queue_handler);
irq_set_enabled(DMA_IRQ_0, true);
dma_channel_set_irq1_enabled(dma_chan, true);
irq_set_exclusive_handler(DMA_IRQ_1, rp2040_adc_queue_handler);
irq_set_enabled(DMA_IRQ_1, true);
}

void rp2040_adc_queue_handler() {
Expand All @@ -119,16 +119,17 @@ void rp2040_adc_queue_handler() {
////// BEGIN audio output code //////
#define LOOP_YIELD tight_loop_contents(); // apparently needed, among other things, to service the alarm pool

#include <hardware/pwm.h>

#if (RP2040_AUDIO_OUT_MODE == PWM_VIA_BARE_CHIP) || (EXTERNAL_AUDIO_OUTPUT == true)
#include <hardware/pwm.h>
#if (EXTERNAL_AUDIO_OUTPUT != true) // otherwise, the last stage - audioOutput() - will be provided by the user
inline void audioOutput(const AudioOutput f) {
pwm_set_gpio_level(AUDIO_CHANNEL_1_PIN, f.l()+AUDIO_BIAS);
#if (AUDIO_CHANNELS > 1)
pwm_set_gpio_level(AUDIO_CHANNEL_2_PIN, f.r()+AUDIO_BIAS);
#endif
}
#endif
#endif // #if (EXTERNAL_AUDIO_OUTPUT != true)

#include <pico/time.h>
/** Implementation notes:
Expand All @@ -150,8 +151,56 @@ void audioOutputCallback(uint) {
// NOTE: hardware_alarm_set_target returns true, if the target was already missed. In that case, keep pushing samples, until we have caught up.
} while (hardware_alarm_set_target(audio_update_alarm_num, next_audio_update));
}

#elif (RP2040_AUDIO_OUT_MODE == EXTERNAL_DAC_VIA_I2S)
#include <I2S.h>
I2S i2s(OUTPUT);


inline bool canBufferAudioOutput() {
return (i2s.availableForWrite());
}

inline void audioOutput(const AudioOutput f) {

#if (AUDIO_BITS == 8)
#if (AUDIO_CHANNELS > 1)
i2s.write8(f.l(), f.r());
#else
i2s.write8(f.l(), 0);
#endif

#elif (AUDIO_BITS == 16)
#if (AUDIO_CHANNELS > 1)
i2s.write16(f.l(), f.r());
#else
i2s.write16(f.l(), 0);
#endif

#elif (AUDIO_BITS == 24)
#if (AUDIO_CHANNELS > 1)
i2s.write24(f.l(), f.r());
#else
i2s.write24(f.l(), 0);
#endif

#elif (AUDIO_BITS == 32)
#if (AUDIO_CHANNELS > 1)
i2s.write32(f.l(), f.r());
#else
i2s.write32(f.l(), 0);
#endif
#else
#error The number of AUDIO_BITS set in AudioConfigRP2040.h is incorrect
#endif


}
#endif


static void startAudio() {
#if (RP2040_AUDIO_OUT_MODE == PWM_VIA_BARE_CHIP) || (EXTERNAL_AUDIO_OUTPUT == true) // EXTERNAL AUDIO needs the timers set here
#if (EXTERNAL_AUDIO_OUTPUT != true)
// calling analogWrite for the first time will try to init the pwm frequency and range on all pins. We don't want that happening after we've set up our own,
// so we start off with a dummy call to analogWrite:
Expand Down Expand Up @@ -185,9 +234,25 @@ static void startAudio() {
next_audio_update = make_timeout_time_us(micros_per_update);
// See audioOutputCallback(), above. In _theory_ some interrupt stuff might delay us, here, causing us to miss the first beat (and everything that follows)
} while (hardware_alarm_set_target(audio_update_alarm_num, next_audio_update));

#elif (RP2040_AUDIO_OUT_MODE == EXTERNAL_DAC_VIA_I2S)
i2s.setBCLK(BCLK_PIN);
i2s.setDATA(DOUT_PIN);
i2s.setBitsPerSample(AUDIO_BITS);
i2s.setBuffers(BUFFERS, BUFFER_WORDS, 0);
#if (LSBJ_FORMAT == true)
i2s.setLSBJFormat();
#endif
i2s.begin(AUDIO_RATE);
#endif
}

void stopMozzi() {
#if (RP2040_AUDIO_OUT_MODE == PWM_VIA_BARE_CHIP) || (EXTERNAL_AUDIO_OUTPUT == true)
hardware_alarm_set_callback(audio_update_alarm_num, NULL);
#elif (RP2040_AUDIO_OUT_MODE == EXTERNAL_DAC_VIA_I2S)
i2s.end();
#endif

}
////// END audio output code //////
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,9 +289,16 @@ on the RP2040 SDK API. Tested on a Pi Pico.

- This is a recent addition, implementation details may still change (currently just PWM driven by a timer; this may be worth changing to a DMA driven output)
- Wavetables and samples are not kept in progmem on this platform. While apparently speed (of the external flash) is not much of an issue, the data always seems to be copied into RAM, anyway.
- Audio output is to pin 0, by default, with 11 bits default output resolution
- One hardware alarm and one DMA channel are claimed (number not hardcoded)
- HIFI_MODE not yet implemented (although that should not be too hard to do)
- Currently, two audio output modes exist (configurable in AudioConfigRP2040.h) in addition to using an user-defined `audioOutput` function, with the default being PWM_VIA_BARE_CHIP:
- PWM_VIA_BARE_CHIP: PWM audio output on pin 0, by default, with 11 bits default output resolution
- One hardware alarm and one DMA channel are claimed (number not hardcoded).
- HIFI_MODE not yet implemented (although that should not be too hard to do).
- EXTERNAL_DAC_VIA_I2S: I2S output to be connected to an external DAC
- 16 bits resolution by default (configurable in AudioConfigRP2040.h), 8, 16, 24 (left aligned) and 32 resolution are available.
- Both plain I2S and LSBJ_FORMAT (for the PT8211 for instance) are available (configurable in AudioConfigRP2040.h), default is LSBJ.
- Outputs pins can be configured in AudioConfigRP2040.h. Default is BCK: 20, WS: 21, DATA: 22.
- Two hardware alarms and two DMA channels are claimed.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the i2s-library claim the hardware alarms?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It claims IRQ0, isn't that an hardware alarm :3 ?

- At the time of writing, LSBJ is only available with github arduino-pico core.
- Note that AUDIO_INPUT and mozziAnalogRead() return values in the RP2040's full ADC resolution of 0-4095 rather than AVR's 0-1023.
- twi_nonblock is not ported
- Code uses only one CPU core
Expand Down