1

If I use direct port manipulation, what is the average number of clock cycles it takes to flip a pin on the arduino

ie.

void loop(){
  PORTB |=  0b00010000;
  PORTB &= ~0b00010000;
}

So how fast is this generally?

1
  • PINB = 0b00010000; or sbi(PINB, 4); will toggle the pin in a single instruction. May 21 '16 at 18:29
4

If you compile the code and examine the results you can see how many instructions it will take, and thus how many clock cycles.

It's slightly more complex because of what else happens in main() to call loop, but basically you have:

for (;;) {
loop();
  aa:0e 94 49 00 call 0x92; 0x92 <loop>
if (serialEventRun) serialEventRun();
  ae:20 97       sbi wr28, 0x00; 0
  b0:e1 f3       breq.-8      ; 0xaa <main+0x10>


00000092 <loop>:
  92:2c 9a       sbi0x05, 4; 5
  94:2c 98       cbi0x05, 4; 5
  96:08 95       ret

So you can see there is:

Operation                            Cycles
Call loop()                          4
Check if serialEventRun exists       2
Since it doesn't, got start of loop  2

Then in loop:

Set bit                              2
Clear bit                            2
Return                               4

So in total you have 16 clock cycles. At 16MHz that gives you a frequency of 1MHz - however that won't be a square wave of 50% duty cycle since there is much less time between set and clear than between clear and set. The full 16 cycles, mapped to the state of the IO pin, would look like this:

             _ 
 _ _ _ _ _ |   | _ _ _ _ _ _ _ _
| call  |sbi|cbi|  ret  |chk|bra|
 . . . . . . . . . . . . . . . .

You can get a much tighter operation by wrapping the pin manipulation in a while (1) loop:

void loop(){
  while(1) {
    PORTB |=  0b00010000;
    PORTB &= ~0b00010000;
  }
}

That reduces down to just:

00000092 <loop>:
  92:2c 9a       sbi 0x05, 4; 5
  94:2c 98       cbi 0x05, 4; 5
  96:fd cf       rjmp .-6      ; 0x92 <loop>

That's sbi (2) cbi (2) and rjmp (2). A total of 6 clock cycles. At 16MHz that's 2.667MHz. Again, though, not a 50% duty cycle, since the rjmp takes two extra clock cycles:

     _
 _ |   | _ _
|sbi|cbi|jmp|
 . . . . . .

By inserting a pair of 1-cycle NOP instructions you can even out the duty cycle and make it 50%:

void loop(){
  while(1) {
    PORTB |=  0b00010000;
    asm volatile("nop");
    asm volatile("nop");
    PORTB &= ~0b00010000;
  }
}

00000092 <loop>:
  92:2c 9a       sbi 0x05, 4; 5
  94:00 00       nop
  96:00 00       nop
  98:2c 98       cbi 0x05, 4; 5
  9a:fb cf       rjmp.-10     ; 0x92 <loop>


     _ _ _
 _ |       | _ _
|sbi|n|n|cbi|jmp|
 . . . . . . . .

Now, of course, it's 8 clock cycles, so at 16MHz that's 2MHz output, but with 50% duty cycle.

Incidentally, all these instruction timings are available in the Instruction Set Summary chapter of the ATMega328P data sheet.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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