for (uint32_t i=0; i<2500; i++) /* 10ms */
/* force compiler to not optimize this... */
__asm__ __volatile__(""); for (uint32_t i=0; i<2500; i++) /* 10ms */
/* force compiler to not optimize this... */
__asm__ __volatile__("");It may be overkill for such cases, but if one keeps the strategy as reference, it's best to know the optimal one :-)
I even think it's not overkill for this case, as they're actually working around the compiler, and a clean approach is preferrable regardless.
You have to be very careful in a bootloader, since you're initializing the clocks and other very low level subsystems you don't want to risk putting the CPU in a state you won't be able to get out of.
In general the standard method is to use a dummy busy delay like the "nop loop" shown above in the very early stages until you get to the point where you can enable interrupts and standby states and implement a proper sleep method that will effectively shut the CPU down waiting for an external event.
If you're talking about userland code (or even driver code) using busyloops you're right though.
The [Microsoft Compiler help](https://docs.microsoft.com/en-us/cpp/intrinsics/x86-intrinsi...) includes it in the "x86 intrinsics list".
I've just compiled with success, for testing purposes, on an AMD processor.
C, relevant section:
#include <immintrin.h>
void test() {
_mm_pause();
}
Output ASM, relevant section: test:
.LFB4006:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
rep nop
nop
nop
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE4006:
.size test, .-test
.globl main
.type main, @function
It should be the `rep nop`.Including `ammintrin.h` yields the same `rep nop`.
It seems that at least some ARMs have [timers that can be used](https://electronics.stackexchange.com/q/450971), which is a different strategy, but still more appropriate (I suppose).
More sophisticated does not always means better.
If you look at the Linux kernel code dealing with calibrating delay loops you can see that it's far from trivial in the general case (and not necessarily very accurate): https://elixir.bootlin.com/linux/latest/source/init/calibrat...
If you want a precise and deterministic delay you almost always want a hardware timer. The problem of course is that in these early boot stages you can't always use one easily, so a rough busy delay approximation does the job just fine.
These MCUs are designed for hard realtime systems so the silicon design is required to have all timings well-known in the datasheets and required to be deterministic. The core (Cortex-M0) design removes nondeterministic aspects of the CPU so there are neither caches nor branch predictions in it. So the busy loops have deterministic timings.
Of course it won't be trivial in MPU/Linux spaces as you mention.
Lots of programming practices are quite different between MPU and MCU worlds. I personally find it interesting that many people are talking about MPU/Linux/x86 here. It's good to learn about them :)
[1] https://github.com/arduino/ArduinoCore-samd/blob/master/boot...
On a Cortex-sized MCU that is most probably as simple as installing appropriate interrupt handler and launching the timer by writing appropriate data to some register(s).
Not saying it is bad, but technical debt is high from several angles.
Aren't you overgeneralizing a little? There is nothing wrong with using loops when execution time is known and constant (like an Arduino). It saves the effort of setting up interrupts, and they can be used with interrupts disabled.
They won't be so accurate: time spent in interrupts will not be counted, and loop setup/teardown won't as well, but it's rare to need such precision over short scales, e.g. when delaying a LED blink.
Never use those when execution time is not guaranteed though, like in emulation or binaries executing on different MCUs.
Yes, in all cases you should try to avoid nop loops, similarly like in high level applications you should try and avoid blocking IO in favor of non-blocking IO. And it does not only mean "avoid nop-loops in code". It also means design your thing in a way where random waits (nop loops are random waits) are unnecessary in the first place. Wait on readiness flag, use timers. Sometimes you have shitty hardware that either does not indicate readiness or indicates readiness falsely, happens. There definitely are situations where nop loops are the only sane available solution.
> using loops when execution time is known and constant
Um... Cortex and larger cores normally can run at multiple clock speeds (e.g. via PLL or clock multiplier config), so this speed is constant only as long as you do not change core clock configuration. Furthermore, if the core in question is even more advanced and employs dynamic PLL, nop-loop execution time is not constant.
> (like an Arduino)
Yes, if you run on small cores like ATmega this can be more or less true.
> It saves the effort of setting up interrupts, and they can be used with interrupts disabled.
Sure and that is one of the main advantages of nop-loops. However, unless you have all "userland" interrupts disabled (all except fault handlers), you run inherently concurrent code which by definition cannot have constant real world run time. To be fair, in the context of a bootloader application flow can be controlled and you can avoid most concurrency issues.
> Never use those when execution time is not guaranteed though
This is the core issue. Execution time is most probably not guaranteed. Nop loops can work during development, hobby project or when you need just a tiny little bit of delay and hardware sync is unavailable.
And why even bother setting timer peripherals if it is to delay for 10ms once in the entire program?