let mut dp = pac::Peripherals::take().unwrap();
dp.RCC.ahb2enr.modify(|_, w| w.gpioaen().set_bit());
In C that would be: RCC->AHBENR2 |= RCC_AHBENR2_GPIOAEN;
Okay, you only have to take ownership of the Peripherals object once, but what about when they want to set a UART register in a function? Rust wants mutable pointers to have a trail of ownership outside of 'unsafe' blocks, so now we need: pub fn setup_transmission(regs: &mut R, [...])
where R: Deref<Target = pac::usart1::RegisterBlock> {
[...]
regs.cr2.modify(|_, w| w.stop().bits(config.stop_bits as u8));
[...]
}
This is a bit of a construction, in C(++) we can just do something like: void setup_transmission([...]) {
[...]
USART1->CR2 &= ~(USART_CR2_STOP_BITS);
USART1->CR2 |= ((uint8_t)config.stop_bits << USART_CR2_STOP_BITS_Pos);
[...]
}
The Rust snippet seems like it would be safer in a multi-threaded environment like an RTOS, or in cases where interrupts might modify peripheral registers, but...there are so many syntactical details to remember.
I think the 'peripheral access crates' try to auto-generate those sorts of struct objects from SVD files, but you still need to obey Rust's ownership rules to get the advantage of its built-in memory safety.
So if a function needs to modify a peripheral's memory, it needs to claim mutable ownership of the relevant peripheral registers. That's a feature of the language which makes it 'safer', but the cost/benefit calculation is a bit different on embedded platforms. They have less memory, and static allocation is usually preferred.
Still, microcontrollers are getting faster quickly. You can even run Linux on some cortex-M4/M7 chips. The verbose syntax might be worthwhile if you're collaborating on complicated firmware.