Skip to content

Commit

Permalink
Add PWMAudio support for the RP2040 (#597)
Browse files Browse the repository at this point in the history
Uses the RP2040 HW PWM generator to provide a stereo audio signal.
  • Loading branch information
earlephilhower committed Jan 4, 2023
1 parent 46b3657 commit 0b5da0e
Show file tree
Hide file tree
Showing 8 changed files with 11,480 additions and 3 deletions.
11,253 changes: 11,253 additions & 0 deletions examples/PlayMODFromPROGMEMToPWM/5steps.h

Large diffs are not rendered by default.

47 changes: 47 additions & 0 deletions examples/PlayMODFromPROGMEMToPWM/PlayMODFromPROGMEMToPWM.ino
@@ -0,0 +1,47 @@
#include <Arduino.h>
#include "AudioFileSourcePROGMEM.h"
#include "AudioGeneratorMOD.h"
#include "AudioOutputPWM.h"

#if !defined(ARDUINO_ARCH_RP2040)
void setup() {
Serial.begin(115200);
Serial.printf("Only for the RP2040/Raspberry Pi Pico\n");
}

void loop() {
}
#else

// 5_steps.mod sample from the mod archive: https://modarchive.org/
#include "5steps.h"

AudioGeneratorMOD *mod;
AudioFileSourcePROGMEM *file;
AudioOutputPWM *out;

void setup()
{
Serial.begin(115200);
delay(1000);

audioLogger = &Serial;
file = new AudioFileSourcePROGMEM(steps_mod, sizeof(steps_mod));
out = new AudioOutputPWM();
mod = new AudioGeneratorMOD();
mod->SetBufferSize(3*1024);
mod->SetSampleRate(44100);
mod->SetStereoSeparation(32);
mod->begin(file, out);
}

void loop()
{
if (mod->isRunning()) {
if (!mod->loop()) mod->stop();
} else {
Serial.printf("MOD done\n");
delay(1000);
}
}
#endif
1 change: 1 addition & 0 deletions keywords.txt
Expand Up @@ -23,6 +23,7 @@ AudioOutput KEYWORD1
AudioOutputI2S KEYWORD1
AudioOutputI2SNoDAC KEYWORD1
AudioOutputI2SClass KEYWORD1
AudioOutputPWM KEYWORD1
AudioOutputNull KEYWORD1
AudioOutputBuffer KEYWORD1
AudioOutputSerialWAV KEYWORD1
Expand Down
4 changes: 2 additions & 2 deletions src/AudioGeneratorWAV.cpp
Expand Up @@ -127,7 +127,7 @@ bool AudioGeneratorWAV::ReadWAVInfo()
return false;
};
if (u32 != 0x46464952) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, invalid RIFF header, got: %08X \n"), (uint32_t) u32);
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, invalid RIFF header\n"));
return false;
}

Expand All @@ -143,7 +143,7 @@ bool AudioGeneratorWAV::ReadWAVInfo()
return false;
};
if (u32 != 0x45564157) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, invalid WAVE header, got: %08X \n"), (uint32_t) u32);
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, invalid WAVE header\n"));
return false;
}

Expand Down
114 changes: 114 additions & 0 deletions src/AudioOutputPWM.cpp
@@ -0,0 +1,114 @@
/*
AudioOutputPWM
Base class for the RP2040 PWM audio
Copyright (C) 2023 Earle F. Philhower, III
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#if defined(ARDUINO_ARCH_RP2040)
#include <Arduino.h>
#include "AudioOutputPWM.h"

AudioOutputPWM::AudioOutputPWM(long sampleRate, pin_size_t data) {
pwmOn = false;
mono = false;
bps = 16;
channels = 2;
hertz = sampleRate;
doutPin = data;
SetGain(1.0);
pwm.setStereo(true);
}

AudioOutputPWM::~AudioOutputPWM() {
stop();
}

bool AudioOutputPWM::SetRate(int hz) {
this->hertz = hz;
if (pwmOn) {
pwm.setFrequency(hz);
}
return true;
}

bool AudioOutputPWM::SetBitsPerSample(int bits) {
if ( (bits != 16) && (bits != 8) ) return false;
this->bps = bits;
return true;
}

bool AudioOutputPWM::SetChannels(int channels) {
if ( (channels < 1) || (channels > 2) ) return false;
this->channels = channels;
return true;
}

bool AudioOutputPWM::SetOutputModeMono(bool mono) {
this->mono = mono;
return true;
}

bool AudioOutputPWM::begin() {
if (!pwmOn) {
pwm.setPin(doutPin);
pwm.begin(hertz);
}
pwmOn = true;
SetRate(hertz); // Default
return true;
}

bool AudioOutputPWM::ConsumeSample(int16_t sample[2]) {

if (!pwmOn)
return false;

int16_t ms[2];

ms[0] = sample[0];
ms[1] = sample[1];
MakeSampleStereo16( ms );

if (this->mono) {
// Average the two samples and overwrite
int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL];
ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (ttl>>1) & 0xffff;
}

if (pwm.available()) {
pwm.write((int16_t) ms[0]);
pwm.write((int16_t) ms[1]);
return true;
} else {
return false;
}
}

void AudioOutputPWM::flush() {
pwm.flush();
}

bool AudioOutputPWM::stop() {
if (!pwmOn)
return false;

pwm.end();
pwmOn = false;
return true;
}

#endif
57 changes: 57 additions & 0 deletions src/AudioOutputPWM.h
@@ -0,0 +1,57 @@
/*
AudioOutputPWM
Base class for the RP2040 PWM audio output
Copyright (C) 2023 Earle F. Philhower, III
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "AudioOutput.h"

#if defined(ARDUINO_ARCH_RP2040)
#include <Arduino.h>
#include <PWMAudio.h>

class AudioOutputPWM : public AudioOutput
{
public:
AudioOutputPWM(long sampleRate = 44100, pin_size_t data = 0);
virtual ~AudioOutputPWM() override;
virtual bool SetRate(int hz) override;
virtual bool SetBitsPerSample(int bits) override;
virtual bool SetChannels(int channels) override;
virtual bool begin() override;
virtual bool ConsumeSample(int16_t sample[2]) override;
virtual void flush() override;
virtual bool stop() override;

bool SetOutputModeMono(bool mono); // Force mono output no matter the input

protected:
bool SetPinout();
virtual int AdjustI2SRate(int hz) { return hz; }
uint8_t portNo;
int output_mode;
bool mono;
bool pwmOn;

uint8_t doutPin;

PWMAudio pwm;
};

#endif
1 change: 1 addition & 0 deletions src/ESP8266Audio.h
Expand Up @@ -41,6 +41,7 @@
#include "AudioOutput.h"
#include "AudioOutputI2S.h"
#include "AudioOutputI2SNoDAC.h"
#include "AudioOutputPWM.h"
#include "AudioOutputMixer.h"
#include "AudioOutputNull.h"
#include "AudioOutputSerialWAV.h"
Expand Down
6 changes: 5 additions & 1 deletion tests/common.sh
Expand Up @@ -109,7 +109,11 @@ if [ "$BUILD_TYPE" = "build" ]; then
install_arduino
install_esp8266 "$HOME/arduino_ide"
source "$HOME/arduino_ide/hardware/esp8266com/esp8266/tests/common.sh"
build_sketches "$HOME/arduino_ide" "$TRAVIS_BUILD_DIR" "-l $HOME/Arduino/libraries" "$BUILD_MOD" "$BUILD_REM" "lm2f"
export ESP8266_ARDUINO_SKETCHES=$(find $HOME/Arduino/libraries/ESP8266Audio -name *.ino | sort)
# ESP8266 scripts now expect tools in wrong spot. Use simple and dumb fix
mkdir -p "$HOME/work/ESP8266Audio/ESP8266Audio"
ln -s "$HOME/arduino_ide/hardware/esp8266com/esp8266/tools" "$HOME/work/ESP8266Audio/ESP8266Audio/tools"
build_sketches "$TRAVIS_BUILD_DIR" "$HOME/arduino_ide" "$HOME/arduino_ide/hardware" "$HOME/Arduino/libraries" "$BUILD_MOD" "$BUILD_REM" "lm2f"
elif [ "$BUILD_TYPE" = "build_esp32" ]; then
install_arduino
install_esp32 "$HOME/arduino_ide"
Expand Down

0 comments on commit 0b5da0e

Please sign in to comment.