Skip to content

Commit 2f2768a

Browse files
author
Daniel L. Christensen
committed
Implement accurate pwm frequency by fixing prescaler and period.
1 parent c033f15 commit 2f2768a

File tree

1 file changed

+38
-35
lines changed

1 file changed

+38
-35
lines changed

cores/arduino/wiring_pwm.cpp

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -41,71 +41,68 @@ static void syncTCC(Tcc* TCCx) {
4141
}
4242
#endif
4343

44-
extern uint32_t toneMaxFrequency;
45-
4644
#if defined(__SAMD51__)
47-
#define PER_COUNTER 0xFF
45+
#define MAX_PERIOD 0xFF
4846
#else
49-
#define PER_COUNTER 0xFFFF
47+
#define MAX_PERIOD 0xFFFF
5048
#endif
5149

52-
static inline uint32_t calcPrescaler(uint32_t frequency)
50+
static inline unsigned long calcPrescaler(uint32_t frequency, uint32_t &period)
5351
{
5452
//if it's a rest, set to 1Hz (below audio range)
5553
frequency = (frequency > 0 ? frequency : 1);
5654
//
5755
// Calculate best prescaler divider and comparator value for a 16 bit TC peripheral
58-
uint32_t prescalerConfigBits;
59-
uint32_t ccValue;
56+
unsigned long prescalerConfigVal;
6057

61-
ccValue = toneMaxFrequency / frequency - 1;
62-
prescalerConfigBits = TC_CTRLA_PRESCALER_DIV1;
58+
period = F_CPU / frequency - 1;
59+
prescalerConfigVal = TC_CTRLA_PRESCALER_DIV1_Val;
6360

6461
uint8_t i = 0;
6562

66-
while (ccValue > PER_COUNTER)
63+
while (period > MAX_PERIOD)
6764
{
68-
ccValue = toneMaxFrequency / frequency / (2 << i) - 1;
69-
i++;
7065
if (i == 4 || i == 6 || i == 8) //DIV32 DIV128 and DIV512 are not available
7166
i++;
67+
period = F_CPU / frequency / (2 << i) - 1;
68+
i++;
7269
}
7370

7471
switch (i - 1)
7572
{
7673
case 0:
77-
prescalerConfigBits = TC_CTRLA_PRESCALER_DIV2;
74+
prescalerConfigVal = TC_CTRLA_PRESCALER_DIV2_Val;
7875
break;
7976

8077
case 1:
81-
prescalerConfigBits = TC_CTRLA_PRESCALER_DIV4;
78+
prescalerConfigVal = TC_CTRLA_PRESCALER_DIV4_Val;
8279
break;
8380

8481
case 2:
85-
prescalerConfigBits = TC_CTRLA_PRESCALER_DIV8;
82+
prescalerConfigVal = TC_CTRLA_PRESCALER_DIV8_Val;
8683
break;
8784

8885
case 3:
89-
prescalerConfigBits = TC_CTRLA_PRESCALER_DIV16;
86+
prescalerConfigVal = TC_CTRLA_PRESCALER_DIV16_Val;
9087
break;
9188

9289
case 5:
93-
prescalerConfigBits = TC_CTRLA_PRESCALER_DIV64;
90+
prescalerConfigVal = TC_CTRLA_PRESCALER_DIV64_Val;
9491
break;
9592

9693
case 7:
97-
prescalerConfigBits = TC_CTRLA_PRESCALER_DIV256;
94+
prescalerConfigVal = TC_CTRLA_PRESCALER_DIV256_Val;
9895
break;
9996

10097
case 9:
101-
prescalerConfigBits = TC_CTRLA_PRESCALER_DIV1024;
98+
prescalerConfigVal = TC_CTRLA_PRESCALER_DIV1024_Val;
10299
break;
103100

104101
default:
105102
break;
106103
}
107104

108-
return prescalerConfigBits;
105+
return prescalerConfigVal;
109106
}
110107

111108
void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
@@ -116,10 +113,11 @@ void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
116113
#if defined(__SAMD51__)
117114
if (attr & (PIN_ATTR_PWM_E | PIN_ATTR_PWM_F | PIN_ATTR_PWM_G))
118115
{
119-
duty = mapResolution(duty, 10, 8);
120-
uint32_t prescalerConfigBits;
116+
unsigned long prescalerConfigVal;
117+
uint32_t period;
121118

122-
prescalerConfigBits = calcPrescaler(frequency);
119+
prescalerConfigVal = calcPrescaler(frequency, period);
120+
duty = map(duty, 0, 1024, 0, period);
123121

124122
uint32_t tcNum = GetTCNumber(pinDesc.ulPWMChannel);
125123
uint8_t tcChannel = GetTCChannelNumber(pinDesc.ulPWMChannel);
@@ -149,7 +147,7 @@ void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
149147
while (TCx->COUNT8.SYNCBUSY.bit.ENABLE)
150148
;
151149
// Set Timer counter Mode to 8 bits, normal PWM,
152-
TCx->COUNT8.CTRLA.reg = TC_CTRLA_MODE_COUNT8 | prescalerConfigBits;
150+
TCx->COUNT8.CTRLA.reg = TC_CTRLA_MODE_COUNT8 | TC_CTRLA_PRESCALER(prescalerConfigVal);
153151
TCx->COUNT8.WAVE.reg = TC_WAVE_WAVEGEN_NPWM;
154152

155153
while (TCx->COUNT8.SYNCBUSY.bit.CC0)
@@ -158,8 +156,8 @@ void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
158156
TCx->COUNT8.CC[tcChannel].reg = (uint8_t)duty;
159157
while (TCx->COUNT8.SYNCBUSY.bit.CC0)
160158
;
161-
// Set PER to maximum counter value (resolution : 0xFF)
162-
TCx->COUNT8.PER.reg = 0xFF;
159+
// Set PER to calculated period
160+
TCx->COUNT8.PER.reg = period;
163161
while (TCx->COUNT8.SYNCBUSY.bit.PER)
164162
;
165163
// Enable TCx
@@ -181,7 +179,7 @@ void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
181179
while (TCCx->SYNCBUSY.bit.ENABLE)
182180
;
183181
// Set prescaler
184-
TCCx->CTRLA.reg = prescalerConfigBits | TCC_CTRLA_PRESCSYNC_GCLK;
182+
TCCx->CTRLA.reg = TC_CTRLA_PRESCALER(prescalerConfigVal) | TCC_CTRLA_PRESCSYNC_GCLK;
185183

186184
// Set TCx as normal PWM
187185
TCCx->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;
@@ -194,8 +192,8 @@ void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
194192
TCCx->CC[tcChannel].reg = (uint32_t)duty;
195193
while (TCCx->SYNCBUSY.bit.CC0 || TCCx->SYNCBUSY.bit.CC1)
196194
;
197-
// Set PER to maximum counter value (resolution : 0xFF)
198-
TCCx->PER.reg = 0xFF;
195+
// Set PER to calculated period
196+
TCCx->PER.reg = period;
199197
while (TCCx->SYNCBUSY.bit.PER)
200198
;
201199
// Enable TCCx
@@ -210,10 +208,10 @@ void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
210208

211209
if ((attr & PIN_ATTR_PWM) == PIN_ATTR_PWM)
212210
{
213-
duty = mapResolution(duty, 10, 16);
214-
uint32_t prescalerConfigBits;
211+
uint32_t prescalerConfigVal;
212+
uint32_t period;
215213

216-
prescalerConfigBits = calcPrescaler(frequency);
214+
prescalerConfigVal = calcPrescaler(frequency, period);
217215

218216
uint32_t tcNum = GetTCNumber(pinDesc.ulPWMChannel);
219217
uint8_t tcChannel = GetTCChannelNumber(pinDesc.ulPWMChannel);
@@ -259,13 +257,14 @@ void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
259257
// Set PORT
260258
if (tcNum >= TCC_INST_NUM)
261259
{
260+
duty = mapResolution(duty, 10, 16);
262261
// -- Configure TC
263262
Tc *TCx = (Tc *)GetTC(pinDesc.ulPWMChannel);
264263
// Disable TCx
265264
TCx->COUNT16.CTRLA.bit.ENABLE = 0;
266265
syncTC_16(TCx);
267266
// Set Timer counter Mode to 16 bits, normal PWM
268-
TCx->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16 | TC_CTRLA_WAVEGEN_NPWM | prescalerConfigBits;
267+
TCx->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16 | TC_CTRLA_WAVEGEN_NPWM | TC_CTRLA_PRESCALER(prescalerConfigVal);
269268
syncTC_16(TCx);
270269
// Set the initial value
271270
TCx->COUNT16.CC[tcChannel].reg = (uint32_t)duty;
@@ -276,19 +275,23 @@ void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
276275
}
277276
else
278277
{
278+
duty = map(duty, 0, 1024, 0, period);
279279
// -- Configure TCC
280280
Tcc *TCCx = (Tcc *)GetTC(pinDesc.ulPWMChannel);
281281
// Disable TCCx
282282
TCCx->CTRLA.bit.ENABLE = 0;
283283
syncTCC(TCCx);
284+
// Set prescaler
285+
TCCx->CTRLA.bit.PRESCALER = prescalerConfigVal;
286+
syncTCC(TCCx);
284287
// Set TCCx as normal PWM
285288
TCCx->WAVE.reg |= TCC_WAVE_WAVEGEN_NPWM;
286289
syncTCC(TCCx);
287290
// Set the initial value
288291
TCCx->CC[tcChannel].reg = (uint32_t)duty;
289292
syncTCC(TCCx);
290-
// Set PER to maximum counter value (resolution : 0xFFFF)
291-
TCCx->PER.reg = 0xFFFF;
293+
// Set PER to calculated period
294+
TCCx->PER.reg = period;
292295
syncTCC(TCCx);
293296
// Enable TCCx
294297
TCCx->CTRLA.bit.ENABLE = 1;

0 commit comments

Comments
 (0)