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.

asked Jun 25, 2024 at 23:23
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. Commented Jun 25, 2024 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, 2024 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. Commented Jun 26, 2024 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, 2024 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 Commented Jun 26, 2024 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.

answered Jun 25, 2024 at 23:44
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, 2024 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.

answered Jun 26, 2024 at 12:57
1
  • Thanks Edgar, this is really helpful! Commented Jun 27, 2024 at 1:16

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.