Skip to content

Commit 8398d1a

Browse files
authored
Merge pull request arduino#23 from dachristensen/pwmfix
Accurate PWM frequency for SAMD21 Thanks for you PR!
2 parents 94f4fb4 + dc337bc commit 8398d1a

File tree

1 file changed

+49
-35
lines changed

1 file changed

+49
-35
lines changed

cores/arduino/wiring_pwm.cpp

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -41,71 +41,79 @@ 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+
// API uses 10 bit resolution
51+
#define PWM_API_RESOLUTION 10
52+
53+
static inline unsigned long calcPrescaler(uint32_t frequency, uint32_t &period)
5354
{
5455
//if it's a rest, set to 1Hz (below audio range)
5556
frequency = (frequency > 0 ? frequency : 1);
5657
//
5758
// Calculate best prescaler divider and comparator value for a 16 bit TC peripheral
58-
uint32_t prescalerConfigBits;
59-
uint32_t ccValue;
59+
unsigned long prescalerConfigVal;
6060

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

6464
uint8_t i = 0;
6565

66-
while (ccValue > PER_COUNTER)
66+
while (period > MAX_PERIOD)
6767
{
68-
ccValue = toneMaxFrequency / frequency / (2 << i) - 1;
69-
i++;
7068
if (i == 4 || i == 6 || i == 8) //DIV32 DIV128 and DIV512 are not available
7169
i++;
70+
period = F_CPU / frequency / (2 << i) - 1;
71+
i++;
7272
}
7373

74+
#if defined(__SAMD51__)
75+
period = MAX_PERIOD;
76+
#else
77+
// Ensure that our period does not erode the API resolution
78+
if(period < (1<<PWM_API_RESOLUTION))
79+
period = (1<<PWM_API_RESOLUTION) - 1;
80+
#endif
81+
7482
switch (i - 1)
7583
{
7684
case 0:
77-
prescalerConfigBits = TC_CTRLA_PRESCALER_DIV2;
85+
prescalerConfigVal = TC_CTRLA_PRESCALER_DIV2_Val;
7886
break;
7987

8088
case 1:
81-
prescalerConfigBits = TC_CTRLA_PRESCALER_DIV4;
89+
prescalerConfigVal = TC_CTRLA_PRESCALER_DIV4_Val;
8290
break;
8391

8492
case 2:
85-
prescalerConfigBits = TC_CTRLA_PRESCALER_DIV8;
93+
prescalerConfigVal = TC_CTRLA_PRESCALER_DIV8_Val;
8694
break;
8795

8896
case 3:
89-
prescalerConfigBits = TC_CTRLA_PRESCALER_DIV16;
97+
prescalerConfigVal = TC_CTRLA_PRESCALER_DIV16_Val;
9098
break;
9199

92100
case 5:
93-
prescalerConfigBits = TC_CTRLA_PRESCALER_DIV64;
101+
prescalerConfigVal = TC_CTRLA_PRESCALER_DIV64_Val;
94102
break;
95103

96104
case 7:
97-
prescalerConfigBits = TC_CTRLA_PRESCALER_DIV256;
105+
prescalerConfigVal = TC_CTRLA_PRESCALER_DIV256_Val;
98106
break;
99107

100108
case 9:
101-
prescalerConfigBits = TC_CTRLA_PRESCALER_DIV1024;
109+
prescalerConfigVal = TC_CTRLA_PRESCALER_DIV1024_Val;
102110
break;
103111

104112
default:
105113
break;
106114
}
107115

108-
return prescalerConfigBits;
116+
return prescalerConfigVal;
109117
}
110118

111119
void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
@@ -116,10 +124,11 @@ void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
116124
#if defined(__SAMD51__)
117125
if (attr & (PIN_ATTR_PWM_E | PIN_ATTR_PWM_F | PIN_ATTR_PWM_G))
118126
{
119-
duty = mapResolution(duty, 10, 8);
120-
uint32_t prescalerConfigBits;
127+
unsigned long prescalerConfigVal;
128+
uint32_t period;
121129

122-
prescalerConfigBits = calcPrescaler(frequency);
130+
prescalerConfigVal = calcPrescaler(frequency, period);
131+
duty = map(duty, 0, (1<<PWM_API_RESOLUTION), 0, period);
123132

124133
uint32_t tcNum = GetTCNumber(pinDesc.ulPWMChannel);
125134
uint8_t tcChannel = GetTCChannelNumber(pinDesc.ulPWMChannel);
@@ -149,7 +158,7 @@ void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
149158
while (TCx->COUNT8.SYNCBUSY.bit.ENABLE)
150159
;
151160
// Set Timer counter Mode to 8 bits, normal PWM,
152-
TCx->COUNT8.CTRLA.reg = TC_CTRLA_MODE_COUNT8 | prescalerConfigBits;
161+
TCx->COUNT8.CTRLA.reg = TC_CTRLA_MODE_COUNT8 | TC_CTRLA_PRESCALER(prescalerConfigVal);
153162
TCx->COUNT8.WAVE.reg = TC_WAVE_WAVEGEN_NPWM;
154163

155164
while (TCx->COUNT8.SYNCBUSY.bit.CC0)
@@ -158,8 +167,8 @@ void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
158167
TCx->COUNT8.CC[tcChannel].reg = (uint8_t)duty;
159168
while (TCx->COUNT8.SYNCBUSY.bit.CC0)
160169
;
161-
// Set PER to maximum counter value (resolution : 0xFF)
162-
TCx->COUNT8.PER.reg = 0xFF;
170+
// Set PER to calculated period
171+
TCx->COUNT8.PER.reg = period;
163172
while (TCx->COUNT8.SYNCBUSY.bit.PER)
164173
;
165174
// Enable TCx
@@ -181,7 +190,7 @@ void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
181190
while (TCCx->SYNCBUSY.bit.ENABLE)
182191
;
183192
// Set prescaler
184-
TCCx->CTRLA.reg = prescalerConfigBits | TCC_CTRLA_PRESCSYNC_GCLK;
193+
TCCx->CTRLA.reg = TC_CTRLA_PRESCALER(prescalerConfigVal) | TCC_CTRLA_PRESCSYNC_GCLK;
185194

186195
// Set TCx as normal PWM
187196
TCCx->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;
@@ -194,8 +203,8 @@ void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
194203
TCCx->CC[tcChannel].reg = (uint32_t)duty;
195204
while (TCCx->SYNCBUSY.bit.CC0 || TCCx->SYNCBUSY.bit.CC1)
196205
;
197-
// Set PER to maximum counter value (resolution : 0xFF)
198-
TCCx->PER.reg = 0xFF;
206+
// Set PER to calculated period
207+
TCCx->PER.reg = period;
199208
while (TCCx->SYNCBUSY.bit.PER)
200209
;
201210
// Enable TCCx
@@ -210,10 +219,10 @@ void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
210219

211220
if ((attr & PIN_ATTR_PWM) == PIN_ATTR_PWM)
212221
{
213-
duty = mapResolution(duty, 10, 16);
214-
uint32_t prescalerConfigBits;
222+
uint32_t prescalerConfigVal;
223+
uint32_t period;
215224

216-
prescalerConfigBits = calcPrescaler(frequency);
225+
prescalerConfigVal = calcPrescaler(frequency, period);
217226

218227
uint32_t tcNum = GetTCNumber(pinDesc.ulPWMChannel);
219228
uint8_t tcChannel = GetTCChannelNumber(pinDesc.ulPWMChannel);
@@ -259,13 +268,14 @@ void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
259268
// Set PORT
260269
if (tcNum >= TCC_INST_NUM)
261270
{
271+
duty = mapResolution(duty, 10, 16);
262272
// -- Configure TC
263273
Tc *TCx = (Tc *)GetTC(pinDesc.ulPWMChannel);
264274
// Disable TCx
265275
TCx->COUNT16.CTRLA.bit.ENABLE = 0;
266276
syncTC_16(TCx);
267277
// Set Timer counter Mode to 16 bits, normal PWM
268-
TCx->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16 | TC_CTRLA_WAVEGEN_NPWM | prescalerConfigBits;
278+
TCx->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16 | TC_CTRLA_WAVEGEN_NPWM | TC_CTRLA_PRESCALER(prescalerConfigVal);
269279
syncTC_16(TCx);
270280
// Set the initial value
271281
TCx->COUNT16.CC[tcChannel].reg = (uint32_t)duty;
@@ -276,19 +286,23 @@ void pwm(uint32_t outputPin, uint32_t frequency, uint32_t duty)
276286
}
277287
else
278288
{
289+
duty = map(duty, 0, (1<<PWM_API_RESOLUTION), 0, period);
279290
// -- Configure TCC
280291
Tcc *TCCx = (Tcc *)GetTC(pinDesc.ulPWMChannel);
281292
// Disable TCCx
282293
TCCx->CTRLA.bit.ENABLE = 0;
283294
syncTCC(TCCx);
295+
// Set prescaler
296+
TCCx->CTRLA.bit.PRESCALER = prescalerConfigVal;
297+
syncTCC(TCCx);
284298
// Set TCCx as normal PWM
285299
TCCx->WAVE.reg |= TCC_WAVE_WAVEGEN_NPWM;
286300
syncTCC(TCCx);
287301
// Set the initial value
288302
TCCx->CC[tcChannel].reg = (uint32_t)duty;
289303
syncTCC(TCCx);
290-
// Set PER to maximum counter value (resolution : 0xFFFF)
291-
TCCx->PER.reg = 0xFFFF;
304+
// Set PER to calculated period
305+
TCCx->PER.reg = period;
292306
syncTCC(TCCx);
293307
// Enable TCCx
294308
TCCx->CTRLA.bit.ENABLE = 1;

0 commit comments

Comments
 (0)