Skip to content

Commit fc894fb

Browse files
authored
PWMAudio low bitrate whine fix (DMA pacing timer) (#1996)
1 parent 929ee98 commit fc894fb

File tree

2 files changed

+90
-5
lines changed

2 files changed

+90
-5
lines changed

libraries/PWMAudio/src/PWMAudio.cpp

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ PWMAudio::PWMAudio(pin_size_t pin, bool stereo) {
3232
_buffers = 8;
3333
_bufferWords = 0;
3434
_stereo = stereo;
35+
_sampleRate = 48000;
36+
_pacer = -1;
3537
}
3638

3739
PWMAudio::~PWMAudio() {
@@ -63,7 +65,7 @@ bool PWMAudio::setStereo(bool stereo) {
6365
return true;
6466
}
6567

66-
bool PWMAudio::setFrequency(int newFreq) {
68+
bool PWMAudio::setPWMFrequency(int newFreq) {
6769
_freq = newFreq;
6870

6971
// Figure out the scale factor for PWM values
@@ -92,6 +94,17 @@ bool PWMAudio::setFrequency(int newFreq) {
9294
return true;
9395
}
9496

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+
95108
void PWMAudio::onTransmit(void(*fn)(void)) {
96109
_cb = fn;
97110
if (_running) {
@@ -117,12 +130,25 @@ bool PWMAudio::begin() {
117130
_bufferWords = 16;
118131
}
119132

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);
121147

122148
uint32_t ccAddr = PWM_BASE + PWM_CH0_CC_OFFSET + pwm_gpio_to_slice_num(_pin) * 20;
123149

124150
_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)) {
126152
_running = false;
127153
delete _arb;
128154
_arb = nullptr;
@@ -143,6 +169,9 @@ void PWMAudio::end() {
143169
}
144170
delete _arb;
145171
_arb = nullptr;
172+
173+
dma_timer_unclaim(_pacer);
174+
_pacer = -1;
146175
}
147176
}
148177

@@ -219,3 +248,44 @@ size_t PWMAudio::write(const uint8_t *buffer, size_t size) {
219248
}
220249
return writtenSize;
221250
}
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+
}

libraries/PWMAudio/src/PWMAudio.h

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,21 @@ class PWMAudio : public Stream {
2929
virtual ~PWMAudio();
3030

3131
bool setBuffers(size_t buffers, size_t bufferWords);
32-
bool setFrequency(int newFreq);
32+
/*Sets the frequency of the PWM in hz*/
33+
bool setPWMFrequency(int newFreq);
34+
/*Sets the sample rate frequency in hz*/
35+
bool setFrequency(int frequency);
3336
bool setPin(pin_size_t pin);
3437
bool setStereo(bool stereo = true);
3538

3639
bool begin(long sampleRate) {
37-
setFrequency(sampleRate);
40+
_sampleRate = sampleRate;
41+
return begin();
42+
}
43+
44+
bool begin(long sampleRate, long PWMfrequency) {
45+
setPWMFrequency(PWMfrequency);
46+
_sampleRate = sampleRate;
3847
return begin();
3948
}
4049

@@ -70,6 +79,9 @@ class PWMAudio : public Stream {
7079
bool _stereo;
7180

7281
int _freq;
82+
int _sampleRate;
83+
84+
int _pacer;
7385

7486
size_t _buffers;
7587
size_t _bufferWords;
@@ -84,4 +96,7 @@ class PWMAudio : public Stream {
8496
void (*_cb)();
8597

8698
AudioBufferManager *_arb;
99+
100+
/*An accurate but brute force method to find 16bit numerator and denominator.*/
101+
void find_pacer_fraction(int target, uint16_t *numerator, uint16_t *denominator);
87102
};

0 commit comments

Comments
 (0)