embassy blinky for stm32g474

This commit is contained in:
Joe Ardent 2026-06-28 16:21:27 -07:00
commit b2cf4fd3bb
10 changed files with 1737 additions and 0 deletions

28
.cargo/config.toml Normal file
View file

@ -0,0 +1,28 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = ["probe-rs", "run", "--chip", "STM32G474RE", "--log-format=oneline"]
# If you have an nRF52, you might also want to add "--allow-erase-all" to the list
rustflags = [
"-C", "linker=flip-link",
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=-Tdefmt.x",
# This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
# See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
"-C", "link-arg=--nmagic",
]
[build]
# Select the correct target for your processor:
# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
# target = "thumbv7m-none-eabi" # Cortex-M3
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
[env]
DEFMT_LOG = "trace"
[alias]
# `cargo rb foo` will expand to `cargo run --bin foo`
rb = "run --bin"
# `cargo rrb foo` will expand to `cargo run --release --bin foo`
rrb = "run --release --bin"

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

1201
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

59
Cargo.toml Normal file
View file

@ -0,0 +1,59 @@
[package]
authors = ["Joe Ardent <code@ardent.nebcorp.com>"]
name = "treatbot-5k"
edition = "2024"
version = "50.0.0"
default-run = "treatbot-5k"
# To run all the tests via `cargo test` the tests need to be explicitly disabled for the binary targets
# If you use a standard main.rs file the following is sufficient:
[[bin]]
name = "treatbot-5k"
test = false
[lib]
harness = false
# needed for each integration test
[[test]]
name = "integration"
harness = false
[dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
defmt = "1.0"
defmt-rtt = "1.0"
embassy-executor = { version = "0.10.0", features = ["defmt", "platform-cortex-m", "executor-thread"] }
embassy-futures = "0.1.2"
embassy-stm32 = { version = "0.6.0", features = ["defmt", "stm32g474re", "memory-x", "unstable-pac", "exti", "dual-bank", "stm32-hrtim", "time-driver-any"] }
embassy-sync = { version = "0.8.0", features = ["defmt"] }
embassy-time = { version = "0.5.1", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
panic-probe = { version = "1.0", features = ["print-defmt"] }
semihosting = "0.1.20"
# stm32g4xx-hal = { version = "0.1.0", features = ["defmt", "stm32g474", "hrtim"] }
[dev-dependencies]
defmt-test = "0.3"
# cargo build/run
[profile.dev]
# default is opt-level = '0', but that makes very
# verbose machine code
opt-level = 's'
# trade compile speed for slightly better optimisations
codegen-units = 1
# cargo build/run --release
[profile.release]
# default is opt-level = '3', but that makes quite
# verbose machine code
opt-level = 's'
# trade compile speed for slightly better optimisations
codegen-units = 1
# Use Link Time Optimisations to further inline things across
# crates
lto = 'fat'
# Leave the debug symbols in (default is no debug info)
debug = 2

35
build.rs Normal file
View file

@ -0,0 +1,35 @@
//! This build script copies the `memory.x` file from the crate root into
//! a directory where the linker can always find it at build time.
//! For many projects this is optional, as the linker always searches the
//! project root directory -- wherever `Cargo.toml` is. However, if you
//! are using a workspace or have a more complicated build setup, this
//! build script becomes required. Additionally, by requesting that
//! Cargo re-run the build script whenever `memory.x` is changed,
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.
//!
//! The build script also sets the linker flags to tell it which link script to use.
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
// By default, Cargo will re-run a build script whenever
// any file in the project changes. By specifying `memory.x`
// here, we ensure the build script is only re-run when
// `memory.x` is changed.
println!("cargo:rerun-if-changed=memory.x");
// we're setting linker args in .cargo/config.toml via rustc flags
}

232
generated.md Normal file
View file

@ -0,0 +1,232 @@
# `app-template`
> Quickly set up a [`probe-rs`] + [`defmt`] + [`flip-link`] embedded project
[`probe-rs`]: https://crates.io/crates/probe-rs
[`defmt`]: https://github.com/knurling-rs/defmt
[`flip-link`]: https://github.com/knurling-rs/flip-link
## Dependencies
### 1. `flip-link`:
```bash
cargo install flip-link
```
### 2. `probe-rs`:
Install probe-rs by following the instructions at <https://probe.rs/docs/getting-started/installation/>.
### 3. [`cargo-generate`]:
```bash
cargo install cargo-generate
```
[`cargo-generate`]: https://crates.io/crates/cargo-generate
> *Note:* You can also just clone this repository instead of using `cargo-generate`, but this involves additional manual adjustments.
## Setup
### 1. Initialize the project template
```bash
cargo generate \
--git https://github.com/knurling-rs/app-template \
--branch main \
--name my-app
```
If you look into your new `my-app` folder, you'll find that there are a few `TODO`s in the files marking the properties you need to set.
Let's walk through them together now.
### 2. Set `probe-rs` chip
Pick a chip from ` probe-rs chip list` and enter it into `.cargo/config.toml`.
If, for example, you have a nRF52840 Development Kit as used in one of [our exercises], replace `{{chip}}` with `nRF52840_xxAA`.
[our workshops]: https://rust-exercises.ferrous-systems.com
```diff
# .cargo/config.toml
-runner = ["probe-rs", "run", "--chip", "$CHIP", "--log-format=oneline"]
+runner = ["probe-rs", "run", "--chip", "nRF52840_xxAA", "--log-format=oneline"]
```
### 3. Adjust the compilation target
In `.cargo/config.toml`, pick the right compilation target for your board.
```diff
# .cargo/config.toml
[build]
-target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
-# target = "thumbv7m-none-eabi" # Cortex-M3
-# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
-# target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
+target = "thumbv7em-none-eabihf" # Cortex-M4F (with FPU)
```
Add the target with `rustup`.
```bash
rustup target add thumbv7em-none-eabihf
```
### 4. Add a HAL as a dependency
In `Cargo.toml`, list the Hardware Abstraction Layer (HAL) for your board as a dependency.
For the nRF52840 you'll want to use the [`nrf52840-hal`].
[`nrf52840-hal`]: https://crates.io/crates/nrf52840-hal
```diff
# Cargo.toml
[dependencies]
-# some-hal = "1.2.3"
+nrf52840-hal = "0.14.0"
```
⚠️ Note for RP2040 users ⚠️
You will need to not just specify the `rp-hal` HAL, but a BSP (board support crate) which includes a second stage bootloader. Please find a list of available BSPs [here](https://github.com/rp-rs/rp-hal-boards#packages).
### 5. Import your HAL
Now that you have selected a HAL, fix the HAL import in `src/lib.rs`
```diff
// my-app/src/lib.rs
-// use some_hal as _; // memory layout
+use nrf52840_hal as _; // memory layout
```
### (6. Get a linker script)
Some HAL crates require that you manually copy over a file called `memory.x` from the HAL to the root of your project. For nrf52840-hal, this is done automatically so no action is needed. For other HAL crates, see their documentation on where to find an example file.
The `memory.x` file should look something like:
```text
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 1024K
RAM : ORIGIN = 0x20000000, LENGTH = 256K
}
```
The `memory.x` file is included in the `cortex-m-rt` linker script `link.x`, and so `link.x` is the one you should tell `rustc` to use (see the `.cargo/config.toml` file where we do that).
### 7. Run!
You are now all set to `cargo-run` your first `defmt`-powered application!
There are some examples in the `src/bin` directory.
Start by `cargo run`-ning `my-app/src/bin/hello.rs`:
```console
$ # `rb` is an alias for `run --bin`
$ cargo rb hello
Finished `dev` profile [optimized + debuginfo] target(s) in 0.01s
Running `probe-rs run --chip nrf52840_xxaa --log-format=oneline target/thumbv6m-none-eabi/debug/hello`
Erasing ✔ 100% [####################] 8.00 KiB @ 15.79 KiB/s (took 1s)
Programming ✔ 100% [####################] 8.00 KiB @ 13.19 KiB/s (took 1s) Finished in 1.11s
Hello, world!
$ echo $?
0
```
If you're running out of memory (`flip-link` bails with an overflow error), you can decrease the size of the device memory buffer by setting the `DEFMT_RTT_BUFFER_SIZE` environment variable. The default value is 1024 bytes, and powers of two should be used for optimal performance:
```console
$ DEFMT_RTT_BUFFER_SIZE=64 cargo rb hello
```
### (8. Set `rust-analyzer.linkedProjects`)
If you are using [rust-analyzer] with VS Code for IDE-like features you can add following configuration to your `.vscode/settings.json` to make it work transparently across workspaces. Find the details of this option in the [RA docs].
```json
{
"rust-analyzer.linkedProjects": [
"Cargo.toml",
"firmware/Cargo.toml",
]
}
```
[RA docs]: https://rust-analyzer.github.io/manual.html#configuration
[rust-analyzer]: https://rust-analyzer.github.io/
## Running tests
The template comes configured for running unit tests and integration tests on the target.
Unit tests reside in the library crate and can test private API; the initial set of unit tests are in `src/lib.rs`.
`cargo test --lib` will run those unit tests.
```console
$ cargo test --lib
Compiling example v0.1.0 (./knurling-rs/example)
Finished `test` profile [optimized + debuginfo] target(s) in 0.15s
Running unittests src/lib.rs (target/thumbv6m-none-eabi/debug/deps/example-2b0d0e25d141bf57)
Erasing ✔ 100% [####################] 8.00 KiB @ 15.99 KiB/s (took 1s)
Programming ✔ 100% [####################] 8.00 KiB @ 13.33 KiB/s (took 1s) Finished in 1.10s
(1/1) running `it_works`...
all tests passed!
```
Integration tests reside in the `tests` directory; the initial set of integration tests are in `tests/integration.rs`.
`cargo test --test integration` will run those integration tests.
Note that the argument of the `--test` flag must match the name of the test file in the `tests` directory.
```console
$ cargo test --test integration
Compiling example v0.1.0 (./knurling-rs/example)
Finished `test` profile [optimized + debuginfo] target(s) in 0.10s
Running tests/integration.rs (target/thumbv6m-none-eabi/debug/deps/integration-aaaff41151f6a722)
Erasing ✔ 100% [####################] 8.00 KiB @ 16.03 KiB/s (took 0s)
Programming ✔ 100% [####################] 8.00 KiB @ 13.19 KiB/s (took 1s) Finished in 1.11s
(1/1) running `it_works`...
all tests passed!
```
Note that to add a new test file to the `tests` directory you also need to add a new `[[test]]` section to `Cargo.toml`.
To run all the tests via `cargo test` the tests need to be explicitly disabled for all the existing binary targets.
See `Cargo.toml` for details on how to do this.
## Support
`app-template` is part of the [Knurling] project, [Ferrous Systems]' effort at
improving tooling used to develop for embedded systems.
If you think that our work is useful, consider sponsoring it via [GitHub
Sponsors].
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
licensed as above, without any additional terms or conditions.
[Knurling]: https://knurling.ferrous-systems.com
[Ferrous Systems]: https://ferrous-systems.com/
[GitHub Sponsors]: https://github.com/sponsors/knurling-rs

33
memory.x Normal file
View file

@ -0,0 +1,33 @@
MEMORY
{
/* NOTE 1 K = 1 KiBi = 1024 bytes */
FLASH : ORIGIN = 0x8000000, LENGTH = 256K
RAM : ORIGIN = 0x20000000, LENGTH = 80K
}
/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* You may want to use this variable to locate the call stack and static
variables in different memory regions. Below is shown the default value */
/* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */
/* You can use this symbol to customize the location of the .text section */
/* If omitted the .text section will be placed right after the .vector_table
section */
/* This is required only on microcontrollers that store some configuration right
after the vector table */
/* _stext = ORIGIN(FLASH) + 0x400; */
/* Example of putting non-initialized variables into custom RAM locations. */
/* This assumes you have defined a region RAM2 above, and in the Rust
sources added the attribute `#[link_section = ".ram2bss"]` to the data
you want to place there. */
/* Note that the section will not be zero-initialized by the runtime! */
/* SECTIONS {
.ram2bss (NOLOAD) : ALIGN(4) {
*(.ram2bss);
. = ALIGN(4);
} > RAM2
} INSERT AFTER .bss;
*/

43
src/lib.rs Normal file
View file

@ -0,0 +1,43 @@
#![no_main]
#![no_std]
// use defmt_rtt as _; // global logger
// use panic_probe as _;
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
#[defmt::panic_handler]
fn panic() -> ! {
cortex_m::asm::udf()
}
/// Terminates the application and makes a semihosting-capable debug tool exit
/// with status code 0.
pub fn exit() -> ! {
semihosting::process::exit(0);
}
/// Hardfault handler.
///
/// Terminates the application and makes a semihosting-capable debug tool exit
/// with an error. This seems better than the default, which is to spin in a
/// loop.
#[cortex_m_rt::exception]
unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
semihosting::process::exit(1);
}
// defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used
// once within a crate. the module can be in any file but there can only be at most
// one `#[tests]` module in this library crate
#[cfg(test)]
#[defmt_test::tests]
mod unit_tests {
use defmt::assert;
#[test]
fn it_works() {
assert!(true)
}
}

89
src/main.rs Normal file
View file

@ -0,0 +1,89 @@
#![no_std]
#![no_main]
use defmt::{info, unwrap};
use embassy_executor::Spawner;
use embassy_stm32::flash::{Flash, InterruptHandler};
use embassy_stm32::gpio::{AnyPin, Level, Output, Speed};
use embassy_stm32::{bind_interrupts, Peri};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
FLASH => InterruptHandler;
});
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
info!("Hello Flash!");
// Led should blink uninterrupted during erase operation
spawner.spawn(unwrap!(blinky(p.PA5.into())));
// let mut f = Flash::new(p.FLASH, Irqs);
// // Test on bank 2 so the CPU doesn't stall (code runs from bank 1).
// // G474RE in dual-bank mode: bank 2 starts at 256KB offset.
// // Erase 4KB (2 pages at 2KB each in dual-bank mode).
// test_flash(&mut f, 256 * 1024, 4 * 1024).await;
//treatbot_5k::exit()
}
#[embassy_executor::task]
async fn blinky(p: Peri<'static, AnyPin>) {
let mut led = Output::new(p, Level::High, Speed::Low);
loop {
info!("high");
led.set_high();
Timer::after_millis(300).await;
info!("low");
led.set_low();
Timer::after_millis(300).await;
}
}
async fn _test_flash(f: &mut Flash<'_>, offset: u32, size: u32) {
info!("Testing offset: {=u32:#X}, size: {=u32:#X}", offset, size);
info!("Reading...");
let mut buf = [0u8; 32];
unwrap!(f.blocking_read(offset, &mut buf));
info!("Read: {=[u8]:x}", buf);
info!("Erasing...");
unwrap!(f.erase(offset, offset + size).await);
info!("Reading...");
let mut buf = [0u8; 32];
unwrap!(f.blocking_read(offset, &mut buf));
info!("Read after erase: {=[u8]:x}", buf);
info!("Writing...");
unwrap!(
f.write(
offset,
&[
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32
]
)
.await
);
info!("Reading...");
let mut buf = [0u8; 32];
unwrap!(f.blocking_read(offset, &mut buf));
info!("Read: {=[u8]:x}", buf);
assert_eq!(
&buf[..],
&[
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32
]
);
info!("Flash async test passed!");
}

16
tests/integration.rs Normal file
View file

@ -0,0 +1,16 @@
#![no_std]
#![no_main]
use treatbot_5k as _; // memory layout + panic handler
// See https://crates.io/crates/defmt-test/0.3.0 for more documentation (e.g. about the 'state'
// feature)
#[defmt_test::tests]
mod tests {
use defmt::assert;
#[test]
fn it_works() {
assert!(true)
}
}