I am relatively new to writing arduino code so I have had great difficulty sorting out what appear to be errors in the documentation from misconceptions in my own understanding of what I should expect from code execution.
In writing motor driver code for a project, I needed to get the PWM frequency up above the audible range but still stay under the maximum frequency that my H-bridge would support. I am using Timer1 to manage long, variable delays with interrupts to dial down the motor speed, while depending on an external interrupt to turn the motor off. The sequence is to set the motor to max speed, then (on a Timer1 interrupt) cut the PWM duty cycle by 50%, and when the external interrupt occurs, set the duty cycle to 0.
So I was trying to use Timer2 in the phase correct PWM mode with OCR2A as TOP to set the frequency by using the divide by 8 prescaler and setting OCR2A to 60 to achieve a PWM frequency of 16,393.4Hz.
What surprised me was that when the ISR triggered by the external interrupt set the duty cycle to zero with an analogWrite(3,0), OCR2B did not change even though the motor shut off. When I looked at TCCR2A I discovered that whenever the command was for zero duty cycle, COM2B1 was cleared, apparently by the hardware microcode, but OCR2B was left unchanged. This seems to disable the output (pin3) (possibly putting it into a tri-state mode). When I tried Timer0 I found exactly the same behavior so it is clearly a feature rather than a bug. What I suspect, but am not equipped to measure, is that in this mode there is no way to get a 1/OCR2A PWM duty cycle.
Here is the test sketch demonstrating the behavior (you can change all of the Timer2-unique variables into Timer0 equivalents and see the behavior on Timer0 (independent of whether you are truly masochistic or just a skeptic).
int sample;
void setup() {
//initialize Timer2 in phase correct PWM Mode 5 { WGM2[2:0] = B101 } using TOP (OCR2A) to control the PWM frequency
//and OCR2B to set the PWM duty cycle [implicit in analogWrite(pin,duty-cycle x OCR2A)]
//set mode to Toggle on Compare Match" by setting the COM2A[1:0] bits to B01
//set output to Clear OC2B (pin3) on Compare Match when up-counting and Set OC2B on Compare Match when down-counting
//by setting COM2B[1:0] = B10
Serial.begin(115200);//use relatively high serial port speed to speed things up
delay(500);
pinMode(3, OUTPUT); //connect pin3 to Enable pin on external H-bridge motor controller
TCCR2A = 0;
TCCR2B = 0;
TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM20);
TCCR2B = _BV(WGM22) | _BV(CS21);
OCR2A = 60;//PWM values must be in [0,60]
sample = 1;
analogWrite(3, 60); //turn motor on at 100% PWM duty cycle
Serial.println("\tcase\tTCCR2A\tTCCR2B\tOCR2A\tOCR2B");
Print1();
sample = 2;
analogWrite(3, 30); //reduce motor drive to 50% duty cycle
Print1();
sample = 3;
analogWrite(3, 0); //turn motor off by reducing duty cycle to 0
Print1();
sample = 4;
analogWrite(3, 1); //turn motor on to minimum non-zero duty cycle
Print1();
sample = 5;
analogWrite(3, 0); //again turn motor off
Print1();
sample = 6;
OCR2B = 0;//force OCR2B to zero
Print1();
sample = 7;
analogWrite(3, 1); //turn motor on to minimum non-zero duty cycle
Print1();
}
void loop() {
// put your main code here, to run repeatedly:
}
void Print1() {
Serial.print("\t"); Serial.print(sample);
Serial.print("\t"); Serial.print(TCCR2A, BIN);
Serial.print("\t"); Serial.print(TCCR2B, BIN);
Serial.print("\t"); Serial.print(OCR2A, BIN);
Serial.print("\t"); Serial.print(OCR2B, BIN);
Serial.println(" ");//add CR after the last print
}
And here are the printed results for the first 5 cases:
case TCCR2A TCCR2B OCR2A OCR2B
analogWrite(3,60) 1 1100001 1010 111100 111100
analogWrite(3,30) 2 1100001 1010 111100 11110
analogWrite(3,0) 3 1000001 1010 111100 11110
analogWrite(3,1) 4 1100001 1010 111100 1
analogWrite(3,0) 5 1000001 1010 111100 1
Note that commanding PWM = 0 changes only COM2B1 from 1 to 0 and does not change OCR2B
And commanding any other (non zero) PWM value restores COM2B1 to 1
If you set OCR2B to zero, it will change nothing in any of the other registers.
Can someone confirm that it is a special case that the microcode discovers and acts on in realtime?
Or is it something behind the curtain in the compiler that adds code to check for the zero duty cycle case?
And who do I engage to fix the MANY typos in the data sheet that deal with the PWM behaviors. I see that some of them have been picked up and fixed between 2016 and the 2018 release but some new ones were introduced and many were not fixed. Or it could still be that I don't understand what they are trying to tell me.