I got the following code, and based on the datasheet the Freq should be clk / 2 * Prescaller * (1 + OCR1A)
making it that if I set the OCR1A to 0, I should get something close to 8Mhz, but the maximum I can get is ~200kHZ. Can anyone help me understand why?
In FAST PWM MODE I can reach those speeds, just wanted to know why i can't in CTC MODE.
void setup() {
DDRB |= (1 << PINB1) ;
TCCR1A = 0;
TCCR1B = 0;
OCR1A = 0;
TCCR1B |= (1<<CS10) | (1<<WGM12);
TIMSK1 |= (1<<OCIE1A);
sei();
}
void loop() {
}
ISR (TIMER1_COMPA_vect){
PORTB ^= 1 << PINB1;
}
-
2You cannot dispatch an ISR and return fast enough to get anywhere near 8MHz in CTC. But you also cannot do this for Fast PWM mode either. So, I don't know what to make of your question. Did you mean using the actual PWM functionality and just put the ISR in your code as a mistake? Alternately if you were able to get 8 MHz doing this same thing with an ISR and Fast PWM mode, I'm curious to see how.timemage– timemage2023年09月08日 15:02:40 +00:00Commented Sep 8, 2023 at 15:02
-
Hello, thanks for your reply. No, i can get the 8mhz using fast pwm without interrupts (yes to your question on the using the pwm functionality), just using the ICR and OCR1A registers and set them to 0. I imagined it was something related to the time being consumed by the interrupt calls, but couldn,'t figure out the timings that jumping to the ISR takes.Nuno Santos– Nuno Santos2023年09月08日 15:18:28 +00:00Commented Sep 8, 2023 at 15:18
-
I even went to the Microchip IDE and emulated the atmega, but it doesn't implement the ISR very well (either that or i don't know how to), cos i could only trigger the the ISR call when "playing" the debug and couldn't trigger it using the step by step instruction but i manage to get that for a 8MHZ clock the isr was triggered every 5us or something like that i think it was 38 instruction cycles, or close to that, but i coulnd't figure out where did those instructions came from.Nuno Santos– Nuno Santos2023年09月08日 15:18:34 +00:00Commented Sep 8, 2023 at 15:18
-
I think i got thrown off by this on the atmega datasheet : The waveform generated will have a maximum frequency of fOC1A = fclk_I/O/2 when OCR1A is set to zero (0x0000). The waveform frequency is defined by the following equation: So i was expecting a ~8mhz pulse.Nuno Santos– Nuno Santos2023年09月08日 15:22:54 +00:00Commented Sep 8, 2023 at 15:22
1 Answer 1
I disassembled your program. Below is the code for the ISR. Next to each instruction, I wrote the number of CPU cycles it takes, as a comment:
; Prologue: save some registers to the stack.
push r1 ; 2
push r0 ; 2
in r0, 0x3f ; 1
push r0 ; 2
clr r1 ; 1
push r24 ; 2
push r25 ; 2
; ISR body: PORTB ^= 0x02;
in r24, 0x05 ; 1
ldi r25, 0x02 ; 1
eor r24, r25 ; 1
out 0x05, r24 ; 1
; Epilogue: restore registers and return.
pop r25 ; 2
pop r24 ; 2
pop r0 ; 2
out 0x3f, r0 ; 1
pop r0 ; 2
pop r1 ; 2
reti ; 4
I count a total of 31 CPU cycles, most of them spent in the prologue and epilogue. Then, there is some overhead:
The CPU always executes at least one instruction of the main program between two interrupts. Given that the main program is in a tight loop containing only two double-cycle instructions, that will take two CPU cycles.
The CPU takes four cycles to enter interrupt mode: prevent nested interrupts, save the program counter and load the address of the interrupt vector.
The interrupt vector is a
jmp
instruction that jumps to the ISR. This takes three cycles.
The minimal interrupt period is then given by this sum:
2 one loop instruction
4 enter interrupt mode
3 jump to ISR
31 execute ISR
────────────────────────
40 total cycles
This takes 2.5 μs, giving an interrupt frequency of 400 kHz. Given that you need two interrupts for a full cycle of the output signal, the maximum you can see on the output is then 200 kHz.
You can save 10 CPU cycles by toggling the pin with:
PINB |= 1 << PINB1;
This line compiles to a single double-cycle sbi
machine instruction.
As this instruction doesn't access any CPU registers, you don't need to
save and restore r24
and r25
. Actually, you don't need to save and
restore any register, so you could just remove the prologue, and replace
the epilogue by a single reti
instruction:
ISR (TIMER1_COMPA_vect, ISR_NAKED) {
PINB |= 1 << PINB1;
asm("reti"); // return from interrupt
}
This gives an ISR that executes in six cycles, and the total interrupt period can then be as small as 15 cycles. Beware though, that you should not write a naked ISR unless you are absolutely sure that the code within it will not clobber any CPU register, nor the status register.
Clarification: On the face of it, the instruction
PINB |= 1 << PINB1;
seems to mean: "Load PINB
, do a bitwise OR with 0x02
, then write the
result back to PINB
". This is, however, not how the compiler
interprets it. Instead, it takes it to mean "set bit 1 of register
PINB
to one". This happens to be a single machine instruction, as
PINB
is a bit-addressable I/O registers.
Yes, the avr-gcc compiler may be a bit peculiar in this respect.
-
First of all, thanks so much for that detailed explanation. i did tried to include that ISR_NAKED option, and it doubled the frequency. One thing i didn't understand is that you said you can toggle the pin with the |= ? i tried it and it stays always on, i can only toggle with the ^= operator, am i doing anything wrong?Nuno Santos– Nuno Santos2023年09月08日 19:21:38 +00:00Commented Sep 8, 2023 at 19:21
-
PINB |= 1 << PINB1;
can just bePINB = 1 << PINB1;
timemage– timemage2023年09月08日 19:22:03 +00:00Commented Sep 8, 2023 at 19:22 -
But that doesn't toggle the pin, do i need to set any register?Nuno Santos– Nuno Santos2023年09月08日 19:27:17 +00:00Commented Sep 8, 2023 at 19:27
-
@NunoSantos the behaviour of writing to 1-bit to PINx causes that pin to toggle in PORTx. I know it looks weird, but that's how it works on these chips.timemage– timemage2023年09月08日 19:28:53 +00:00Commented Sep 8, 2023 at 19:28
-
Wait, not sure if i totally understand you, bare with me, i'm a noob... So, i had the pin toggling, with the XOR operator., but when i change to the OR ou even just setting the pin with the Equal sign, i get no tggling on that pin, after reading your comment i went to the datasheet because i remember reading that there was one bit in a register that did just that, so i updated my code with the line TCCR1A |= (1<<COM1A0); and NOW it toggles if i set the PIN to HIGH inside the interrupt, not sure if it was this you were talking about.Nuno Santos– Nuno Santos2023年09月08日 19:32:19 +00:00Commented Sep 8, 2023 at 19:32