I was programming an ATtiny13A when I accidentally noticed that when I change the delay time in _delay_us() my code size changes. Here's the sample code:

#include <util/delay.h>

int main(void)
    while (1) 

And here are the different code sizes I get when changing the delay value:

_delay_us(500):    52 bytes
_delay_us(499):    50 bytes
_delay_us(498):    48 bytes
_delay_us(497):    52 bytes
_delay_us(496):    50 bytes
_delay_us(495):    52 bytes
_delay_us(494):    50 bytes
_delay_us(493):    48 bytes
_delay_us(492):    52 bytes
_delay_us(491):    50 bytes
_delay_us(490):    52 bytes

I have a lot of _delay_us() in my code with different delay times. Now it doesn't matter at all if they are a few micro seconds longer or shorter, but it does matter if I can get each one 2-4 bytes smaller.

So I was wondering if someone can tell me what exactly is going on here, so that I can change all delay values across my code to yield the smallest code size. Like say I have a delay that needs to be around 1000us, how do I come up with the number that has the smallest size? Because trial and error is not an option due to the number of these delays in my code.

This has been tested in Arduino IDE with MicroCore and Atmel Studio.

F_CPU is 9.6MHz

  • \$\begingroup\$ wait, you care about a couple of Bytes in size, but use the Arduino IDE?! There's something fundamentally contradicting here... \$\endgroup\$ Commented Sep 21, 2020 at 7:37
  • \$\begingroup\$ @MarcusMüller I'm writing an Arduino library so yes. Also the optimization is pretty severe. I haven't tested the whole code but code snippet like the one posted above don't have any difference in size in Atmel Studio and Arduino IDE. digitalWrite and delayMicroseconds also don't make any difference from pure C either. (If your board is ATtiny13). \$\endgroup\$
    – Pouria P
    Commented Sep 21, 2020 at 7:48
  • \$\begingroup\$ Yes, but you get the whole bootloader that you need to permanently burn into flash... Arduino on Attiny13 doesn't sound so appealing, to be honest, but it's your project :) But if you write a library, you shouldn't be using these wait macros at all, because they are CPU frequency specific, so your library user can't change oscillator speed at runtime anymore. \$\endgroup\$ Commented Sep 21, 2020 at 7:58

2 Answers 2


The delay macro tries to provide as accurate delay it possibly can based on the requested time, so after a delay loop of some counts, it will adjust the number of NOP opcodes to get as close as possible.

If you don't want that, and don't care about the accuracy, simply use the delay loop macros that only include the loop count.

If you are serious about the code space saving, do you notice each time the delay macro is used, it takes about 50 bytes? Make a sigle subroutine to call a 500us delay, and then call the subroutine each time you want a 500us delay. Calling a subroutine much less bytes than inlining the delay macro.

  • \$\begingroup\$ Digging down the source codes I found that _delay_us() uses __builtin_avr_delay_cycles(). Using that will give the same result as using _delay_us(). Using _delay_loop_2() gives me 50 bytes code size regardless of the delay duration. \$\endgroup\$
    – Pouria P
    Commented Sep 21, 2020 at 8:11
  • \$\begingroup\$ Yes, because delay_us calculates how many CPU cycles it needs for a certain delay (based on F_CPU), in order to delay for certain amount of cycles with the delay_cycles. The delay_loop and delay_loop2 simply use a loop count so where each loop is 3 or 4 cycles, so it cannot and won't add any single cycle NOP delays. I do wonder why it takes 50 bytes, the loading of count and the loop itself should not take more than few instructions. Please add a the assembly listing of calling one of the delays to find out what's wrong. \$\endgroup\$
    – Justme
    Commented Sep 21, 2020 at 8:27
  • \$\begingroup\$ 50 bytes is my whole code size. _delay_loop_2(1196) (equivalant of 498us) itself adds only 10 bytes, as opposed to _delay_us(498) which adds 8 bytes. Any chance I can figure out which numbers generate the least NOPs? I seem to have found the source code of __builtin_avr_delay_cycles here but I can' tmake much out of it. \$\endgroup\$
    – Pouria P
    Commented Sep 21, 2020 at 8:38
  • \$\begingroup\$ I'm using -Os and -flto \$\endgroup\$
    – Pouria P
    Commented Sep 21, 2020 at 8:39
  • \$\begingroup\$ As the loops can only delay in units of 3 or 4 cycles depending on which loop is used, the delay is converted to loop count and the extra few NOPs are added on top of that. So make the loops to be equal to multiple of cycles per loop so no extra NOPs are added. It would be simpler to make your own macro to calculate loop count from microseconds and use the delay_loop_2, than to try doing it in reverse, ending up with microseconds from loop counts, that end up being fed to delay_us. \$\endgroup\$
    – Justme
    Commented Sep 21, 2020 at 8:49

if you care about ROM space and can spare a few registers write a subroutine (function) a subroutine call should take only 8 or so bytes.

  • \$\begingroup\$ I think I was unclear, each _delay_us() has a different delay time, so I can't use one function for all of them. I edited the post to make it more clear. \$\endgroup\$
    – Pouria P
    Commented Sep 21, 2020 at 7:21
  • \$\begingroup\$ @PouriaP that's not right. A function accepts parameters, in your case, that'd be the duration. \$\endgroup\$ Commented Sep 21, 2020 at 7:33
  • \$\begingroup\$ @MarcusMüller I mean it wouldn't make a difference. The optimizer already takes care of that. _delay_us() accepts only a compile-time constant. If I put it in a function I would have to add a loop and it would actually take more space. I've already tried this. \$\endgroup\$
    – Pouria P
    Commented Sep 21, 2020 at 7:53
  • \$\begingroup\$ @PouriaP "The optimizer already takes care of that": it doesn't, as it seems. \$\endgroup\$ Commented Sep 21, 2020 at 7:56
  • 1
    \$\begingroup\$ And those can obviously be reduced to a loop of X times a fixed amount of 100us. \$\endgroup\$
    – Justme
    Commented Sep 21, 2020 at 8:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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