rustembeddedbare-metalstm32f3

Setting registers using embedded rust


So... I've been following the embedded rust book... and I'm currently reading about registers. Now, the book does suggest that I use an STM32F303VC discovery to avoid issues, but I couldn't find one, due to which I got a Nucleo F303RE instead. the targets and stuff for cargo remain the same. So I thought there wouldn't be any issues.

So, the MCU that I'm using has the Led attached to portA (0x48000000), which has a BSRR at an offset of 0x18. Now, I read in the datasheet, that the default value for port A is 0xa8000000, which I don't understand why. But when I try setting the led pins using ptr::write_volatile(PORTA_BSRR as *mut u32, 1 << 5); Nothing happens. Even my gdb terminal doesn't reflect any changes. So I tried checking with portE as the original tutorial suggests (0x48001018). But even then the register values don't change. I am unable to debug this issue.

Now, I am able to run the previous tutorials, and able to check variables and stuff. Nothing seems to be wrong with my stm as I am able to control it just fine using the stmc32cubeide.

here's the code in case you want to refer to it

EDIT: So, I read @Ikolbly 's comment, and looked into the RCC_AHBENR register, which I guess is like setting pinMode(pin, HIGH) in the arduino, it turns the port on.

I've modified the code to set that bit, but there seems to be no change. I'm guessing the auxiliary code already did it for portE which is why I didn't have to do any initialization for that... but even changing the register values for portE did not work.

//#![deny(unsafe_code)]
#![no_main]
#![no_std]

use aux5::entry;
use core::ptr;

#[entry]
fn main() -> ! {


    const RCC_AHBENR: u32 = 0x48000014;

    const PORTA_BSRR: u32 = 0x48000018;
    let _y;
    let x = 42;
    _y = x;

    unsafe {
        // EDIT enabling portA
        ptr::write_volatile(RCC_AHBENR as *mut u32, 1 << 17);


        // Toggling pin PA5
        ptr::write_volatile(PORTA_BSRR as *mut u32, 1 << 5);

        // Toggling random shit to see if it works
        ptr::write_volatile(PORTA_BSRR as *mut u32, 1 << 6);
        ptr::write_volatile(PORTA_BSRR as *mut u32, 1 << 7);
        ptr::write_volatile(PORTA_BSRR as *mut u32, 1 << 8);
    }
    // infinite loop; just so we don't leave this stack frame
    loop {}
}

Solution

  • Well over 99% of bare-metal is reading.

    So you figured out that from the nucleo datasheet D13 is LD2 which on the F303 variant is PA5. Port A pin 5.

    In the reference manual for the STM3F303R...

    The base address for

    RCC 0x40021000
    GPIOA 0x48000000
    

    You had the RCC base wrong it appears from the comments.

    RCC_AHBENR is at rcc base + 0x14. bit 17 is IOPAEN (I/O port A (clock) enable) reset value of 0x00000014 flash and sram enabled on reset, that is a pretty good thing to have enabled.

    Now the minimum you need to do for these ST ports to blink an led is, enable the gpio port, in this case bit 17 of RCC_AHBENR needs to be set. Then you set the GPIOA_MODER register to set the port as an output, you do not need to mess with the pullup/down nor speed and the output type register is already set for output on reset. So enable the port, and make it a push-pull output. then use bsrr to blink.

    GPIOA_MODER resets to 0xA8000000 making 15,14,13 alternate function after reset, you will find that these are jtag registers and two are also SWD data and I/O that you can use with an SWD debugger (comes built into the nucleo board). Easier to just copy the binary file over to the virtual thumb drive than to use swd directly (the debug mcu then uses swd to write the target mcu and reset it).

    And as pointed out above RCC_AHBENR has sram and flash enabled.

    As a general rule (there are exceptions) you want to do read-modify-writes. Simply writing 1<<17 to RCC_AHBENR will enable porta. Now you have just disabled sram AND flash during sleep mode. Now if you are not going into a sleep mode you are technically okay, but you really should

    x = read RCC_AHBENR
    x|= 1<<17
    write RCC_AHBENR = x
    

    Which can be done with an or equal, as a habit I would recommend against, it, but for hand optimization for this register, it is fine. I am not a Rust expert yet, so do not know the ways to do this. I think you should have tried asm or C first and succeeded then taken that knowledge to Rust.

    For the MODER register it becomes more apparent as to the nature of the read-modify-write because it is more than one bit.

    x = read GPIOA_MODER
    x&=(~(3<<10))
    x|=1<<10
    write GPIOA_MODER = x
    

    would be a proper generic way to do this, now looking at the documentation and looking at the rest value you could, technically just write 0xA8000400 in a single write or you could do a read-modify-write

    x = read
    x |= 1<<10
    write = x
    

    without clearing bit 11.

    Now some stm32 parts document this, some do not, most people are lucky because the use a canned tool or a read-modify-write habit, and the logic has a strangeness to it with the moder register in particular (likely to make their code work).

    If you were to prepare registers up front and do back to back writes

    ldr r0,=0x40021014
    ldr r1,=0x00020014
    ldr r2,=0x48000000
    ldr r3,=0xA8000400
    str r1,[r0]
    str r3,[r2]
    

    Assuming I have my bits and addresses right (which is not the point of the comment/issue) the back to back writes DO NOT WORK on all stm32 parts, you have a race condition the write to enable the i/o port needs a delay before talking to the i/o port. Now even on those chips, this

    ldr r0,=0x40021014
    ldr r1,=0x00020014
    ldr r2,=0x48000000
    str r1,[r0]
    ldr r3,[r2]
    modify
    write
    

    Does work, not dumb luck I am sure (it has the same race condition).

    And of course this can get worse if you adjust the clocks from reset speeds to faster values.

    If you do back to back writes in a high level language there is no guarantee it will generate the above it may generate

    ldr r0,=0x40021014
    ldr r1,=0x00020014
    ldr r2,=0x48000000
    ldr r3,=0xA8000400
    str r1,[r0]
    one or both of the r2/r3 inserted here
    str r3,[r2]
    

    And at least at reset clock speeds on the stm32 parts I can break, that works. But I have seen one compiler with one set of command line options that generated code that broke, while others or other options did not they inserted an instruction in between just due to the nature of the code generation (making the same C code in that case both work and not work based on dumb luck).

    So I recommend you do not write back to back, and/or you examine the output of the compiler (which you should be doing on any new project, especially one like your first bare-metal Rust program on a language that is tough at the moment to generate bare-metal code like this). (quite doable but the number of say C folks is still one in a million give or take, but for rust an small number out of the whole population of the planet).

    You should be examining the vector table, placement in the binary, things like the loads and stores and addresses and data, etc. Likewise when converting from whatever binary format to the raw binary format needed to copy to the nucleo board, examine that binary as well to see that it starts with the vector table and any padding needed is in place.

    So....

    Fix your rcc register address, and I would fix the data as well.

    Add a read or delay after rcc register write, can do a simple throwaway read of the gpio moder if you want, or better do a read-modify-write.

    Write moder with bits 11:10 as 0b01 to make it a general purpose output

    THEN you can mess with bit 5 and 16+5 in the BSRR.

    Lastly from the nucleo documentation you do not even have to look at the schematic. They nicely document under LD2 that

    the I/O is HIGH value, the LED is on
    

    So you want to SET PA5 to turn the led on and reset to turn it off.

    Write a 1<<(0+5) to GPIOA_BSRR. in one version of the code. Then write 1<<(16+5) in another. The first one should leave the led on, the second leave the led off. (THEN mess with a delay and trying to blink it).

    The infinite loop at the end is only needed of the bootstrap or pre main code messes with things on a return, if you wrote your own bootstrap then you could simply have it do an infinite loop on return from main (that is how I prefer it, sometimes a wfi/wfe loop, some libraries though will hose things on return thus this habit). Since you should be very aware of the boot process and code for any bare-metal project (vendor sandbox or your own), you should know the answer before writing main and know what the requirements are. And maybe you do in this case, just have not shown it here.