user54503: I am programming an attiny202 which as per datasheet can run up to 20Mhz, but after compiling/uploading this simple code to the attiny202 and watch the PA2 pin on the oscilloscope, I see that the pin oscilates at ~2.5MHz which is around 10x slower than I would expect, this is even I disable prescaling, am I missing something? (I am new into programming microcontrollers)

note:
VCC is 5V (from lab bench PSU)

main.c
#include <avr/io.h>

int main(void)
{
  // no prescaling (if lesss than 4.5V, this is overclocking)
  _PROTECTED_WRITE(CLKCTRL_MCLKCTRLB, 0);

  PORTA_DIR = _BV(2);

  while(1)
  {
    PORTA_OUTTGL = _BV(2);
  }
}

Makefile
PRG = main

CC = avr-gcc
MCU_TARGET = attiny202
OPTIMIZE = -Os

OBJCOPY = avr-objcopy
UART = /dev/ttyUSB0

all: $(PRG).elf $(PRG).hex

$(PRG).elf: $(PRG).c                                                                                                                                                                                                                        »       $(CC) -g -Wall $(OPTIMIZE) -mmcu=$(MCU_TARGET) $(PRG).c -o $(PRG).elf

%.hex: %.elf
»       $(OBJCOPY) -j .text -j .data -O ihex $< $@

clean:
»       rm *.elf *.hex

burn: $(PRG).hex
»       pymcuprog -t uart -d $(MCU_TARGET) -u $(UART) write -f $(PRG).hex --erase --verify

bitmaps from oscilloscope:
-
same picture with more measurement information:


user42015: You are constantly toggling a pin in software. This involves copying the register data to the working register, toggling the bit and writing the result back.
This takes way more than only 1 clock cycle. So the resulting frequency is way lower. With only software you just cannot get near the clock frequency of the microcontroller.

user7508: Just as a complement to chrisl's answer, if you disassemble the compiled
program, you should see that your infinite loop looks like this:
1:  sts  0x0407, r24  ; write to PORTA_OUTTGL
    rjmp 1b           ; jump back to previous instruction

These instructions take two cycles each, for a total of four cycles per
loop iteration. The loop is thus expected to run at F_CPU÷4 = 5 MHz.
Since a cycle of the output waveform takes two toggles, you expect to see
2.5 MHz on the oscilloscope.
You should be able to speed this up by writing to the virtual port
register VPORTA_IN instead of PORTA_OUTTGL. Virtual port registers
are equivalent to regular port registers, but they are mapped into the
I/O space of the microcontroller. This allows the compiler to replace
the sts instruction by the shorter and faster out instruction. The
loop should then complete in only 3 CPU cycles and output a waveform at
3.33 MHz.

54503: <p>I am programming an attiny202 which as per <a href="http://ww1.microchip.com/downloads/en/DeviceDoc/ATtiny202-402-AVR-MCU-with-Core-Independent-Peripherals_and-picoPower-40001969A.pdf" rel="nofollow noreferrer">datasheet</a> can run up to 20Mhz, but after compiling/uploading this simple code to the attiny202 and watch the PA2 pin on the oscilloscope, I see that the pin oscilates at ~2.5MHz which is around 10x slower than I would expect, this is even I disable prescaling, am I missing something? (I am new into programming microcontrollers)</p>
<blockquote>
<p>note:
VCC is 5V (from lab bench PSU)</p>
</blockquote>
<p>main.c</p>
<pre class="lang-c prettyprint-override"><code>#include &lt;avr/io.h&gt;

int main(void)
{
  // no prescaling (if lesss than 4.5V, this is overclocking)
  _PROTECTED_WRITE(CLKCTRL_MCLKCTRLB, 0);

  PORTA_DIR = _BV(2);

  while(1)
  {
    PORTA_OUTTGL = _BV(2);
  }
}
</code></pre>
<p>Makefile</p>
<pre><code>PRG = main

CC = avr-gcc
MCU_TARGET = attiny202
OPTIMIZE = -Os

OBJCOPY = avr-objcopy
UART = /dev/ttyUSB0

all: $(PRG).elf $(PRG).hex

$(PRG).elf: $(PRG).c                                                                                                                                                                                                                        »       $(CC) -g -Wall $(OPTIMIZE) -mmcu=$(MCU_TARGET) $(PRG).c -o $(PRG).elf

%.hex: %.elf
»       $(OBJCOPY) -j .text -j .data -O ihex $&lt; $@

clean:
»       rm *.elf *.hex

burn: $(PRG).hex
»       pymcuprog -t uart -d $(MCU_TARGET) -u $(UART) write -f $(PRG).hex --erase --verify
</code></pre>
<p>bitmaps from oscilloscope:</p>
<p>-<a href="https://i.stack.imgur.com/oS5vf.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/oS5vf.png" alt="enter image description here" /></a></p>
<p>same picture with more measurement information:</p>
<p><a href="https://i.stack.imgur.com/LOf6q.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/LOf6q.png" alt="enter image description here" /></a></p>
 42015: <p>You are constantly toggling a pin in software. This involves copying the register data to the working register, toggling the bit and writing the result back.</p>
<p>This takes way more than only 1 clock cycle. So the resulting frequency is way lower. With only software you just cannot get near the clock frequency of the microcontroller.</p>
 7508: <p>Just as a complement to chrisl's answer, if you disassemble the compiled
program, you should see that your infinite loop looks like this:</p>
<pre class="lang-lisp prettyprint-override"><code>1:  sts  0x0407, r24  ; write to PORTA_OUTTGL
    rjmp 1b           ; jump back to previous instruction
</code></pre>
<p>These instructions take two cycles each, for a total of four cycles per
loop iteration. The loop is thus expected to run at F_CPU÷4 = 5 MHz.
Since a cycle of the output waveform takes two toggles, you expect to see
2.5 MHz on the oscilloscope.</p>
<p>You should be able to speed this up by writing to the virtual port
register <code>VPORTA_IN</code> instead of <code>PORTA_OUTTGL</code>. Virtual port registers
are equivalent to regular port registers, but they are mapped into the
I/O space of the microcontroller. This allows the compiler to replace
the <code>sts</code> instruction by the shorter and faster <code>out</code> instruction. The
loop should then complete in only 3 CPU cycles and output a waveform at
3.33 MHz.</p>
