PWM on pins 5 + 6

Hi~ I'm trying to track down some inconsistent PWM behavior between 2 DC motors (using 2 H-bridges with the enable pins attached to pins 5 + 10). I saw the note below in the language reference for 'analogWrite'. What does 'higher-than-expected duty cycles' mean exactly and how would it impact PWM? - does it mean the motor will be driven faster (i.e. get more pulses)? I would like the motor control to be as consistent as possible. I'm using interrupts to read encoders on each motor, etc. and the count on one motor gets way out of whack.

The PWM outputs generated on pins 5 and 6 will have higher-than-expected duty cycles. This is because of interactions with the millis() and delay() functions, which share the same internal timer used to generate those PWM outputs.

tx!

--Roy

It means that the motor will be on a higher percentage of the time, at least at low duty cycles (i.e. a call to analogWrite(5, 0) will not turn the motor off completely).

Thanks. This is an XY plotter type project and I am using @ 60- 90 PWM values. I have been seeing small but noticeable differences in encoder results with each axis and haven't been able to track down the reason (of which there could be many, I realize: small differences in the drive systems, etc.).

Do you think pins 5 + 6 behave differently enough from the other PWM pins that it would be inadvisable to use them for this type of task (I have an etched PCB with the Boarduino mounted on it so it's a bit of a pain to cut the traces and add some jumpers- but I would certainly do it if the duty cycles differ enough).

--Roy

It means that the motor will be on a higher percentage of the time, at least at low duty cycles (i.e. a call to analogWrite(5, 0) will not turn the motor off completely).

Can you elaborate on this slightly? I've been looking through the code and so far I haven't seen any reason why using Timer0 for millis() would affect PWM operation beyond requiring fast PWM rather than phase-correct (which have the same duty cycles but different frequencies).

  • Ben

roy: it's possible this could be the problem. To be honest, I haven't looked at the PWM outputs under a scope, so I don't know if pins 5 and 6 differ from the others at higher PWM values. I should though - or maybe someone else is curious enough to take a look?

bens: I'm not sure I understand / remember the details, but I believe that while the overflow interrupt handler is executing, the pin output stays high. Perhaps the comparison between the counter value and the compare value is done via an interrupt and therefore blocked while another interrupt is executing? I'm not really sure.

bens: I'm not sure I understand / remember the details, but I believe that while the overflow interrupt handler is executing, the pin output stays high. Perhaps the comparison between the counter value and the compare value is done via an interrupt and therefore blocked while another interrupt is executing? I'm not really sure.

It sounds to me like this won't be an issue. The mega168 handles the PWM signal completely in hardware, so it shouldn't be possible to block the transition by occupying the CPU. For example, you should be able to set a PWM running and then enter an infinite while loop with interrupts globally disabled, and the PWM signal will carry on just fine.

If I can get around to it I'll try this out on my scope later today. I don't have an Arduino, but I have some mega168s I can program with the Arduino code.

  • Ben

I didn't think it would be a problem either, but if you do an analogWrite(5, 0); you don't get a 0 duty cycle. There may be another explanation, though. Anything you can figure out would be much appreciated.

I didn't get a chance to look at it today, but I'll see if I can remember to check it out tomorrow. I wonder if what you're seeing are the glitches that AVRs have when you try to generate a 0% duty cycle in fast PWM mode. Perhaps phase-corrected PWMs don't suffer from this glitch problem like fast PWMs.

  • Ben

I did some testing with an oscilloscope and a Baby Orangutan using both analogWrite() and direct manipulation of timer registers. What I found is when you use phase-correct PWM mode, as is used for timers 1 and 2 on Arduinos, you get clean 0% up through 100% duty cycles, with 0% at 0 and 100% at 255. This means that your duty cycle is your analogWrite parameter x divided by 255:

phase-correct PWM duty cycle = x / 255.

When you use fast PWM mode, as is used for timer 0 on Arduinos, you can't get 0% duty cycle. Instead, you get a 1/256 duty cycle for 0, a 2/256 duty cycle for 1, ..., all the way up through a 256/256 (100%) duty cycle at 255. This means that your duty cycle is your analogWrite parameter x plus 1 divided by 256:

fast PWM duty cycle = (x + 1) / 256

What this means is that for low duty cycles, you will be around 0.39% higher than what you might expect if you are used to the way phase-correct duty cycles work. This would probably be one reason why phase correct PWMs are recommended for motor control. The only drawback is that phase-correct PWMs have half the frequency of fast PWMs, which can make it hard to achieve ultrasonic motor control (unless your motor drivers can handle 32 kHz).

  • Ben

You were comparing phase correct and fast pwm on the same timer, right? So it's not caused by the interrupt handler?

It's definitely not caused by the interrupt handler. I did my tests with interrupts disabled. I looked at the outputs of all the timers when using various arguments to analogWrite(), and I did a direct comparison of phase correct PWM mode to fast PWM mode using timer 2. I got the same behavior from timer 2 in fast PWM mode and with interrupts disabled as I did from timer 0 as the Arduino software configures it.

The PWM output is done entirely in hardware, so nothing you do in software (short of manipulating that PWM's control registers or the PWM pin's data direction state) will affect it.

But I definitely learned something useful from this. From my prior work with AVRs, I thought the AVR's PWMs would always glitch at 0% duty cycle and hence could never achieve both a clean 0% and 100%. Now I know that this is only a problem for fast PWMs and not for phase-correct PWMs.

  • Ben

Cool. Thanks for doing this. I guess it's time to look into using phase-correct PWM for timer 0 (although I'm not sure it's possible to do that without changing the timing based on the PWM duty cycle).

Cool. Thanks for doing this. I guess it's time to look into using phase-correct PWM for timer 0 (although I'm not sure it's possible to do that without changing the timing based on the PWM duty cycle).

Hmmm... I was about to try and use the PWM pins 5 & 6 in a project. What is the liklihood of this being resolved/fixed any time soon?

Maybe just avoid PWM pins 5&6 for a while

The difference between PWMs on pins 5 and 6 and the other PWMs is almost negligible. If instead of 0% duty cycle you have 0.39% duty cycle, this won't make a significant difference in most applications, and you can always force a clean zero by just outputing 0 V when you want a 0% duty cycle and using analogWrite() when you want something higher than 0%.

To make the 5/6 PMWs consistant with the rest, you'd have to run timer0 in phase-correct mode, which means it counts up and then counts back down (rather than overflowing). This would make using timer0 for general-purpose timing difficult. I expect this isn't something that will be "fixed" soon, but I could be wrong.

I should note that when doing motor control applications I pretty much always use fast PWMs (the same kind of PWMs as are on pins 5 and 6) because halving the frequency by using phase-correct PWMs is unacceptable for the application. I just make a special case for 0% duty cycle and things work out great. Perhaps at least a temporary fix should be to have analogWrite() just drive the pin low if the requested pin is 5 or 6 and the requested duty cycle is 0%. I think most applications can handle having low duty cycles being off by no more than 0.39%.

  • Ben

Writing a digital 0V for an analogWrite(5 or 6, 0); sounds like a good idea. I'll add that. Actually, is there any harm in doing it for any analogWrite()?

I can't think of any reason why that would be bad off the top of my head. You could even do the same thing for analogWrite(255) = digitalWrite(HIGH).

  • Ben

I don't know why we couldn't manage with phase-correct. I think we could handle the timing functions.

Either, we could we not just just count by twos:

SIGNAL(SIG_OVERFLOW0)
{
      timer0_overflow_count++;
      timer0_overflow_count++;
}

This loses dome resolution, but should work as long as comparisons are of the type ">", and not "=", this shouldn't pose a problem. (This looks like it would require a change in the busy wait in delay microseconds, and other adjustments).

Or, change the prescaler to 8, and adjust the interrupt routine to:

SIGNAL(SIG_OVERFLOW0)
{
      int i=0;
        if(i++&0x07==0) timer0_overflow_count++;
}

This has the disadvantage that it interrupts 8x more frequently.

However, this would have the advantage for letting us accommodate alternate master-clock rates. For example, I would like to use the RC oscillator, and this would just require a simple change in the code above.

Cheers,
David

Part of the problem with this is that using phase-correct mode doesn't exactly halve the frequency since the timer only spends one total count at TOP and one total count at BOTTOM. For example, let's look at the frequency of a normal counter with a TOP value of 4 and the frequency of a phase-correct counter with a TOP value of 4:

Normal**: TCNT =**
0
1
2
3
4
0 (overflow flag gets set now, after five timer ticks, as expected)
Phase-correct: TCNT =
0
1
2
3
4
3
2
1
0 (overflow flag gets set now, after 8 timer ticks)
So with a TOP value of four, the time to overflow is 5 ticks when using normal mode and 8 ticks when using phase-correct (instead of the 10 we would expect if the frequency were halved). This becomes an overflow period of 256 ticks when the Arduino runs timer0 in normal mode and an overflow of 510 ticks when the Arduino runs timer0 in phase-correct mode. If you count millis() by just incrementing the timer0_overflow_count variable by two instead of one, your millisecond counter will be off by 2/512 = 0.4%, but maybe this isn't significant enough to care about?
Also, given that currently the timer0 overflow interrupts slightly faster than once per millisecond, nearly halving the frequency would mean that millis() would only be accurate to the nearest two milliseconds, which might not be so good. Then again, as you said, the prescaler could be changed to something smaller than 64.
- Ben

I was thinking about how we might make the SIG_OVERFLOW0 routine slightly more efficient.

For 16MHz Arduinos, using the 64 prescale and 256 counts gets us close to 1 ms per overflow (actually 1024us). However, we do not need to use an unsigned long for the calulation, if we reduce the respective values by their gcd. So, instead of using 64256=16384 and 16100=16000, we could use 256 and 250 instead.

This has the benefit of only requiring a BYTE accumulator, instead of a long.

Here is the proposed code:

volatile unsigned byte timer0_period_count = 0;  // accumulator: 1 byte
volatile unsigned long timer0_millis = 0;        // millis stays at unsigned long
SIGNAL(SIG_OVERFLOW0) { 
// timer 0 prescale factor is 64 and the timer overflows at 256 
// 64*256=16384, at 16MHz this is 1024us, 1ms=16000 cycles, the common denominator is 128
// 64*256/128=128, and 16000/128=125, so 128/125 = 1 with a remainder of 3.
  timer0_period_count += 3;                      // This is equivalent to +128-125, so ..
  timer0_millis++;                            // .. we are garanteed 1 ms plus a bit, so count it.
  if (timer0_clock_cycles > 125) {                // But, there might be another millisecond, if so .. 
    timer0_period_count -= 125;                // .. correct the count, and ..
    timer0_millis++;                             // .. count the millisecond.
 } 
}

Phase Correct:
If we changed to phase correct, then we can do similar with a gcc of 40:

volatile unsigned byte timer0_period_count = 0;     // accumulator: 1 byte
volatile unsigned long timer0_millis = 0;           // millis stays at unsigned long
SIGNAL(SIG_OVERFLOW0) { 
// timer 0 prescale factor is 64 and the timer overflows at 510 
// 64*510=32640, at 16MHz this is 2040us, 1ms=16000 cycles, the common denominator is 40
// 64*510/40=51, and 16000/40=50, so 51/50 = 1 with a remainder of 1.
  timer0_period_count ++;                         // This is equivalent to +51-50, so ..
  if(timer0_period_count <= 50)   timer0_millis+=2; // Usually step by 2 ms, but...
  else {                                            // ... nned to correct every 50 times
     timer0_period_count = 1;                       // Will only be over by one, so correct the count ...
     timer0_millis+=4;                              // ... and increment by 4 ms
  }
}

This is fun. If we used a 8MHz clock, then would interrupt every 4080us, with a gcc of 80, which gives 51 and 50, again. So, the code is almost identical, except we increment millis by 4 or 8.

8MHz Phase Correct with prescaler=8:
For 8MHz, we might opt for prescaler of 8, which would result in interruts every 510us, with a gcc of 10 and again gives 51 and 50. The code would change to:

volatile unsigned byte timer0_period_count = 0;  // accumulator: 1 byte
volatile unsigned long timer0_millis = 0;        // millis stays at unsigned long
SIGNAL(SIG_OVERFLOW0) { 
// timer 0 prescale factor is 8 and the timer overflows at 510 
// 8*510=4080, at 8MHz this is 510us, the common denominator with 500 is 10
// 8*510/10=51, and 500/10=50, so 51/50 = 1 with a remainder of 1.
  timer0_period_count ++;                        // This is equivalent to +51-50, so ..
                                                 // Usually no step, but...
  if(timer0_period_count >= 100)                 // ... need to correct every 2 times
     timer0_period_count -= 100;                 // Correct the count ...
     timer0_millis++;                            // ... and increment millis
  }                                              // ... and an extra one every 50
}

Food for thought? :o

Smarter people than I will be needed to generalize it. :stuck_out_tongue:

David

I was thinking about how we might make the SIG_OVERFLOW0 routine slightly more efficient.

I haven't really looked too much at the details of the code, but it's possible to make it even more efficienty by performing only one read one one write to each volatile global. This prevents you from having multiple unnecessary reads and writes to RAM, each of which takes two clock cycles to perform:

volatile unsigned byte timer0_period_count = 0;  // accumulator: 1 byte
volatile unsigned long timer0_millis = 0;        // millis stays at unsigned long
SIGNAL(SIG_OVERFLOW0) { 
// timer 0 prescale factor is 64 and the timer overflows at 256 
// 64*256=16384, at 16MHz this is 1024us, 1ms=16000 cycles, the common denominator is 128
// 64*256/128=128, and 16000/128=125, so 128/125 = 1 with a remainder of 3.
  byte period_count = timer0_period_count;  // load global into register by reading from RAM
  unsigned long millis = timer0_millis;  // load global into registers by reading from RAM
  period_count += 3;                      // This is equivalent to +128-125, so ..
  millis++;                            // .. we are garanteed 1 ms plus a bit, so count it.
  if (period_count > 125) {                // But, there might be another millisecond, if so .. 
    period_count -= 125;                // .. correct the count, and ..
    millis++;                             // .. count the millisecond.
 }
 timer0_millis = millis;  // write register values to RAM
 timer0_period_count = period_count;  // write register value to RAM 
}

Making this change will save you two cycles every interrupt. Every 42nd interrupt (i.e. once every 43 ms), when the if statement is true, it will save you 22 cycles (1.375 us).

I'm all for making the timer0 overflow interrupt as fast as possible.

  • Ben