Skip to content

Commit b303521

Browse files
committed
Final Corrections for PWM support
1 parent c024658 commit b303521

File tree

10 files changed

+173
-159
lines changed

10 files changed

+173
-159
lines changed

examples/streams-generator-pwm/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ It should also be possible to connect a headphone to the output pins...
1616

1717
The pins depend on the Processor:
1818

19-
| PIEZO | ESP32 | AVR | Rpi Pico
20-
| --------| ---------------|-----------------|--------------
21-
| + | GPIO3 / GPIO4 | GPIO3 / GPIO11 | GPIO03/GPIO04
19+
| PIEZO | ESP32 | Rpi Pico |
20+
| --------| ---------------|-----------------|
21+
| + | GPIO4/GPIO5 | GPIO02/GPIO03 |
2222
| - | GND | |

examples/streams-generator-pwm/streams-generator-pwm.ino

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
using namespace audio_tools;
1212

13-
int pins[] = {22, 23};
13+
//int pins[] = {22, 23};
1414
int channels = 2;
1515
uint16_t sample_rate=22000;
1616
SineWaveGenerator<int16_t> sineWave(32000); // subclass of SoundGenerator with max amplitude of 32000

examples/streams-memory_wav-pwm/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ MemoryStream -> AudioOutputStream -> WAVDecoder -> AudioPWM
66

77
The pins depend on the Processor:
88

9-
| PIEZO | ESP32 | AVR (Nano) | Rpi Pico
10-
| --------| ---------------|-----------------|--------------
11-
| + | GPIO3 / GPIO4 | GPIO3 / GPIO11 | GPIO03/GPIO04
9+
| PIEZO | ESP32 | RPI Pico |
10+
| --------| ---------------|-----------------|
11+
| + | GPIO4 | GPIO2 |
1212
| - | GND | |
1313

examples/streams-memory_wav-pwm/streams-memory_wav-pwm.ino

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
using namespace audio_tools;
1616

17-
// MemoryStream -> AudioOutputStream -> WAVDecoder -> CsvStream
17+
// MemoryStream -> AudioOutputStream -> WAVDecoder -> PWMAudioStream
1818
MemoryStream wav(knghtsng_wav, knghtsng_wav_len);
1919
PWMAudioStream pwm; // PWM output
2020
WAVDecoder decoder(pwm); // decode wav to pcm and send it to printer
@@ -23,12 +23,13 @@ StreamCopy copier(out, wav); // copy in to out
2323

2424
void setup(){
2525
Serial.begin(115200);
26+
while(!Serial);
2627
AudioLogger::instance().begin(Serial, AudioLogger::Debug);
2728

2829
// setup pwm output
2930
PWMConfig config = pwm.defaultConfig();
3031
config.channels = 1;
31-
config.sample_rate = 1000;
32+
config.sample_rate = 11025;
3233
pwm.begin(config);
3334
}
3435

library.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ sentence=Some useful audio processing classes
66
paragraph=Timer, RingBuffer, I2S
77
category=Device Control
88
url=https://github.com/pschatzmann/arduino-audio-tools
9-
architectures=esp32,mbed_nano
9+
architectures=*

src/AudioConfig.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
#define A2DP_BUFFER_COUNT 8
4242
#define DEFAUT_ADC_PIN 34
4343
#define PWM_BUFFER_SIZE 512
44-
#define PWM_BUFFERS 2
44+
#define PWM_BUFFERS 10
4545

4646
/**
4747
* -------------------------------------------------------------------------

src/AudioPWM/PWMAudioStreamAVR.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class PWMAudioStreamAVR : public PWMAudioStreamBase {
4141
TCCR2A = 0;
4242
interrupts(); // enable all interrupts
4343

44-
data_write_started = false;
44+
is_timer_started = false;
4545
}
4646

4747
/// Setup AVR timer with callback
@@ -120,11 +120,15 @@ class PWMAudioStreamAVR : public PWMAudioStreamBase {
120120
protected:
121121
int pins[2] = {3, 11};
122122

123+
virtual int maxOutputValue(){
124+
return 255;
125+
}
126+
123127
};
124128

125129
/// separate method that can be defined as friend so that we can access protected information
126130
void defaultPWMAudioOutputCallback(){
127-
if (accessAudioPWM!=nullptr && accessAudioPWM->data_write_started){
131+
if (accessAudioPWM!=nullptr && accessAudioPWM->is_timer_started){
128132
accessAudioPWM->playNextFrame();
129133
}
130134
}

src/AudioPWM/PWMAudioStreamBase.h

Lines changed: 92 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,15 @@
99

1010
namespace audio_tools {
1111

12+
// forward declarations
13+
// Callback for user
1214
typedef bool (*PWMCallbackType)(uint8_t channels, int16_t* data);
15+
// Callback used by system
1316
void defaultPWMAudioOutputCallback();
17+
// Stream classes
18+
class PWMAudioStreamESP32;
19+
class PWMAudioStreamPico;
20+
1421
/**
1522
* PWMConfigAVR
1623
* @author Phil Schatzmann
@@ -27,12 +34,36 @@ struct PWMConfig {
2734
LOGI("sample_rate: %d", sample_rate);
2835
LOGI("channels: %d", channels);
2936
LOGI("bits_per_sample: %d", bits_per_sample);
37+
LOGI("buffer_size: %d", buffer_size);
3038
}
3139

3240
#ifdef ESP32
33-
int resolution = 8; // must be between 8 and 11 -> drives pwm frequency
34-
int start_pin = 3;
35-
int *pins = nullptr;
41+
/**
42+
* @brief Configuration for PWM output
43+
* RES | BITS | MAX_FREQ (kHz)
44+
* ----|------|-------
45+
* 8 | 256 | 312.5
46+
* 9 | 512 | 156.25
47+
* 10 | 1024 | 78.125
48+
* 11 | 2048 | 39.0625
49+
*
50+
* The default resolution is 8. The value must be between 8 and 11 and also drives the PWM frequency.
51+
*/
52+
int resolution = 8; // must be between 8 and 11 -> drives pwm frequency
53+
int start_pin = 4;
54+
uint8_t timer_id = 0; // must be between 0 and 3
55+
#endif
56+
57+
#ifdef ARDUINO_ARCH_RP2040
58+
int pwm_freq = 60000; // audable range is from 20 to 20,000Hz
59+
int start_pin = 2; // channel 0 will be on gpio 2, channel 1 on 3 etc
60+
#endif
61+
62+
// for architectures which support many flexible pins we support setPins
63+
#if defined(ESP32) || defined(ARDUINO_ARCH_RP2040)
64+
friend class PWMAudioStreamESP32;
65+
friend class PWMAudioStreamPico;
66+
3667

3768
// define all pins by passing an array and updates the channels by the number of pins
3869
template<size_t N>
@@ -46,11 +77,11 @@ struct PWMConfig {
4677
pins = array;
4778
start_pin = -1; // mark start pin as invalid
4879
}
49-
#endif
5080

51-
#ifdef ARDUINO_ARCH_RP2040
52-
int pwm_freq = 60000; // audable range is from 20 to 20,000Hz
53-
int start_pin = 2; // channel 0 will be on gpio 2, channel 1 on 3 etc
81+
protected:
82+
int *pins = nullptr;
83+
84+
5485
#endif
5586

5687
} default_config;
@@ -71,8 +102,6 @@ class PWMAudioStreamBase : public Stream {
71102
return audio_config;
72103
}
73104

74-
virtual int maxChannels() = 0;
75-
76105
/// Starts the PWMAudio using callbacks
77106
bool begin(uint16_t sampleRate, uint8_t channels, PWMCallbackType cb) {
78107
LOGD(__FUNCTION__);
@@ -88,7 +117,7 @@ class PWMAudioStreamBase : public Stream {
88117
setupPWM();
89118
setupTimer();
90119

91-
data_write_started = true;
120+
is_timer_started = true;
92121
return true;
93122
}
94123

@@ -103,6 +132,7 @@ class PWMAudioStreamBase : public Stream {
103132
}
104133
// allocate new buffer
105134
if (buffer==nullptr) {
135+
LOGI("Allocating new buffer %d * %d bytes",config.buffers, config.buffer_size);
106136
buffer = new NBuffer<uint8_t>(config.buffer_size, config.buffers);
107137
}
108138
// check allocation
@@ -153,100 +183,117 @@ class PWMAudioStreamBase : public Stream {
153183
size_t result = 0;
154184
if (buffer->availableToWrite()>1){
155185
result = buffer->write(value);
156-
setWriteStarted();
186+
startTimer();
157187
}
158188
return result;
159189
}
160190

161191
// blocking write for an array: we expect a singed value and convert it into a unsigned
162192
virtual size_t write(const uint8_t *wrt_buffer, size_t size){
163-
LOGI("write: %lu bytes", size)
193+
LOGD("write: %lu bytes", size)
194+
bool log_flag = true;
164195
while(availableForWrite()<size){
165-
LOGI("Buffer is full - waiting...");
196+
if (log_flag) LOGI("Buffer is full - waiting...");
197+
log_flag = false;
166198
delay(10);
167199
}
168200
size_t result = buffer->writeArray(wrt_buffer, size);
169201
if (result!=size){
170202
LOGW("Could not write all data: %d -> %d", size, result);
171203
}
172204
// activate the timer now - if not already done
173-
setWriteStarted();
205+
startTimer();
174206
return result;
175207
}
176208

177209
// When the timer does not have enough data we increase the underflow_count;
178-
uint64_t underflowsPerSecond(){
179-
return underflow_count;
210+
uint32_t underflowsPerSecond(){
211+
return underflow_per_second;
180212
}
181-
182-
uint64_t frameCount(){
183-
return frame_count;
213+
// provides the effectivly measured output frames per second
214+
uint32_t framesPerSecond(){
215+
return frames_per_second;
184216
}
185217

218+
186219
virtual void pwmWrite(int channel, int value) = 0;
187220

188221
protected:
189-
NBuffer<uint8_t> *buffer;
190-
bool data_write_started = false;
191-
uint64_t underflow_count = 0;
192-
uint64_t frame_count = 0;
193-
PWMCallbackType user_callback = nullptr;
194222
PWMConfig audio_config;
223+
NBuffer<uint8_t> *buffer = nullptr;
224+
PWMCallbackType user_callback = nullptr;
225+
uint32_t underflow_count = 0;
226+
uint32_t underflow_per_second = 0;
227+
uint32_t frame_count = 0;
228+
uint32_t frames_per_second = 0;
229+
uint64_t time_1_sec;
230+
bool is_timer_started = false;
195231

196232
virtual void logConfig() {
197233
audio_config.logConfig();
198234
}
199235

200236
virtual void setupPWM() = 0;
201-
202237
virtual void setupTimer() = 0;
238+
virtual int maxChannels() = 0;
239+
virtual int maxOutputValue() = 0;
203240

204241

205242
/// when we get the first write -> we activate the timer to start with the output of data
206-
virtual void setWriteStarted(){
207-
if (!data_write_started){
208-
LOGI("timerAlarmEnable");
209-
data_write_started = true;
243+
virtual void startTimer(){
244+
if (!is_timer_started){
245+
LOGD(__FUNCTION__);
246+
is_timer_started = true;
210247
}
211248
}
212249

250+
inline void updateStatistics(){
251+
frame_count++;
252+
if (millis()>=time_1_sec){
253+
time_1_sec = millis()+1000;
254+
frames_per_second = frame_count;
255+
underflow_per_second = underflow_count;
256+
underflow_count = 0;
257+
frame_count = 0;
258+
}
259+
}
260+
261+
213262
void playNextFrameCallback(){
263+
//LOGD(__FUNCTION__);
214264
uint8_t channels = audio_config.channels;
215265
int16_t data[channels];
216266
if (user_callback(channels, data)){
217-
frame_count++;
218267
for (uint8_t j=0;j<audio_config.channels;j++){
219268
int value = map(data[j], -maxValue(16), maxValue(16), 0, 255);
220269
pwmWrite(j, value);
221-
}
270+
}
271+
updateStatistics();
222272
}
223273
}
224274

275+
225276
/// writes the next frame to the output pins
226277
void playNextFrameStream(){
227-
static long underflow_time = millis()+1000;
228-
if (data_write_started){
278+
if (is_timer_started){
279+
//LOGD(__FUNCTION__);
229280
int required = (audio_config.bits_per_sample / 8) * audio_config.channels;
230281
if (buffer->available() >= required){
231-
underflow_count = 0;
232-
frame_count++;
233282
for (int j=0;j<audio_config.channels;j++){
234283
int value = nextValue();
235-
Serial.println(value);
236284
pwmWrite(j, value);
237285
}
238286
} else {
239287
underflow_count++;
240288
}
241-
242-
if (underflow_time>millis()){
243-
underflow_time = millis()+1000;
244-
underflow_count = 0;
245-
}
289+
updateStatistics();
290+
} else {
291+
//LOGE("is_timer_started is false");
246292
}
247293
}
248294

249295
void playNextFrame(){
296+
//LOGD(__FUNCTION__);
250297
if (user_callback!=nullptr){
251298
playNextFrameCallback();
252299
} else {
@@ -265,31 +312,31 @@ class PWMAudioStreamBase : public Stream {
265312
LOGE("Could not read full data");
266313
value = 0;
267314
}
268-
result = map(value, -maxValue(8), maxValue(8), 0, 255);
315+
result = map(value, -maxValue(8), maxValue(8), 0, maxOutputValue());
269316
break;
270317
}
271318
case 16: {
272319
int16_t value;
273320
if (buffer->readArray((uint8_t*)&value,2)!=2){
274321
LOGE("Could not read full data");
275322
}
276-
result = map(value, -maxValue(16), maxValue(16), 0, 255);
323+
result = map(value, -maxValue(16), maxValue(16), 0, maxOutputValue());
277324
break;
278325
}
279326
case 24: {
280327
int24_t value;
281328
if (buffer->readArray((uint8_t*)&value,3)!=3){
282329
LOGE("Could not read full data");
283330
}
284-
result = map((int32_t)value, -maxValue(24), maxValue(24), 0, 255);
331+
result = map((int32_t)value, -maxValue(24), maxValue(24), 0, maxOutputValue());
285332
break;
286333
}
287334
case 32: {
288335
int32_t value;
289336
if (buffer->readArray((uint8_t*)&value,4)!=4){
290337
LOGE("Could not read full data");
291338
}
292-
result = map(value, -maxValue(32), maxValue(32), 0, 255);
339+
result = map(value, -maxValue(32), maxValue(32), 0, maxOutputValue());
293340
break;
294341
}
295342
}

0 commit comments

Comments
 (0)