Sometimes the IDE breaks stuff trying to be helpful
For anyone who’s never dabbled with Arduinos, there are two default functions in any Arduino project – setup
and loop
. setup
runs on boot, and then loop
is repeatedly called as one would expect in a loop. At some point I wrote some initialisation error handling code of the form
if (init_a_thing() == 0) printf("Thing initialised successfully\n");
else {
printf("Thing failed to initialise\n");
while (true) {}
}
The IDE helpfully suggested that due to the infinite loop in the case of error, [[noreturn]]
should be added to the setup
function, apparently failing to consider the case where the function returned successfully. Fortunately the compiler inserted some sort of trap instruction in the case that I tried to cause UB by having setup
actually return. Unfortunately though, I still spent an unreasonable amount of time trying to track down why the entire system froze up after initialising.
Stack overflows aren’t always from too much recursion
Recently while working on Popcorn, I modified the bootloader to create a dedicated stack for the kernel to use, rather than reusing the UEFI stack. However, as soon as I changed that, I kept getting stack overflows whenever I tried to do a stack trace. I kept assuming that I must’ve made a mistake and the stack was trying to trace too far (Update from the future: I have now realised that explanation doesn’t even make sense, since that would require the overflow to be at the other end of the stack), but no. It was purely down to the huge difference in stack size between the original UEFI stack (128KiB) and my new stack (16KiB).
pinMode()
is actually important
I had it lodged somewhere in my memory that digitalWrite()
had some safeguards built into it at some point. I had also had vague memory of reading the sources and discovering that for myself. Unfortunately, as always, I assumed wrong.
The actual code looks like this
void digitalWrite(uint8_t pin, uint8_t val)
{
uint8_t timer = digitalPinToTimer(pin);
uint8_t bit = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
volatile uint8_t *out;
if (port == NOT_A_PIN) return;
// If the pin that support PWM output, we need to turn it off
// before doing a digital write.
if (timer != NOT_ON_TIMER) turnOffPWM(timer);
out = portOutputRegister(port);
uint8_t oldSREG = SREG;
cli();
if (val == LOW) {
*out &= ~bit;
} else {
*out |= bit;
}
SREG = oldSREG;
}
There are safeguards in place – on line 8 there’s a check that the pin actually exists, and on line 12 there’s a check to disable the PWM counter if the pin has PWM enabled. Importantly though, there’s no check that the pin is configured as an output pin. Instead, since the pins default to an input pin, all that happens is it flicks back and forth between Hi-Z and some form of pull (this code is from the original AVR support, whereas I was working on an rp2040 than has both pull-ups and downs). And I can say, with experience now, that Hi-Z doesn’t work properly when trying to drive an SPI chip select line.