@@ -32,6 +32,8 @@ PWMAudio::PWMAudio(pin_size_t pin, bool stereo) {
32
32
_buffers = 8 ;
33
33
_bufferWords = 0 ;
34
34
_stereo = stereo;
35
+ _sampleRate = 48000 ;
36
+ _pacer = -1 ;
35
37
}
36
38
37
39
PWMAudio::~PWMAudio () {
@@ -63,7 +65,7 @@ bool PWMAudio::setStereo(bool stereo) {
63
65
return true ;
64
66
}
65
67
66
- bool PWMAudio::setFrequency (int newFreq) {
68
+ bool PWMAudio::setPWMFrequency (int newFreq) {
67
69
_freq = newFreq;
68
70
69
71
// Figure out the scale factor for PWM values
@@ -92,6 +94,17 @@ bool PWMAudio::setFrequency(int newFreq) {
92
94
return true ;
93
95
}
94
96
97
+ bool PWMAudio::setFrequency (int frequency) {
98
+ if (_pacer < 0 ) {
99
+ return false ;
100
+ }
101
+ uint16_t _pacer_D, _pacer_N;
102
+ /* Flip fraction(N for D, D for N) because we are using it as sys_clk * fraction(mechanic of dma_timer_set_fraction) for smaller than sys_clk values*/
103
+ find_pacer_fraction (frequency, &_pacer_D, &_pacer_N);
104
+ dma_timer_set_fraction (_pacer, _pacer_N, _pacer_D);
105
+ return true ;
106
+ }
107
+
95
108
void PWMAudio::onTransmit (void (*fn)(void )) {
96
109
_cb = fn;
97
110
if (_running) {
@@ -117,12 +130,25 @@ bool PWMAudio::begin() {
117
130
_bufferWords = 16 ;
118
131
}
119
132
120
- setFrequency (_freq);
133
+ setPWMFrequency (_freq);
134
+
135
+ /* Calculate and set the DMA pacer timer. This timer will pull data on a fixed sample rate. So the actual PWM frequency can be higher or lower.*/
136
+ _pacer = dma_claim_unused_timer (false );
137
+ /* When no unused timer is found, return*/
138
+ if (_pacer < 0 ) {
139
+ return false ;
140
+ }
141
+ uint16_t _pacer_D = 0 ;
142
+ uint16_t _pacer_N = 0 ;
143
+ /* Flip fraction(N for D, D for N) because we are using it as sys_clk * fraction(mechanic of dma_timer_set_fraction) for smaller than sys_clk values*/
144
+ find_pacer_fraction (_sampleRate, &_pacer_D, &_pacer_N);
145
+ dma_timer_set_fraction (_pacer, _pacer_N, _pacer_D);
146
+ int _pacer_dreq = dma_get_timer_dreq (_pacer);
121
147
122
148
uint32_t ccAddr = PWM_BASE + PWM_CH0_CC_OFFSET + pwm_gpio_to_slice_num (_pin) * 20 ;
123
149
124
150
_arb = new AudioBufferManager (_buffers, _bufferWords, 0x80008000 , OUTPUT, DMA_SIZE_32);
125
- if (!_arb->begin (pwm_get_dreq ( pwm_gpio_to_slice_num (_pin)) , (volatile void *)ccAddr)) {
151
+ if (!_arb->begin (_pacer_dreq , (volatile void *)ccAddr)) {
126
152
_running = false ;
127
153
delete _arb;
128
154
_arb = nullptr ;
@@ -143,6 +169,9 @@ void PWMAudio::end() {
143
169
}
144
170
delete _arb;
145
171
_arb = nullptr ;
172
+
173
+ dma_timer_unclaim (_pacer);
174
+ _pacer = -1 ;
146
175
}
147
176
}
148
177
@@ -219,3 +248,44 @@ size_t PWMAudio::write(const uint8_t *buffer, size_t size) {
219
248
}
220
249
return writtenSize;
221
250
}
251
+
252
+ void PWMAudio::find_pacer_fraction (int target, uint16_t *numerator, uint16_t *denominator) {
253
+ const uint16_t max = 0xFFFF ;
254
+
255
+ /* Cache last results so we dont have to recalculate*/
256
+ static int last_target;
257
+ static uint16_t bestNum;
258
+ static uint16_t bestDenom;
259
+ /* Check if we can load the previous values*/
260
+ if (target == last_target) {
261
+ *numerator = bestNum;
262
+ *denominator = bestDenom;
263
+ return ;
264
+ }
265
+
266
+ float targetRatio = (float )F_CPU / target;
267
+ float lowestError = HUGE_VALF;
268
+
269
+ for (uint16_t denom = 1 ; denom < max; denom++) {
270
+ uint16_t num = (int )((targetRatio * denom) + 0 .5f ); /* Calculate numerator, rounding to nearest integer*/
271
+
272
+ /* Check if numerator is within bounds*/
273
+ if (num > 0 && num < max) {
274
+ float actualRatio = (float )num / denom;
275
+ float error = fabsf (actualRatio - targetRatio);
276
+
277
+ if (error < lowestError) {
278
+ bestNum = num;
279
+ bestDenom = denom;
280
+ lowestError = error;
281
+ if (error == 0 ) {
282
+ break ;
283
+ }
284
+ }
285
+ }
286
+ }
287
+
288
+ last_target = target;
289
+ *numerator = bestNum;
290
+ *denominator = bestDenom;
291
+ }
0 commit comments