PWM on 32U4; some puzzles

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');
  }
}

Mystery partly - solved:

The voltage measured by the meter (conn1) when the output is HIGH is very close to Vcc (conn3) - so why does the adc not read 1023?

Because the 10M oscilloscope probe attached to conn2 is acting as a potential divider
1024 * 10000/10047 = 1019; the 5 LSB error I was seeing.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.