Setting GPIO High or Low generates same binary Rust Embassy STM32

When my code sets GPIO High or Low, rustc still generates the exact same binary. The binary generated is only 24 bytes long and I don't think it contains any instructions relating to GPIO.

Here is my Rust code (I am aware of the trivial warnings):

#![no_std]
#![no_main]

use embassy_executor::Spawner;
use embassy_stm32::gpio::{Level, Output, Speed, Pin};
use embassy_stm32::Config;

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    let mut config = Config::default();
    let p = embassy_stm32::init(config);

    let mut pa3 = Output::new(p.PA3, Level::Low, Speed::VeryHigh);

    pa3.set_low();

    loop { }
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    loop { }
}

I ran cargo build and took the MD5 hash of the binary, then ran cargo clear which deleted the binary. Then I changed 2 lines:

let mut pa3 = Output::new(p.PA3, Level::Low, Speed::VeryHigh);
changed to:
let mut pa3 = Output::new(p.PA3, Level::High, Speed::VeryHigh);

And pa3.set_low(); changed to: pa3.set_high();

I ran cargo build and took the MD5 Hash of the new binary and it was exactly the same as the old one. Additionally, I added lines to set states of other pins like A4 to high, and the binary is also the same.

I tried to be very thorough as to not make any trivial mistakes. Here is what I know:
Even when I change the code as I described, the compiler still produces the exact same 24 byte binaries. Even when I add:

    pa3.set_high();
    embassy_time::Timer::after_millis(500).await;
    pa3.set_low();
    embassy_time::Timer::after_millis(500).await;

inside the loop, nothing changes.

Using advanced analysis (ChatGPT), the binary contains no instructions relating to GPIO. Maybe they are being optimized out for some reason? I am using Rust with Embassy on an STM32WLE5CBU6.

Additional Files

Cargo.toml

[package]
name = "spider-router"
version = "0.1.0"
edition = "2024"

[dependencies]
cortex-m-rt = "0.7.5"
embassy-executor = { version = "0.9.1", features = ["arch-cortex-m", "executor-thread"] }
embassy-stm32 = { version = "0.4.0", features = ["stm32wle5cb"] }
embassy-time = "0.5.0"

I will include any other config files as needed. Thanks.

I think your project is misconfigured, most likely, the linker script is not specified.

since you are using embassy, start with the configs of their example project here:

you can modify Cargo.toml to fit your application, but you need to copy at least these file: build.rs, memory.x, .cargo/config.toml from the example.

if you are not using embassy, you can copy the memory.x and .cargo/cofnig.toml file from the stm32wl-hal crate, or alternatively, just create your project using a template like this one:

3 Likes

Thank you. I added "-C", "link-arg=-Tlink.x" to the rust flags and add the cortex-m crate and it started working. Before, instead of "-C", "link-arg=-Tlink.x" I had "-C", "link-arg=-Tmemory.x". My new .cargo/config.toml looks like:

[build]
target = "thumbv7em-none-eabi"

[target.thumbv7em-none-eabi]
runner = "probe-rs run --chip STM32WLE5CB"
# The linker line is optional but fine to keep.
linker = "arm-none-eabi-gcc"
# This is the corrected line:
rustflags = [
  "-C", "link-arg=-Tlink.x"
]
1 Like

memory.x defines the memory map for the target, it is included by link.x, but should not be used standalone.

I think most tutorials/books/guides overlooked the low level aspect of rust on bare metal targets, such as how the vector table is generated, where are the boot/init code, etc. they just mention the memory.x, but not how it is used during the building process. this is very unfortunate, especially for people new to embedded rust.

this information is loosely documented in the runtime crate for the target architecture, such as cortex-m-rt or riscv-rt. quoting the relevant section in cortex-m-rt:

memory.x

This crate expects the user, or some other crate, to provide the memory layout of the target device via a linker script named memory.x, described in this section. The memory.x file is used during linking by the link.x script provided by this crate. If you are using a custom linker script, you do not need a memory.x file.

MEMORY

The linker script must specify the memory available in the device as, at least, two MEMORY regions: one named FLASH and one named RAM. The .text and .rodata sections of the program will be placed in the FLASH region, whereas the .bss and .data sections, as well as the heap, will be placed in the RAM region.

I recommend to study the code of the cortex-m-rt crate, you'll learn a lot technique details, for example, how the stack is initialized, where the vector table is placed, how the #[entry] and #[interrupt] attributes work, and so on.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.