0

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;
 }
Rohit Gupta
6122 gold badges5 silver badges18 bronze badges
asked Sep 8, 2023 at 14:38
4
  • 2
    You 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. Commented 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. Commented 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. Commented 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. Commented Sep 8, 2023 at 15:22

1 Answer 1

2

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.

answered Sep 8, 2023 at 19:10
13
  • 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? Commented Sep 8, 2023 at 19:21
  • PINB |= 1 << PINB1; can just be PINB = 1 << PINB1; Commented Sep 8, 2023 at 19:22
  • But that doesn't toggle the pin, do i need to set any register? Commented 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. Commented 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. Commented Sep 8, 2023 at 19:32

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.