Wow level 3 there was really elegant. In general I try to find use for a strong type system to force correct code, but here it's Rust's ownership model that ends up saving the day.
AVR microcontrollers use the same register to define the pin value when it's set as an output, and define the state of the internal pullups when it's set as an input.
I haven't seen that in ARM microcontrollers, but as peripherals vary across manufacturers I don't think you can assume that it's not done this way.
Wouldn't you then write two sets of functions? set_high, set_low, enable_pullup, disable_pullup. Now internally set_high and enable_pullup might do the exact same thing, but externally they signal intent and prevent you from trying to set a pullup on an output for example.
To prevent glitches it rarely problem but sometimes happens: imagine that default pin state must be high (on reset pin are floating) and moment you set pin mode to output it goes low, because data register is 0
Consider a I/O pin set to open-drain drive. It either actively pulls the output pin to the ground rail, or the I/O floats. Externally, you have a pull-up resistor.
If there are multiple chips connected in open-drain, you write the I/O pin to 0 or 1 to either ground or float the pin. You read the I/O pin to see if anyone else has grounded it if you haven't. This was called "wired-OR" in the old days. (It's really AND, but was commonly used in DeMorgan equivalent form for communication buses before tri-state drivers became a thing. I'll get my cane and hobble back to my rocking chair now..)
I think in the old days, before CMOS, this was called open-collector. I had learned about it in first semester digital logic not too long ago playing with the "high speed" 7400 series. If I remember correctly, the latch needs to support the open collector capability, but the reason escapes me.
Open collector if you have a bipolar technology. Modern microcontrollers are CMOS, so the pull-down transistor has a drain, not a collector. TTL is bipolar, so the transistor has a collector.
> Which is throughly incorrect. Of course you can set the state of an output register that’s set to input, that state will just not appear on the pin.
The context is Rust, where the statement is clearly correct. One can't call a function if the Rust compiler refuses to generate the binary. The author probably should have used a period between the first and second independent clauses and a semicolon between the second and third in order to show the close relations ship between being unable to call the function and it being a compile error.
My point was that the OP's objection would be valid if the statement being objected to were about architectural limitations of the hardware.
However, the statement being objected to was under the section about the 3rd level of abstraction: "The Embedded HAL to the Rescue", which is detailing an extra semantic restriction imposed by Rust. The final two sentences of section 2 even allude to the prevented actions being legal but almost certainly being a logic error. To object that the hardware allows nonsensical actions without trapping is to miss the point of section 3 of the article. At the third level of abstraction: Rust prevents something legal but nonsensical.
Now, if this particular board doesn't clear output state when switching a GPIO pin from input to output, then it's a valid criticism that perhaps the developer wants to set the output state correctly before driving the pin and the prevented action is in fact semantically meaningful and perhaps the intent of the developer. However, that wasn't the OP's objection. The OP's objection was simply that Rust was preventing something that the underlying platform allows.
No, I just mean that you misunderstood what the OP was calling "incorrect". They weren't saying that it's incorrect that a compile time error is triggered in the situation described.
I understand that the OP was calling the model of the microcontroller incorrect.
To recap:
The article said "Rust doesn't allow you to do X"
The OP said "Wrong!!!! The mocrocontoller totally allows you to do X"
I pointed out that the article never said the microcontroller doesn't allow X.
As for input it can be always read to check real pin state, for example in open collector where 'high' value is determined by pull-up resistor, because microcontroller just make pin floating
C and Rust (can) have different calling conventions. C is the de-facto standard calling convention. When you mark a function as `extern "C"`, it means that it can be called as if it is a C function.
But what C code is running alongside the Rust code on the microcontroller that would make adhering to the C calling convention necessary? Is the hardware expecting the C calling convention?
yes, ARM ISRs use the C calling convention, and they should be marked as extern because they're effectively at global scope and can be invoked from anywhere.
I just skimmed over this, toolchain setup talks about TivaTi/Stellaris, code is for STM32F. My memory is muddy on this, so take it with a bit of salt. Cores have specific "interrupt context" (do not recall the actual name) with a specific set of call[er]-saved registers (EABI). Making fault handlers `extern C` is a way to ensure that these functions adhere to that EABI and do not clobber processor state. Probably.
I don't know much of anything about Rust, and I have a pretty limited knowledge of ARM, but I think that the 'Nested Vector Interrupt Controller' basically just pushes a stack frame and jumps to the memory location specified in a vector table that lives near the start of memory.
That sounds like how C functions are called; does Rust do something different?
The fundamentals of pushing a stack frame and jumping is the same, but the details of how arguments are passed (e.g. is a value in registers, or on the stack) and return values returned differ between the calling conventions.
It would have been awesome if this had shown the resulting assembly for the first few cases, too.