Hacker News new | past | comments | ask | show | jobs | submit login
Programming ARM μC in Rust at four different levels of abstraction (pramode.in)
184 points by signa11 on Feb 22, 2018 | hide | past | favorite | 32 comments



This is great and really helps emphasize why I should look into Rust some day (I program STM32s in C for a living).

It would have been awesome if this had shown the resulting assembly for the first few cases, too.



Thanks, that's very nice and impressive. Zero-cost abstractions are very attractive.


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.


> A GPIO pin starts out as an input; in that state, you can’t call the function set_high. It is a compile time error!

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 state of the pin is encoded in the type of the variable that is used to access the pin.

This kind of encoding prevents you from making the programming error of writing to an input pin - which is technically possible, just useless.


Not even remotely useless, it's often a feature.


Well, _if_ you need that feature, you can wrap the trait into another trait that allows a write to the pin, but discards it.

However, by default, nonsensical operations should not be possible.


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.


Oh? When would you use this? I'm sure there must be some reason, but I can't think of one.


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.


As mentioned above, in some uC's the output register defines the pull direction when the pin is an input.

In other cases, you want to avoid glitching when the pin switches to an output on startup.


It can be used to implement capacitive touch.


> 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.


The OP means that this is an incorrect model of the microcontroller.


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


... which is probably not what you wanted.

One would suspect that other functions in the HAL that control the feature you are looking for directly.


Why is it necessary to mark the interrupt handler and the panic formatting function as extern C functions? Is the C way of doing that set in silicon?


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.


There is an interrupt-mode stack and a user-mode stack, the stacks are switched on interrupt invocation.

The value pushed on to the stack is a special value that causes a pop into SP to switch stacks back on return.


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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: