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.
-
1There's already a function called micros() to count microseconds that works. You don't have to re-invent it. It uses Timer0.Delta_G– Delta_G2024年06月25日 23:40:48 +00:00Commented 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.Bryan Hanson– Bryan Hanson2024年06月25日 23:55:47 +00:00Commented Jun 25, 2024 at 23:55
-
1It 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– Delta_G2024年06月26日 00:05:49 +00:00Commented 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.Bryan Hanson– Bryan Hanson2024年06月26日 00:10:14 +00:00Commented 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 takesjsotola– jsotola2024年06月26日 00:14:56 +00:00Commented Jun 26, 2024 at 0:14
2 Answers 2
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.
-
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.Bryan Hanson– Bryan Hanson2024年06月25日 23:57:56 +00:00Commented Jun 25, 2024 at 23:57
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.
-
Thanks Edgar, this is really helpful!Bryan Hanson– Bryan Hanson2024年06月27日 01:16:50 +00:00Commented Jun 27, 2024 at 1:16