@@ -34,15 +34,31 @@ static PWMAudioStreamESP32 *accessAudioPWM = nullptr;
34
34
struct PWMConfigESP32 {
35
35
int sample_rate = 10000 ; // sample rate in Hz
36
36
int channels = 2 ;
37
- int start_pin = 3 ;
38
37
int buffer_size = 1024 * 8 ;
39
38
int bits_per_sample = 16 ;
40
39
int resolution = 8 ; // must be between 8 and 11 -> drives pwm frequency
41
40
41
+ int start_pin = 3 ;
42
+ int *pins = nullptr ;
43
+
44
+ // determine the maximum number of channels
42
45
int maxChannels () {
43
46
return 16 ;
44
47
}
45
48
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
+
46
62
} default_config;
47
63
48
64
typedef PWMConfigESP32 PWMConfig;
@@ -59,7 +75,6 @@ struct PinInfoESP32 {
59
75
60
76
typedef PinInfoESP32 PinInfo;
61
77
62
-
63
78
/* *
64
79
* @brief Audio output to PWM pins for the ESP32. The ESP32 supports up to 16 channels.
65
80
* @author Phil Schatzmann
@@ -91,7 +106,8 @@ class PWMAudioStreamESP32 : public Stream {
91
106
LOGI (" channels: %d" , audio_config.channels );
92
107
LOGI (" bits_per_sample: %d" , audio_config.bits_per_sample );
93
108
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 ));
95
111
96
112
// controller has max 16 independent channels
97
113
if (audio_config.channels >=16 ){
@@ -104,7 +120,6 @@ class PWMAudioStreamESP32 : public Stream {
104
120
LOGE (" The resolution must be between 8 and 11!" );
105
121
return false ;
106
122
}
107
-
108
123
setupPWM ();
109
124
setupTimer ();
110
125
return true ;
@@ -171,10 +186,16 @@ class PWMAudioStreamESP32 : public Stream {
171
186
if (result!=size){
172
187
LOGW (" Could not write all data: %d -> %d" , size, result);
173
188
}
189
+ // activate the timer now - if not already done
174
190
setWriteStarted ();
175
191
return result;
176
192
}
177
193
194
+ // When the timer does not have enough data we increase the underflow_count;
195
+ uint64_t underflowsPerSecond (){
196
+ return underflow_count;
197
+ }
198
+
178
199
179
200
protected:
180
201
PWMConfig audio_config;
@@ -183,6 +204,7 @@ class PWMAudioStreamESP32 : public Stream {
183
204
hw_timer_t * timer = nullptr ;
184
205
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
185
206
bool data_write_started = false ;
207
+ uint64_t underflow_count = 0 ;
186
208
187
209
// / when we get the first write -> we activate the timer to start with the output of data
188
210
void setWriteStarted (){
@@ -199,22 +221,36 @@ class PWMAudioStreamESP32 : public Stream {
199
221
200
222
pins.resize (audio_config.channels );
201
223
for (int j=0 ;j<audio_config.channels ;j++){
224
+ LOGD (" Processing channel %d" , j);
202
225
int pwmChannel = j;
203
226
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" );
205
237
ledcSetup (pwmChannel, frequency (audio_config.resolution ), audio_config.resolution );
206
- ledcAttachPin (pwmChannel, pins[j].gpio );
238
+ LOGD (" -> ledcAttachPin" );
239
+ ledcAttachPin (pins[j].gpio , pwmChannel);
207
240
}
208
241
}
209
242
210
243
// / Setup ESP32 timer with callback
211
244
void setupTimer () {
212
245
LOGD (__FUNCTION__);
246
+
213
247
// Attach timer int at sample rate
214
248
timer = timerBegin (0 , 1 , true ); // Timer at full 40Mhz, no prescaling
215
249
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" );
217
252
timerAttachInterrupt (timer, &defaultPWMAudioOutputCallback, true );
253
+ LOGD (" -> timerAlarmWrite" );
218
254
timerAlarmWrite (timer, counter, true ); // Timer fires at ~44100Hz [40Mhz / 907]
219
255
}
220
256
@@ -241,58 +277,76 @@ class PWMAudioStreamESP32 : public Stream {
241
277
242
278
// / writes the next frame to the output pins
243
279
void playNextFrame (){
280
+ static long underflow_time = millis ()+1000 ;
244
281
if (data_write_started){
245
282
int required = (audio_config.bits_per_sample / 8 ) * audio_config.channels ;
246
283
if (buffer.available () >= required){
284
+ underflow_count = 0 ;
247
285
for (int j=0 ;j<audio_config.channels ;j++){
248
286
int value = nextValue ();
249
- // Serial.println(value);
250
287
ledcWrite (pins[j].pwm_channel , value);
251
288
}
252
289
} else {
253
- LOGW ( " playNextFrame - underflow " ) ;
290
+ underflow_count++ ;
254
291
}
292
+
293
+ if (underflow_time>millis ()){
294
+ underflow_time = millis ()+1000 ;
295
+ underflow_count = 0 ;
296
+ }
297
+
255
298
}
256
299
}
257
300
258
301
// / determines the next scaled value
259
302
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;
260
307
switch (audio_config.bits_per_sample ){
261
308
case 8 : {
262
309
int value = buffer.read ();
263
310
if (value<0 ){
264
311
LOGE (" Could not read full data" );
265
312
value = 0 ;
266
313
}
267
- return map (value, -maxValue (8 ), maxValue (8 ), 0 , maxUnsignedValue ());
314
+ result = map (value, -maxValue (8 ), maxValue (8 ), 0 , maxUnsignedValue ());
315
+ break ;
268
316
}
269
317
case 16 : {
270
318
int16_t value;
271
319
if (buffer.readArray ((uint8_t *)&value,2 )!=2 ){
272
320
LOGE (" Could not read full data" );
273
321
}
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 ;
278
324
}
279
325
case 24 : {
280
326
int24_t value;
281
327
if (buffer.readArray ((uint8_t *)&value,3 )!=3 ){
282
328
LOGE (" Could not read full data" );
283
329
}
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 ;
285
332
}
286
333
case 32 : {
287
334
int32_t value;
288
335
if (buffer.readArray ((uint8_t *)&value,4 )!=4 ){
289
336
LOGE (" Could not read full data" );
290
337
}
291
- return map (value, -maxValue (32 ), maxValue (32 ), 0 , maxUnsignedValue ());
338
+ result = map (value, -maxValue (32 ), maxValue (32 ), 0 , maxUnsignedValue ());
339
+ break ;
292
340
}
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;
296
350
}
297
351
298
352
};
0 commit comments