Skip to content

Commit 8d7353b

Browse files
committed
Error corrections (PWM for ESP32 and notes)
1 parent f020620 commit 8d7353b

File tree

4 files changed

+76
-22
lines changed

4 files changed

+76
-22
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ A complete list of the supported Audio Stream classes and scenarios can be found
7575

7676
- __I2SStream__: The best quality can be achieved with the help of I2S and an external DAC. I2S is supporting 2 channels only.
7777
- __AnalogAudioStream__: Some processors are providing an analog output, this is usually an easy and good approach: The number of pins (and herewith output channels) however is usually very limited.
78-
- __PWMAudioStream__: The last possibility is to simulate an analog output with the help of PWM by using a frequency which is beyond the audible range of 20 KHz. This method is supported by all processor and usually supports a bigger number of output pins. In terms of audio quality this is usually the worst option.
78+
- __PWMAudioStream__: The last possibility is to simulate an analog output with the help of PWM by using a frequency which is beyond the audible range of 20 KHz. This method is supported by all processors and usually supports a bigger number of output pins. In terms of audio quality this is usually the worst option.
7979

8080

8181
## Examples

examples/streams-generator-i2s/streams-generator-i2s.ino

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ void setup(void) {
3333
out.begin(config);
3434

3535
// Setup sine wave
36-
sineWave.begin(sample_rate, B4);
36+
sineWave.begin(sample_rate, N_B4);
3737
}
3838

3939
// Arduino loop - copy sound to out

examples/streams-generator-webserver_wav/streams-generator-server_wav.ino

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ void setup() {
2424
server.begin(in, sample_rate, channels);
2525

2626
// start generation of sound
27-
sineWave.begin(sample_rate, B4);
27+
sineWave.begin(sample_rate, N_B4);
2828
in.begin();
2929
}
3030

src/AudioPWM/PWMforESP32.h

Lines changed: 73 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,31 @@ static PWMAudioStreamESP32 *accessAudioPWM = nullptr;
3434
struct PWMConfigESP32 {
3535
int sample_rate = 10000; // sample rate in Hz
3636
int channels = 2;
37-
int start_pin = 3;
3837
int buffer_size = 1024 * 8;
3938
int bits_per_sample = 16;
4039
int resolution = 8; // must be between 8 and 11 -> drives pwm frequency
4140

41+
int start_pin = 3;
42+
int *pins = nullptr;
43+
44+
// determine the maximum number of channels
4245
int maxChannels() {
4346
return 16;
4447
}
4548

49+
// define all pins by passing an array and updates the channels by the number of pins
50+
template<size_t N>
51+
void setPins(int (&array)[N]) {
52+
LOGD(__FUNCTION__);
53+
int new_channels = sizeof(array)/sizeof(int);
54+
if (channels!=new_channels) {
55+
LOGI("channels updated to %d", new_channels);
56+
channels = new_channels;
57+
}
58+
pins = array;
59+
start_pin = -1; // mark start pin as invalid
60+
}
61+
4662
} default_config;
4763

4864
typedef PWMConfigESP32 PWMConfig;
@@ -59,7 +75,6 @@ struct PinInfoESP32 {
5975

6076
typedef PinInfoESP32 PinInfo;
6177

62-
6378
/**
6479
* @brief Audio output to PWM pins for the ESP32. The ESP32 supports up to 16 channels.
6580
* @author Phil Schatzmann
@@ -91,7 +106,8 @@ class PWMAudioStreamESP32 : public Stream {
91106
LOGI("channels: %d", audio_config.channels);
92107
LOGI("bits_per_sample: %d", audio_config.bits_per_sample);
93108
LOGI("start_pin: %d", audio_config.start_pin);
94-
LOGI("resolution: %d", audio_config.resolution);
109+
LOGI("resolution: %d bits", audio_config.resolution);
110+
LOGI("pwm freq: %f khz", frequency(audio_config.resolution));
95111

96112
// controller has max 16 independent channels
97113
if (audio_config.channels>=16){
@@ -104,7 +120,6 @@ class PWMAudioStreamESP32 : public Stream {
104120
LOGE("The resolution must be between 8 and 11!");
105121
return false;
106122
}
107-
108123
setupPWM();
109124
setupTimer();
110125
return true;
@@ -171,10 +186,16 @@ class PWMAudioStreamESP32 : public Stream {
171186
if (result!=size){
172187
LOGW("Could not write all data: %d -> %d", size, result);
173188
}
189+
// activate the timer now - if not already done
174190
setWriteStarted();
175191
return result;
176192
}
177193

194+
// When the timer does not have enough data we increase the underflow_count;
195+
uint64_t underflowsPerSecond(){
196+
return underflow_count;
197+
}
198+
178199

179200
protected:
180201
PWMConfig audio_config;
@@ -183,6 +204,7 @@ class PWMAudioStreamESP32 : public Stream {
183204
hw_timer_t * timer = nullptr;
184205
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
185206
bool data_write_started = false;
207+
uint64_t underflow_count = 0;
186208

187209
/// when we get the first write -> we activate the timer to start with the output of data
188210
void setWriteStarted(){
@@ -199,22 +221,36 @@ class PWMAudioStreamESP32 : public Stream {
199221

200222
pins.resize(audio_config.channels);
201223
for (int j=0;j<audio_config.channels;j++){
224+
LOGD("Processing channel %d", j);
202225
int pwmChannel = j;
203226
pins[j].pwm_channel = pwmChannel;
204-
pins[j].gpio = audio_config.start_pin + j;
227+
if (audio_config.pins==nullptr){
228+
// use sequential pins starting from start_pin
229+
LOGD("-> defining pin %d",audio_config.start_pin + j);
230+
pins[j].gpio = audio_config.start_pin + j;
231+
} else {
232+
// use defined pins
233+
LOGD("-> defining pin %d",audio_config.pins[j]);
234+
pins[j].gpio = audio_config.pins[j];
235+
}
236+
LOGD("-> ledcSetup");
205237
ledcSetup(pwmChannel, frequency(audio_config.resolution), audio_config.resolution);
206-
ledcAttachPin(pwmChannel, pins[j].gpio);
238+
LOGD("-> ledcAttachPin");
239+
ledcAttachPin(pins[j].gpio, pwmChannel);
207240
}
208241
}
209242

210243
/// Setup ESP32 timer with callback
211244
void setupTimer() {
212245
LOGD(__FUNCTION__);
246+
213247
// Attach timer int at sample rate
214248
timer = timerBegin(0, 1, true); // Timer at full 40Mhz, no prescaling
215249
uint64_t counter = 40000000 / audio_config.sample_rate;
216-
LOGI("timer counter is %lu", counter);
250+
LOGI("-> timer counter is %lu", counter);
251+
LOGD("-> timerAttachInterrupt");
217252
timerAttachInterrupt(timer, &defaultPWMAudioOutputCallback, true);
253+
LOGD("-> timerAlarmWrite");
218254
timerAlarmWrite(timer, counter, true); // Timer fires at ~44100Hz [40Mhz / 907]
219255
}
220256

@@ -241,58 +277,76 @@ class PWMAudioStreamESP32 : public Stream {
241277

242278
/// writes the next frame to the output pins
243279
void playNextFrame(){
280+
static long underflow_time = millis()+1000;
244281
if (data_write_started){
245282
int required = (audio_config.bits_per_sample / 8) * audio_config.channels;
246283
if (buffer.available() >= required){
284+
underflow_count = 0;
247285
for (int j=0;j<audio_config.channels;j++){
248286
int value = nextValue();
249-
//Serial.println(value);
250287
ledcWrite(pins[j].pwm_channel, value);
251288
}
252289
} else {
253-
LOGW("playNextFrame - underflow");
290+
underflow_count++;
254291
}
292+
293+
if (underflow_time>millis()){
294+
underflow_time = millis()+1000;
295+
underflow_count = 0;
296+
}
297+
255298
}
256299
}
257300

258301
/// determines the next scaled value
259302
int nextValue() {
303+
static long counter=0; static int min_value=INT_MAX; static int max_value=INT_MIN;
304+
305+
counter++;
306+
int result;
260307
switch(audio_config.bits_per_sample ){
261308
case 8: {
262309
int value = buffer.read();
263310
if (value<0){
264311
LOGE("Could not read full data");
265312
value = 0;
266313
}
267-
return map(value, -maxValue(8), maxValue(8), 0, maxUnsignedValue());
314+
result = map(value, -maxValue(8), maxValue(8), 0, maxUnsignedValue());
315+
break;
268316
}
269317
case 16: {
270318
int16_t value;
271319
if (buffer.readArray((uint8_t*)&value,2)!=2){
272320
LOGE("Could not read full data");
273321
}
274-
//Serial.print(value);
275-
//Serial.print(" -> ");
276-
//Serial.println(map(value, -maxValue(16), maxValue(16), 0, maxUnsignedValue()));
277-
return map(value, -maxValue(16), maxValue(16), 0, maxUnsignedValue());
322+
result = map(value, -maxValue(16), maxValue(16), 0, maxUnsignedValue());
323+
break;
278324
}
279325
case 24: {
280326
int24_t value;
281327
if (buffer.readArray((uint8_t*)&value,3)!=3){
282328
LOGE("Could not read full data");
283329
}
284-
return map((int32_t)value, -maxValue(24), maxValue(24), 0, maxUnsignedValue());
330+
result = map((int32_t)value, -maxValue(24), maxValue(24), 0, maxUnsignedValue());
331+
break;
285332
}
286333
case 32: {
287334
int32_t value;
288335
if (buffer.readArray((uint8_t*)&value,4)!=4){
289336
LOGE("Could not read full data");
290337
}
291-
return map(value, -maxValue(32), maxValue(32), 0, maxUnsignedValue());
338+
result = map(value, -maxValue(32), maxValue(32), 0, maxUnsignedValue());
339+
break;
292340
}
293-
}
294-
LOGE("nextValue could not be determined because bits_per_sample is not valid: ",audio_config.bits_per_sample);
295-
return 0;
341+
}
342+
343+
// if (counter>=1024){
344+
// max_value = max(max_value, result);
345+
// min_value = min(min_value, result);
346+
// LOGD("output: min %d / max %d", min_value, max_value);
347+
// counter = 0;
348+
// }
349+
return result;
296350
}
297351

298352
};

0 commit comments

Comments
 (0)