5
5
#include " AudioTools/AudioLogger.h"
6
6
#include " AudioTools/Vector.h"
7
7
#include " Stream.h"
8
-
9
- #define PWM_BUFFER_LENGTH 512
8
+ #include < math.h> /* pow */
10
9
11
10
namespace audio_tools {
12
11
13
- enum PWMResolution {Res8,Res9,Res10,Res11};
14
- void defaultAudioOutputCallback ();
12
+ // forward declaration
15
13
class AudioPWM ;
16
- static AudioPWM *accessAudioPWM;
14
+ void defaultPWMAudioOutputCallback ();
15
+ static AudioPWM *accessAudioPWM = nullptr ;
17
16
18
17
/* *
19
18
* @brief Configuration for PWM output
@@ -25,6 +24,7 @@ static AudioPWM *accessAudioPWM;
25
24
* 11 | 2048 | 39.0625
26
25
*
27
26
* The default resolution is 8. The value must be between 8 and 11 and also drives the PWM frequency.
27
+ *
28
28
* @author Phil Schatzmann
29
29
* @copyright GPLv3
30
30
@@ -36,7 +36,7 @@ struct PWMConfig {
36
36
int start_pin = 3 ;
37
37
int buffer_size = 1024 * 8 ;
38
38
int bits_per_sample = 16 ;
39
- int resolution = 8 ; // must be between 8 and 11
39
+ int resolution = 8 ; // must be between 8 and 11 -> drives pwm frequency
40
40
} default_config;
41
41
42
42
/* *
@@ -50,17 +50,17 @@ struct PINInfo {
50
50
};
51
51
52
52
/* *
53
- * @brief Audio output to PWM pins for the ESP32.
53
+ * @brief Audio output to PWM pins for the ESP32. The ESP32 supports up to 16 channels.
54
54
* @author Phil Schatzmann
55
55
* @copyright GPLv3
56
56
*/
57
57
58
- class AudioPWM : public Stream {
58
+ class PWMAudioStream : public Stream {
59
59
friend void defaultAudioOutputCallback ();
60
60
61
61
public:
62
62
63
- AudioPWM (){
63
+ PWMAudioStream (){
64
64
accessAudioPWM = this ;
65
65
}
66
66
@@ -73,15 +73,30 @@ class AudioPWM : public Stream {
73
73
}
74
74
75
75
// starts the processing
76
- virtual void begin (PWMConfig config){
76
+ bool begin (PWMConfig config){
77
77
LOGD (__FUNCTION__);
78
78
this ->audio_config = config;
79
79
LOGI (" sample_rate: %d" , audio_config.sample_rate );
80
80
LOGI (" channels: %d" , audio_config.channels );
81
+ LOGI (" bits_per_sample: %d" , audio_config.bits_per_sample );
81
82
LOGI (" start_pin: %d" , audio_config.start_pin );
83
+ LOGI (" resolution: %d" , audio_config.resolution );
84
+
85
+ // controller has max 16 independent channels
86
+ if (audio_config.channels >=16 ){
87
+ LOGE (" Only max 16 channels are supported" );
88
+ return false ;
89
+ }
90
+
91
+ // check selected resolution
92
+ if (audio_config.resolution <8 || audio_config.resolution >11 ){
93
+ LOGE (" The resolution must be between 8 and 11!" );
94
+ return false ;
95
+ }
82
96
83
97
setupPWM ();
84
98
setupTimer ();
99
+ return true ;
85
100
}
86
101
87
102
// Ends the output
@@ -91,7 +106,7 @@ class AudioPWM : public Stream {
91
106
for (int j=0 ;j<audio_config.channels ;j++){
92
107
ledcDetachPin (pins[j].gpio );
93
108
}
94
- has_data = false ;
109
+ data_write_started = false ;
95
110
}
96
111
97
112
// not supported
@@ -125,37 +140,52 @@ class AudioPWM : public Stream {
125
140
virtual void flush () {
126
141
}
127
142
143
+
128
144
// blocking write for a single byte
129
145
virtual size_t write (uint8_t value) {
130
146
if (buffer.availableToWrite ()>1 ){
131
147
buffer.write (value);
132
- has_data = true ;
148
+ setWriteStarted () ;
133
149
}
134
150
}
135
151
136
152
// blocking write for an array: we expect a singed value and convert it into a unsigned
137
153
virtual size_t write (const uint8_t *wrt_buffer, size_t size){
154
+ LOGI (" write: %lu bytes" , size)
138
155
while (availableForWrite ()<size){
139
- delay (5 );
156
+ LOGI (" Buffer is full - waiting..." );
157
+ delay (10 );
140
158
}
141
159
size_t result = buffer.writeArray (wrt_buffer, size);
142
- has_data = true ;
160
+ if (result!=size){
161
+ LOGW (" Could not write all data: %d -> %d" , size, result);
162
+ }
163
+ setWriteStarted ();
143
164
return result;
144
165
}
145
166
146
167
147
168
protected:
148
169
PWMConfig audio_config;
149
170
Vector<PINInfo> pins;
150
- NBuffer<uint8_t > buffer = NBuffer<uint8_t >(DEFAULT_BUFFER_SIZE,4 );
151
- int buffer_idx = 0 ;
152
- bool data_write_started = false ;
153
- hw_timer_t * timer = NULL ;
171
+ NBuffer<uint8_t > buffer = NBuffer<uint8_t >(PWM_BUFFER_SIZE, PWM_BUFFERS);
172
+ hw_timer_t * timer = nullptr ;
154
173
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
155
- bool has_data = false ;
174
+ bool data_write_started = false ;
156
175
176
+ // / when we get the first write -> we activate the timer to start with the output of data
177
+ void setWriteStarted (){
178
+ if (!data_write_started){
179
+ LOGI (" timerAlarmEnable" );
180
+ data_write_started = true ;
181
+ timerAlarmEnable (timer);
182
+ }
183
+ }
157
184
185
+ // / Setup LED PWM
158
186
void setupPWM (){
187
+ LOGD (__FUNCTION__);
188
+
159
189
pins.resize (audio_config.channels );
160
190
for (int j=0 ;j<audio_config.channels ;j++){
161
191
int pwmChannel = j;
@@ -166,23 +196,25 @@ class AudioPWM : public Stream {
166
196
}
167
197
}
168
198
199
+ // / Setup ESP32 timer with callback
169
200
void setupTimer () {
201
+ LOGD (__FUNCTION__);
170
202
// Attach timer int at sample rate
171
203
timer = timerBegin (0 , 1 , true ); // Timer at full 40Mhz, no prescaling
172
204
uint64_t counter = 40000000 / audio_config.sample_rate ;
173
205
LOGI (" timer counter is %lu" , counter);
174
- timerAttachInterrupt (timer, &defaultAudioOutputCallback , true );
206
+ timerAttachInterrupt (timer, &defaultPWMAudioOutputCallback , true );
175
207
timerAlarmWrite (timer, counter, true ); // Timer fires at ~44100Hz [40Mhz / 907]
176
- timerAlarmEnable (timer);
177
208
}
178
209
210
+ // / provides the max value for the configured resulution
179
211
int maxUnsignedValue (){
180
- return maxUnsignedValue (audio_config.bits_per_sample );
212
+ return maxUnsignedValue (audio_config.resolution );
181
213
}
182
-
183
214
215
+ // / provides the max value for the indicated resulution
184
216
int maxUnsignedValue (int resolution){
185
- return 2 ^ resolution;
217
+ return pow ( 2 , resolution) ;
186
218
}
187
219
188
220
// / determiens the PWM frequency based on the requested resolution
@@ -198,10 +230,16 @@ class AudioPWM : public Stream {
198
230
199
231
// / writes the next frame to the output pins
200
232
void playNextFrame (){
201
- int required = (audio_config.bits_per_sample / 8 ) * audio_config.channels ;
202
- if (has_data && buffer.available () >= required){
203
- for (int j=0 ;j<audio_config.channels ;j++){
204
- ledcWrite (pins[j].pwm_channel , nextValue ());
233
+ if (data_write_started){
234
+ int required = (audio_config.bits_per_sample / 8 ) * audio_config.channels ;
235
+ if (buffer.available () >= required){
236
+ for (int j=0 ;j<audio_config.channels ;j++){
237
+ int value = nextValue ();
238
+ // Serial.println(value);
239
+ ledcWrite (pins[j].pwm_channel , value);
240
+ }
241
+ } else {
242
+ LOGW (" playNextFrame - underflow" );
205
243
}
206
244
}
207
245
}
@@ -210,23 +248,35 @@ class AudioPWM : public Stream {
210
248
int nextValue () {
211
249
switch (audio_config.bits_per_sample ){
212
250
case 8 : {
213
- int8_t value;
214
- buffer.readArray ((uint8_t *)&value,1 );
251
+ int value = buffer.read ();
252
+ if (value<0 ){
253
+ LOGE (" Could not read full data" );
254
+ value = 0 ;
255
+ }
215
256
return map (value, -maxValue (8 ), maxValue (8 ), 0 , maxUnsignedValue ());
216
257
}
217
258
case 16 : {
218
259
int16_t value;
219
- buffer.readArray ((uint8_t *)&value,2 );
260
+ if (buffer.readArray ((uint8_t *)&value,2 )!=2 ){
261
+ LOGE (" Could not read full data" );
262
+ }
263
+ // Serial.print(value);
264
+ // Serial.print(" -> ");
265
+ // Serial.println(map(value, -maxValue(16), maxValue(16), 0, maxUnsignedValue()));
220
266
return map (value, -maxValue (16 ), maxValue (16 ), 0 , maxUnsignedValue ());
221
267
}
222
268
case 24 : {
223
269
int24_t value;
224
- buffer.readArray ((uint8_t *)&value,3 );
270
+ if (buffer.readArray ((uint8_t *)&value,3 )!=3 ){
271
+ LOGE (" Could not read full data" );
272
+ }
225
273
return map ((int32_t )value, -maxValue (24 ), maxValue (24 ), 0 , maxUnsignedValue ());
226
274
}
227
275
case 32 : {
228
276
int32_t value;
229
- buffer.readArray ((uint8_t *)&value,4 );
277
+ if (buffer.readArray ((uint8_t *)&value,4 )!=4 ){
278
+ LOGE (" Could not read full data" );
279
+ }
230
280
return map (value, -maxValue (32 ), maxValue (32 ), 0 , maxUnsignedValue ());
231
281
}
232
282
}
@@ -236,7 +286,8 @@ class AudioPWM : public Stream {
236
286
237
287
};
238
288
239
- void IRAM_ATTR defaultAudioOutputCallback () {
289
+ // / timer callback: write the next frame to the pins
290
+ void IRAM_ATTR defaultPWMAudioOutputCallback () {
240
291
if (accessAudioPWM!=nullptr ){
241
292
portENTER_CRITICAL_ISR (&(accessAudioPWM->timerMux ));
242
293
accessAudioPWM->playNextFrame ();
0 commit comments