0

This example is a simplified version of what I really need to do, but I think it demonstrates the problem (= my misunderstanding?). I need to use a timer to count microseconds; my code shows how I set up the timer and indeed the timer produces 1 MHz square wave on pin 11 if setup_timer2() is active.

However, when setup_timer2() is active, the LED does not blink. If I comment out setup_timer2(), the LED blinks as expected. In my more complex sketch, it appears that whatever I put in the loop is blocked when activating the timer.

It seems that activating the timer interferes with the blinking logic and I cannot figure out why. Relatively new to this stuff so quite possibly something simple.


// Logic to control on board LED derived from
// https://forum.arduino.cc/t/demonstration-code-for-several-things-at-the-same-time/217158

const int on_board_LED_pin = 13;
const int on_board_LED_interval = 3000;
const int on_board_LED_duration = 500;
byte on_board_LED_state = LOW;
unsigned long currentMillis = 0;
unsigned long previousMillis = 0;

void setup() {
  Serial.begin(9600);
  Serial.println("Starting Demo");
  pinMode(on_board_LED_pin, OUTPUT);
  pinMode(11, OUTPUT); // 1 MHz square wave on this pin if timer activated
  setup_timer2();
}

void loop() {
  currentMillis = millis();
  toggle_on_board_LED();
}

void toggle_on_board_LED() {
  // change the state as needed
  if (on_board_LED_state == LOW) {
    if (currentMillis - previousMillis >= on_board_LED_interval) {
      on_board_LED_state = HIGH;
      previousMillis += on_board_LED_interval;
    }
  }
  if (on_board_LED_state == HIGH) {
    if (currentMillis - previousMillis >= on_board_LED_duration) {
      on_board_LED_state = LOW;
      previousMillis += on_board_LED_duration;
    }
  }
  // implement the state
  digitalWrite(on_board_LED_pin, on_board_LED_state);
}

void setup_timer2() {
  cli();       //stop interrupts globally
  TCCR2A = 0;  // set entire TCCR0A register to 0
  TCCR2B = 0;  // same for TCCR0B
  TCNT2 = 0;   //initialize counter value to 0
  OCR2A = 0;
  TCCR2A |= (1 << WGM21);   // CTC mode
  TCCR2A |= (1 << COM2A0);  // toggle pin OC2A on compare match (= pin 11)
  TCCR2B |= (1 << CS21);    // prescale by 8
  TIMSK2 |= (1 << OCIE2A);  // enable timer compare interrupt
  sei();                    // allow interrupts globally
}

EDIT

If I add the following declaration and ISR:

volatile unsigned long u_sec;

ISR(TIMER2_COMPA_vect) {
  volatile unsigned long u_sec;
  u_sec++;
}

The LED still does not blink.

7
  • 1
    There's already a function called micros() to count microseconds that works. You don't have to re-invent it. It uses Timer0.
    – Delta_G
    Commented Jun 25 at 23:40
  • Thanks, I am aware of micros() but I also need the reference waveform and need to have them synchronized. So there will be an ISR from the timer that counts micros. Commented Jun 25 at 23:55
  • 1
    It takes longer than 1 microsecond to even enter an ISR. What you propose won't work. You've got a 16mHz microcontroller. You're pretty limited in what you can do in on microsecond in code.
    – Delta_G
    Commented Jun 26 at 0:05
  • I was wondering about that -- is there a source that gives the overhead times for various operations? I see many comments about operation X takes Y amount of time, but I don't know where people get these numbers from. Thank you. Commented Jun 26 at 0:10
  • after compiling, look at the resultant assembly code ... refer to the microcontroller programming datasheet to learn how many clock cycles each instruction takes
    – jsotola
    Commented Jun 26 at 0:14

2 Answers 2

1

You are enabling the timer compare interrupt, but you do not have an interrupt handler defined for it. You don't need to set that bit if you just want to output PWM.

I think you should either remove this line or define an interrupt handler for this vector.

TIMSK2 |= (1 << OCIE2A);  // enable timer compare interrupt

When the compare match happens, the code is trying to jump to an undefined interrupt handler and that is locking up the board.

1
  • Thanks... you are correct that if I comment out that line it works, and I see your logic. However, I had previously had an ISR in there, and it wasn't working, so I removed it to get to the minimum erroring example. I will append the code example so you can see what I tried. Commented Jun 25 at 23:57
1

First thing: setting OCR2A to 0 is wrong: if you want the interrupt to fire every microsecond (16 CPU cycles, 2 timer cycles), you should set OCR2A to 1.

Then, as stated in some comments, there is no way your poor ATmega could execute this ISR every microsecond. So let's try to make it a little bit shorter:

ISR(TIMER2_COMPA_vect){}

If you try this, you may notice it's still too much. Disassembling the ISR gives the following:

__vector_7:
    push r1
    push r0
    in   r0, SREG
    push r0
    clr  r1
    pop  r0
    out  SREG, r0
    pop  r0
    pop  r1
    reti

All this takes 19 CPU cycles. Add the 4 cycles taken by the CPU to enter the interrupt, and the 3 cycles of the interrupt vector (a jmp instruction) and you get... too much to do in a microsecond (which is only 16 cycles). At this point you may wonder: why so many instructions for an empty function? It turns out it is all boilerplate. Everything up to clr r1 is an interrupt prologue meant to save the CPU context and clear the register r1 (which is required by the gcc AVR programming model). The rest is the epilogue meant to restore the saved context and return from the interrupt.

We can make the ISR still shorter this way:

EMPTY_INTERRUPT(TIMER2_COMPA_vect)

This compiles to a single reti instruction, which executes in 3 CPU cycles. Now the whole interrupt processing only takes 11 cycles... and the program works! With some caveats:

  • processing these interrupts eats almost 69% of the CPU power

  • since other ISRs take more than one microsecond to execute, our empty ISR will miss many interrupts (not that it matters...)

In summary: you should probably avoid interrupting your CPU every microsecond.

1
  • Thanks Edgar, this is really helpful! Commented Jun 27 at 1:16

Not the answer you're looking for? Browse other questions tagged or ask your own question.