I've written some code to test the PWM function on a 32u4 (Micro Pro)
However its raised some questions.
1: The value read from the timer control register does not match the value I write to it.
2: Documentation leads me to expect differences between fast PWM and Phase correct PWM that I dont see in practise
3: I have a small - but significant - gain error on the ADC.
Can anyone shed any light on these?
The circuit is very simple - a 47k resistor from pin 9 to a .22uF capacitor to ground.
The voltage on the capacitor is read on A0.
The output on pin 9 is read with a dvm and a hantek scope.
I was expecting to see a difference between fast PWM and PCPWM as described here
https://docs.arduino.cc/tutorials/generic/secrets-of-arduino-pwm/
in that fast PWM should always produce a minimum duty cycle of 1 :255
however while I am seeing a difference in frequency I dont see that difference in duty cycle.
The duty cycle values for nOUT=0 is 0 for both fast & PC PWM
or for nOUT=255 is 1 for both fast & PC PWM
PC PWM gives dc=0.4% (ie 1/255) for nOUT=1, and dc=99.6% (ie 254/255) for nOUT=254
FAST PWM gives dc=0.8% (ie 2/255) for nOUT=1, and dc=99.2% (ie 2543/255) for nOUT=254
I'm also puzzled that while the code to manage the clock division (TCCR1B bits 0, 1,2) is effective
(as shown by the scope) the value READ from TCCR1B always shows those bits as 011.
I'm also a bit surprised that the ADC reading from the filter is slightly on the low side. The whole circuit is potentiometric so I'd not expect a gain error. Here the averaged ADC reading is 254 and should be 64*4 = 256.
Here are the results for one setting. for a divider of 8 TCCR1B shoud be xxxx x010
PWM experiment: generating vOut, measuring vIn
PWM mode: 0 = Phase correct, 1 = fast, 2 = none
PWM step size in mV is 19.14: clock divider = 8: PWM mode is 0
timer 1 PC pwm
TCCR1A is: 00000001 TCCR1B is: 00000011
nOut, mVoltsOut, ADCreading, vFilter
64, 1225, 223.25, 1215.23
64, 1225, 253.50, 1209.58
64, 1225, 254.13, 1211.66
64, 1225, 254.63, 1215.23
64, 1225, 254.19, 1211.66
/*
Program to demonstrate and test PWM as DAC
uses a simple RC filter R=47k C=.22uF on pin 9
reads the filter voltage on A0
PWM is proportional to Vcc so use Vcc as analog reference
For expected value for Vout you need to measure Vcc in mV and enter the value below.
*/
//Measure and enter true value of Vcc here
float mVcc = 4880; //mV
float offset = 0; //mV - amount actual output is high for PCPWM = 0
//Analog input and pin assignments
const byte v0Pin = A0; //Filter voltage
const byte vOutPin = 9; // use pin 9 for pwm out
//reading an ADC and converting to voltage or current
byte nTimes = 16; //number of ADC readings to be averaged
byte rDelay = 7; //msec between each reading of ADC; eg 16 * 7msec = 112msec to take each reading
float reading; //actual adc averaged
//generating PWM signal
const int divider = 8; //PWM: for pin 9 divider=1 sets pwm freq at 31,250Hz; =8 sets it to 3,906Hz
int pMode = 0; // 0 - pcPWM; 1 - fastPWM; else "normal" no PWM
int nMax = 255; // value is the duty cycle of the pwm ie 255 = always 1, 0 = always off. 2v5=128
int nMin = 0;
int stepSize = 5;
int nOut; //number to be output via PWM
float mVoltsOut; //expected pwm average output voltage in mV
float vOutcal;
void setPwmFrequency(int pin, int divisor);
void setPwmMode(int pin, int mode);
float readADC(int which); //averages a set of readings and prints results as CSV to serial; "which" selects which pin to read
void printBits(byte myByte); //to print binary value showing all bits even leading zeros.
void setup() {
Serial.begin(57600);
delay(10000); //allow time to show data on serial monitor
analogReference(DEFAULT);
Serial.println("PWM experiment: generating vOut, measuring vIn");
Serial.println("PWM mode: 0 = Phase correct, 1 = fast, 2 = none");
pinMode(vOutPin, OUTPUT); //
vOutcal = mVcc / nMax; //pwm step size in mV
Serial.print("PWM step size in mV is ");
Serial.print(vOutcal);
Serial.print(": clock divider = ");
Serial.print(divider);
Serial.print(": PWM mode is ");
Serial.println(pMode);
setPwmMode(vOutPin, pMode);
setPwmFrequency(vOutPin, divider);
}
void loop() {
delay(10000); //time to clear the monitor display
Serial.println("nOut, mVoltsOut, ADCreading, vFilter");
//for (nOut = nMin; nOut <= nMax; nOut += stepSize) { // generates a rising ramp
for (nOut = 64;;) { // 0 used for offset null; 255 full scale; for fast PWM the duty cycle is from 1 to 256, not 0-255
analogWrite(vOutPin, nOut); //generates the PWM
Serial.print(nOut);
Serial.print(", ");
mVoltsOut = (nOut * vOutcal) + offset; // average vout for pwm is nOut * Vcc/255 (for phase correct pwm)
Serial.print(mVoltsOut, 0);
Serial.print(", ");
reading = readADC(0);
Serial.print(reading);
Serial.print(", ");
Serial.println(readADC(0) * mVcc / 1024); //end of line of data
delay(1000); // time between successive readings
}
}
float readADC(int which) { //read chosen ADC, return average of nTimes readings
float nVolts = 0;
for (int i = 0; i < nTimes; i++) {
nVolts += analogRead(which);
delay(rDelay);
}
nVolts = nVolts / (float)nTimes; //find average
return (nVolts); //average adc reading of voltage as a number
}
/**
Divides a given PWM pin frequency by a divisor.
The resulting frequency is equal to the base frequency divided by
the given divisor:
- Base frequencies:
o The base frequency for pins 3, 9, 10, and 11 is 31250 Hz.
o The base frequency for pins 5 and 6 is 62500 Hz.
- Divisors:
o The divisors available on pins 5, 6, 9 and 10 are: 1, 8, 64,
256, and 1024.
o The divisors available on pins 3 and 11 are: 1, 8, 32, 64,
128, 256, and 1024.
PWM frequencies are tied together in pairs of pins. If one in a
pair is changed, the other is also changed to match:
- Pins 5 and 6 are paired on timer0
- Pins 9 and 10 are paired on timer1
- Pins 3 and 11 are paired on timer2
Note that this function will have side effects on anything else
that uses timers:
- Changes on pins 3, 5, 6, or 11 may cause the delay() and
millis() functions to stop working. Other timing-related
functions may also be affected.
- Changes on pins 9 or 10 will cause the Servo library to function
incorrectly.
Thanks to macegr of the Arduino forums for his documentation of the
PWM frequency divisors. His post can be viewed at:
https://forum.arduino.cc/index.php?topic=16612#msg121031
*/
void setPwmFrequency(int pin, int divisor) {
byte fmode;
if (pin == 5 || pin == 6 || pin == 9 || pin == 10) {
switch (divisor) {
case 1: fmode = 0x01; break;
case 8: fmode = 0x02; break;
case 64: fmode = 0x03; break;
case 256: fmode = 0x04; break;
case 1024: fmode = 0x05; break;
default: return;
}
if (pin == 5 || pin == 6) {
TCCR0B = TCCR0B & 0b11111000 | fmode;
} else {
TCCR1B = TCCR1B & 0b11111000 | fmode;
}
} //ide doesnt recognise TCCR2 for Micro
}
void setPwmMode(int pin, int mode) { // mode: 0=pcpwm, 1=fastPWM, otherwise "normal" (return that timer to default)
if (pin == 5 || pin == 6) { //Timer 0: CAUTION may affect delay and millis timing.
if (mode == 0) { //pcpwm
TCCR0A = TCCR0A | 0b00000001; // set WGM0 - 8 bit PWM
TCCR0B = TCCR0B & 0b11100111; // clear WGM03, WGM02 to set pcpwm
} else if (mode == 1) { //fast pwm
TCCR0A = TCCR0A | 0b00000001; // set WGM0 - 8 bit PWM
TCCR0B = TCCR0B | 0b00001000; //set WGM02 for fastpwm
} else { // normal
TCCR0A = TCCR0A & 0b11111110; // clear WGM0 - normal
TCCR0B = TCCR0B & 0b11100111; // clear WGM03, WGM02
}
} else { //Timer 1
if (mode == 0) { //pcpwm
Serial.println("timer 1 PC pwm");
TCCR1A = TCCR1A | 0b00000001; // set WGM11 - 8 bit PWM
TCCR1B = TCCR1B & 0b11100111; // clear WGM12, WGM13 to set pcpwm
} else if (mode == 1) { //fast pwm
Serial.println("timer 1 fast pwm");
TCCR1A = TCCR1A | 0b00000001; // set WGM11 - 8 bit PWM
TCCR1B = TCCR1B | 0b00001000; //set fast pwm
} else { // normal
Serial.println("timer 1 no pwm");
TCCR1A = TCCR1A & 0b11111110; // clear WGM11 - normal
TCCR1B = TCCR1B & 0b11100111; // clear WGM12, WGM13
}
// print TCCR1 values as binary
Serial.print("TCCR1A is: ");
printBits(TCCR1A);
Serial.print(" TCCR1B is: ");
printBits(TCCR1B);
Serial.println();
}
}
void printBits(byte myByte) {
for (byte mask = 0x80; mask; mask >>= 1) {
if (mask & myByte)
Serial.print('1');
else
Serial.print('0');
}
}
