Using Timer2 to generate clock - not as regular as I expected

I’m using Timer2 to generate a clock signal. I wanted to check that it
was generating at 50% on and 50% off, so I used the code below to try
to check it. I am surprised that the output - although close to 50% - seems
to have a small varying error, whatever the timer counter values are.

Am I using the timer counter and output toggle feature correctly?

I may want to use this to generate a clock signal at about 9600 Hz,
rather than bit-banging the output. I thought it might produce less
jitter.

I even get the error with the timer set to run (prescalar set to max)
as slow as possible, as shown with this output:

ints/ mark/space/ diff /percent error
X31X/41552/41702/-150.00/-0.36/
X31X/40620/42728/-2108.00/-5.19/
X30X/40550/42796/-2246.00/-5.54/
X31X/41274/41980/-706.00/-1.71/
X30X/42266/40978/1288.00/3.05/
X31X/42209/41123/1086.00/2.57/
X31X/41192/42151/-959.00/-2.33/
X30X/40550/42798/-2248.00/-5.54/
X31X/40785/42473/-1688.00/-4.14/
X31X/41718/41531/187.00/0.45/
X30X/42355/40977/1378.00/3.25/

The hardware circuit is just a single wire, connecting pin11 to pin7. Arduino0022, using a Duemilanove (168) clone.

#include <avr/interrupt.h>
#include <avr/io.h>

const int ledPin = 13;
const int ClockPin = 11;  //PB3 is OC2A, Arduino pin 11
const int StPin = 7;


void print_hex_byte_leading_zero(byte val)
{
  if (val < 0x10)
  {
    Serial.print('0');
  }
  Serial.print(val, HEX);
}

byte converthexdigit(char c1)
{
  byte retval;
  if (c1 >= '0' && c1 <= '9') retval = (c1 - 48 );
  if (c1 >= 'A' && c1 <= 'F') retval = (c1 - 65 + 10 );       
  if (c1 >= 'a' && c1 <= 'f') retval = (c1 - 97 + 10 );
  return retval;
}


volatile boolean int_out_rdy;
volatile int int_out_data;
volatile int int_out_bits;
volatile long l_ints = 0L;
long scount, mcount;


ISR(TIMER2_COMPA_vect) { 

    l_ints++;

    if (int_out_rdy)
    {
      if (int_out_bits--) 
      {
        if (int_out_data & 0x20) 
        {
          //digitalWrite(ledPin, 1);
          PORTB |= 0x20;
        }
        else
        {
          //digitalWrite(ledPin, 0);
          PORTB &= ~(0x20);
        }
        int_out_data <<= 1;
      } 
      else
      {
        int_out_rdy = 0;
        PORTB &= ~(0x20);
      }
    }

}



void setup_interrupt()
{

  //Timer2 Settings
  TCCR2B |=   (1<<CS22); // was off
  TCCR2B |=   (1<<CS21); 
  TCCR2B |=   (1<<CS20);  // was off

  TCCR2B |=  (1<<WGM22);
  TCCR2A &= ~(1<<WGM21);
  TCCR2A |=  (1<<WGM20); 

  TCCR2A |=  (1<<COM2A0); // set up for OCR2A toggle
  TCCR2A &= ~(1<<COM2A1);


  // Use internal clock - external clock not used in Arduino
  ASSR &= ~(1<<AS2);
  //Timer2 Overflow Interrupt disable,
  TIMSK2 &= ~(1<<TOIE2);
  // set one output compare interrupt enables
  TIMSK2 |=  (1<<OCIE2A);
  TIMSK2 &= ~(1<<OCIE2B);  

  OCR2A = 0x34;        // gives as near to 9600 as I can get       
  sei();  
}


unsigned long millinext;
unsigned long milliinterval = 1000L;

void setup() {
  Serial.begin(9600);
  setup_interrupt(); 
  millinext = millis() + milliinterval;
  pinMode(ledPin,     OUTPUT);
  pinMode(ClockPin,   OUTPUT);
  pinMode(StPin,      INPUT);
  digitalWrite(StPin, HIGH);
}


void loop() {

  char c; 

  if (millis() > millinext) {
    Serial.print("X"); 
    Serial.print(l_ints); 
    Serial.print("X");
    
    Serial.print("/"); Serial.print(mcount); Serial.print("/"); 
    Serial.print(scount); Serial.print("/");
    float diff = (mcount - scount);
    Serial.print(diff); Serial.print("/");
    float percent = 100.0*diff/(float)mcount;
    Serial.print(percent); Serial.println("/");
    mcount = scount = 0L;
    
    l_ints = 0L;
    millinext = millis() + milliinterval;   
  }
  
  
  digitalRead(StPin) == HIGH ? mcount++: scount++;


  if (Serial.available())
  {    
    c = Serial.read();

    // to read a command like O1
    if (c == 'O' && !int_out_rdy) // O for output
    {
      while(!Serial.available());
      char c1 = Serial.read();
      int_out_data = 0;
      int_out_data = (c1 << 1) | 0x01;  // put stop bit on at end
      Serial.print(int_out_data, HEX);
      int_out_bits = 6;
      int_out_rdy = 1; // flag for interrupt routine
    }

    // to read a command like Pf0
    if (c == 'P')
    {
      char c1;
      byte add;
      Serial.print("*");
      while(Serial.available() < 2) { // expecting 2 characters
        ; 
      }
      add = converthexdigit(Serial.read()) * 16 + converthexdigit(Serial.read());
      OCR2A = add;
      Serial.print("P"); 
      print_hex_byte_leading_zero(add); 
      Serial.println(";");
    }

  }

}

One thing is the massive amount of floating maths and serial printing going on, maybe that causes jitter.

Then the ISR does a lot of work, isn't it just supposed to toggle a pin?

The setup_interrupt() func is entered with interrupts enabled, probably wouldn't matter but better to disable at the start.

So why all the floating maths etc, just set a flag in the ISR and print the millis() value direct with no formatting. That's way too complicated for a test program. To prove the concept just do something simple, something like this

volatile int count;

ISR(TIMER2_COMPA_vect) { 

 if (count > 0) count--;

}


void loop() {
    if (count == 0) {
        Serial.println (millis());
        count = 100;
    }
}

Note that the printing does not happen with every tick because it will interfere with the last serial.print.


Rob

You can let the hardware toggle the pin. Not much jitter then:

const byte LED = 11;

void setup() {
  pinMode (LED, OUTPUT);
  
  // set up Timer 2
  TCCR2A = _BV (COM2A0) | _BV(WGM21);  // CTC, toggle OC2A on Compare Match
  TCCR2B = _BV(CS21) ;   // scale to clock / 8
  OCR2A =  103;       // compare A register value (104 * clock speed / 8)
}  // end of setup

void loop() { }

That toggles pin 11 at 9630 Hz (as measured), without much noticeable jitter.

(edit) It toggles at twice that frequency, but the "frequency" is from one 'on' to the next 'on', so you have to double the rate.

16000000/8/104 = 19230.76 giving 9615 Hz (for a complete cycle).

Thank you for the responses.

Rob, I will cut down on the printing. I can certainly remove the floating maths - that was just to show what the error was in a %. I saw the errors before that though.

I will do it again without the ISR. The ISR in this case increments the long count, then a single test if has to output - in the test output I posted I was not using it for output at all. I will try that with no other changes first.

Nick - I am using the hardware to toggle the pin. I will try your code which seems to have different pin settings than I was using, so that is perhaps where I was going wrong. I had WGM22 and WGM20 on, WGM21 off, you had only WGM21 on unless I am missing something. Nothing is fiddling with pin11 except the hardware.

I have not got a scope, but can get access in a few days.

shelleycat: Nick - I am using the hardware to toggle the pin.

Oh, right. So why the ISR? Just for debugging? That won't show exactly when the hardware toggles the pin. For one thing there is at least a microsecond or two before the ISR is called.

I’ve taken out the ISR now (and unset the bit to enable the interrupt too), taken out all floating point
calculations, and reduced the amount of integer calculations.
I still get some variation,

-592
-1050
-886
-423
32
-766
-883
-423
36
-759
-885
-514
-52
-499

I think next I will use a second arduino to do the monitoring, or read the variations into an array and print them all
out after 10 seconds.

#include <avr/interrupt.h>
#include <avr/io.h>

const int ledPin = 13;
const int ClockPin = 11;  //PB3 is OC2A, Arduino pin 11
const int StPin = 7;


void print_hex_byte_leading_zero(byte val)
{
  if (val < 0x10)
  {
    Serial.print('0');
  }
  Serial.print(val, HEX);
}

byte converthexdigit(char c1)
{
  byte retval;
  if (c1 >= '0' && c1 <= '9') retval = (c1 - 48 );
  if (c1 >= 'A' && c1 <= 'F') retval = (c1 - 65 + 10 );       
  if (c1 >= 'a' && c1 <= 'f') retval = (c1 - 97 + 10 );
  return retval;
}


volatile boolean int_out_rdy;
volatile int int_out_data;
volatile int int_out_bits;
volatile long l_ints = 0L;
long scount, mcount;



void setup_interrupt()
{

  //Timer2 Settings
  TCCR2B |=   (1<<CS22); // was off
  TCCR2B |=   (1<<CS21); 
  TCCR2B |=   (1<<CS20);  // was off

  TCCR2B |=  (1<<WGM22);
  TCCR2A &= ~(1<<WGM21);
  TCCR2A |=  (1<<WGM20); 

  TCCR2A |=  (1<<COM2A0); // set up for OCR2A toggle
  TCCR2A &= ~(1<<COM2A1);


  // Use internal clock - external clock not used in Arduino
  ASSR &= ~(1<<AS2);
  //Timer2 Overflow Interrupt disable,
  TIMSK2 &= ~(1<<TOIE2);
  // set one output compare interrupt enables
  TIMSK2 &=  ~(1<<OCIE2A); // was this one on 
  TIMSK2 &= ~(1<<OCIE2B);  

  OCR2A = 0x34;        // gives as near to 9600 as I can get       
  sei();  
}


unsigned long millinext;
unsigned long milliinterval = 1000L;

void setup() {
  Serial.begin(9600);
  setup_interrupt(); 
  millinext = millis() + milliinterval;
  pinMode(ledPin,     OUTPUT);
  pinMode(ClockPin,   OUTPUT);
  pinMode(StPin,      INPUT);
  digitalWrite(StPin, HIGH);
}


void loop() {

  char c; 

  if (millis() > millinext) {
 
    int diff = (mcount - scount);
    Serial.println(diff); 
    mcount = scount = 0L;
    
    l_ints = 0L;
    millinext = millis() + milliinterval;   
  }
  
  
  digitalRead(StPin) == HIGH ? mcount++: scount++;


  if (Serial.available())
  {    
    c = Serial.read();

    // to read a command like Pf0
    if (c == 'P')
    {
      char c1;
      byte add;
      Serial.print("*");
      while(Serial.available() < 2) { // expecting 2 characters
        ; 
      }
      add = converthexdigit(Serial.read()) * 16 + converthexdigit(Serial.read());
      OCR2A = add;
      Serial.print("P"); 
      print_hex_byte_leading_zero(add); 
      Serial.println(";");
    }

  }

}

edit:
And even when taking all serial code out of the loop, just the print output every second, I still get some variation. Perhaps it is the Serial.Print making a difference.

edit again:
Well I did not expect this. This is storing the differences each second, then printing them all out at once. Still get variations.

Here are the results
-894/-1105/-542/-430/-655/-1103/-993/-652/-542/-543/
-894/-1103/-768/-430/-544/-992/-1105/-764/-430/-544/
-895/-1106/-877/-541/-430/-884/-1103/-988/-430/-542/
-861/-994/-1102/-545/-430/-657/-1102/-990/-656/-542/
-636/-989/-1103/-766/-430/-544/-992/-1105/-764/-434/
-638/-992/-1103/-653/-430/-772/-990/-1102/-543/-430/
-861/-1107/-989/-434/-541/-772/-989/-1105/-541/-432/
-751/-1104/-990/-655/-541/-544/-992/-1102/-768/-430/
-633/-994/-990/-877/-542/-430/-994/-989/-878/-542/
-632/-432/-992/-993/-877/-543/-430/-883/-1102/-988/

and code.

#include <avr/interrupt.h>
#include <avr/io.h>

const int ledPin = 13;
const int ClockPin = 11;  //PB3 is OC2A, Arduino pin 11
const int StPin = 7;


long scount, mcount;
const int CNT_RESARRAY=10;
int resarray[CNT_RESARRAY];
int resindex;

void setup_interrupt()
{

  //Timer2 Settings
  TCCR2B |=   (1<<CS22); // was off
  TCCR2B |=   (1<<CS21); 
  TCCR2B |=   (1<<CS20);  // was off

  TCCR2B |=  (1<<WGM22);
  TCCR2A &= ~(1<<WGM21);
  TCCR2A |=  (1<<WGM20); 

  TCCR2A |=  (1<<COM2A0); // set up for OCR2A toggle
  TCCR2A &= ~(1<<COM2A1);


  // Use internal clock - external clock not used in Arduino
  ASSR &= ~(1<<AS2);
  //Timer2 Overflow Interrupt disable,
  TIMSK2 &= ~(1<<TOIE2);
  // set one output compare interrupt enables
  TIMSK2 &=  ~(1<<OCIE2A); // was this one on 
  TIMSK2 &= ~(1<<OCIE2B);  

  OCR2A = 0x34;        // gives as near to 9600 as I can get       
  sei();  
}


unsigned long millinext;
unsigned long milliinterval = 1000L;

void setup() {
  Serial.begin(9600);
  setup_interrupt(); 
  millinext = millis() + milliinterval;
  pinMode(ledPin,     OUTPUT);
  pinMode(ClockPin,   OUTPUT);
  pinMode(StPin,      INPUT);
  digitalWrite(StPin, HIGH);
}


void loop() {

  if (millis() > millinext) {
 
    int diff = (mcount - scount);
    resarray[resindex++] = diff;
    if (resindex == CNT_RESARRAY)
    {
      for (int i = 0; i < CNT_RESARRAY; i++)
      {
        Serial.print(resarray[i]); Serial.print("/");
      }
      Serial.println();
      resindex = 0;
    }
    
    mcount = scount = 0L;
    millinext = millis() + milliinterval;   
  }
  
  
  digitalRead(StPin) == HIGH ? mcount++: scount++;


}

I don't think those reads prove a huge amount. Timer (0) interrupts will kick in, changing the figures slightly.

The proof would be to check the output on a scope. Or are you trying to use the pulses internally (ie. read them back)?

Plus if I remember correctly, millis() itself has a certain error amount. This is because the millis interrupt occurs every 1/1024 seconds, not every 1/1000 second. It corrects itself after a while (it isn’t cumulative over the long term) but has a local error amount.

I will try this on a scope, it will take a few days though. Thanks for the helpful responses.

A small variation in millis() would be enough to give this effect, I never thought of that.

If you aren't toggling the pin in the interrupt routine itself you can't get accurate timings. Even so occasionally you might get jitter as the timer0 interrupt is a higher priority than timer2 I believe - should be rare unless the periods are harmonically related.

If the body of the sketch is frequently disabling interrupts that would also lead to jitter.

Timer 2 is higher priority, however once Timer 0 fires it will hold up Timer 2.

The output is completely steady on a scope, so that's a good way to generate a clock signal with no software cost. Thanks for the advice.