commit a48ba2963d3eca77820bae3909ee560f520c90f9 Author: wieerwill Date: Sun Mar 8 19:41:38 2026 +0100 start tutorial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..27e1387 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Rust build output in workshop steps +tutorial/**/target/ + +# Probe-rs RTT logs +tutorial/**/target/logs/ + +# Editor temp files +*~ +*.swp +.DS_Store + +sources \ No newline at end of file diff --git a/HARDWARE.md b/HARDWARE.md new file mode 100644 index 0000000..91be202 --- /dev/null +++ b/HARDWARE.md @@ -0,0 +1,31 @@ +# Hardware Baseline (Bluepill + ST-Link) + +## Board and Probe + +- MCU board: STM32F103C8T6 Bluepill +- Debug probe: ST-Link (SWD) + +## SWD Wiring + +1. ST-Link `SWDIO` -> Bluepill `PA13` +2. ST-Link `SWCLK` -> Bluepill `PA14` +3. ST-Link `GND` -> Bluepill `GND` +4. ST-Link `3V3` -> Bluepill `3V3` (falls notwendig) +5. Optional: ST-Link `NRST` -> Bluepill `NRST` + +## Workshop I/O Wiring + +1. LED: + - Onboard LED an `PC13` (active-low) +2. Button: + - Externer Taster von `PA0` nach `GND` + - Interner Pull-up wird im Code aktiviert +3. Analog: + - Poti/Sensor-Ausgang an `PA1` + - Sensorversorgung über `3V3` + `GND` + +## Sanity Checks + +1. `probe-rs list` zeigt ST-Link an. +2. `probe-rs chip list | grep STM32F103C8` findet Target. +3. Beim Flashen keine "No probe found"-Meldung. diff --git a/README.md b/README.md new file mode 100644 index 0000000..96ec7b5 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Rust on Robots - Workshop (Didacta Ed.) + +Rust ist schnell, modern und hilft dabei, typische Embedded-Fehler früh zu vermeiden – genau das, was man auf Robotern braucht. +In diesem Workshop gebe ich eine kurze, praxisnahe Einführung in Rust und wir steigen dann direkt in gemeinsame Übungen ein: selbst Rust schreiben, kleine Aufgaben lösen und anschließend Rust auf einem STM32 Bluepill flashen und testen. +Ideal für RoboCup-Interessierte aus der außerschulischen bzw. beruflichen Bildung, die schon einmal programmiert haben und Rust als Werkzeug für robuste Robotik ausprobieren wollen. + +## Workshop Layout + +Dieses Repository ist als "Rust by Example"-ähnlicher Lernpfad aufgebaut: + +- `tutorial/00-setup-live` bis `tutorial/08-final-combined` +- Jeder Schritt hat: + - `task/` (Aufgabe mit TODOs) + - `solution/` (Referenzlösung) +- Teilnehmer arbeiten nur in `task/`. + +## Quick Start + +1. Host vorbereiten: + - `bash scripts/setup-live.sh` +2. Host prüfen: + - `bash scripts/verify-host.sh` +3. Probe prüfen: + - `bash scripts/verify-probe.sh` +4. Schritt ausführen: + - `bash scripts/run-step.sh 01 task` + +## Core Files + +- [WORKSHOP.md](/home/wieerwill/Dokumente/GitHub/didkata-rust-on-robots/WORKSHOP.md) +- [HARDWARE.md](/home/wieerwill/Dokumente/GitHub/didkata-rust-on-robots/HARDWARE.md) +- [TROUBLESHOOTING.md](/home/wieerwill/Dokumente/GitHub/didkata-rust-on-robots/TROUBLESHOOTING.md) +- [references/source-map.md](/home/wieerwill/Dokumente/GitHub/didkata-rust-on-robots/references/source-map.md) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 0000000..3cfd60d --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,41 @@ +# Troubleshooting + +## Host Setup + +1. `rustup: command not found` + - Rust via rustup installieren. +2. `probe-rs: command not found` + - `cargo install probe-rs-tools` +3. Target fehlt + - `rustup target add thumbv7m-none-eabi` + +## Probe / Flashing + +1. `No probe found` + - USB-Kabel/Port prüfen + - ST-Link Treiber/udev prüfen + - `probe-rs list` erneut ausführen +2. `chip not found` + - Runner-String prüfen: `STM32F103C8` +3. `Permission denied` (Linux) + - udev-Regeln für ST-Link setzen und neu laden + +## Runtime Behavior + +1. LED blinkt nicht + - PC13 active-low beachten + - Versorgung prüfen +2. Button reagiert nicht + - Taster nach GND verdrahten + - `PA0` korrekt belegt? +3. Analogwerte ändern sich nicht + - Sensor/Poti wirklich an `PA1` + - Gemeinsame Masse sicherstellen + +## Fallback in Session + +Wenn eine Station nicht stabil läuft: + +1. Mit funktionierender Nachbarn pairen. +2. `solution/` ausführen. +3. Später wieder auf `task/` zurückwechseln. diff --git a/WORKSHOP.md b/WORKSHOP.md new file mode 100644 index 0000000..5fcd71a --- /dev/null +++ b/WORKSHOP.md @@ -0,0 +1,71 @@ +# Workshop Plan (60 Minutes) + +## Goal + +Von Rust-Basics zu echter Hardware auf STM32F103C8 Bluepill: + +- LED blinken +- Button einlesen +- Analogwert einlesen +- Ausgabe live über `probe-rs` / RTT sehen + +## Timing + +1. 00:00-00:10: Setup + Probe Smoke Test (`00`) +2. 00:10-00:28: Rust Basics (`01`-`03`) +3. 00:28-00:36: Erstes `no_std` Firmware-Projekt (`04`) +4. 00:36-00:44: LED Blinky (`05`) +5. 00:44-00:50: Button Input (`06`) +6. 00:50-00:56: Analog Readout (`07`) +7. 00:56-01:00: Integration (`08`) + +## Rules During Workshop + +1. Bearbeite nur `task/`. +2. Nutze `solution/` nur als Hilfe bei Blockern. +3. Falls >3 Minuten blockiert: Diff vergleichen, minimalen Fix übernehmen, weiter. +4. Bei Embedded-Schritten: Board vor jedem `cargo run` korrekt verkabeln. + +## Repo Management During Workshop + +1. `main` enthält Aufgaben und Referenzlösungen. +2. Instructor kann Wiederherstellungspunkte als Tags setzen: + - `step-00-start` ... `step-08-solution` +3. Teilnehmer arbeiten auf lokalem Branch: + - `participant/` +4. Wenn eine Aufgabe nicht weitergeht: + - minimalen Diff aus `solution/` übernehmen und fortfahren. + +## Step Contract + +Jeder Schritt enthält: + +- `README.md` mit Ziel, Ablauf, Done-Check +- `task/` (Aufgabe) +- `solution/` (Referenz) + +## Script Contract + +- `scripts/setup-live.sh` +- `scripts/verify-host.sh` +- `scripts/verify-probe.sh` +- `scripts/run-step.sh ` +- `scripts/check-step.sh ` + +## Target Contract (Embedded Steps 04-08) + +- Target: `thumbv7m-none-eabi` +- Runner: `probe-rs run --chip STM32F103C8` +- Logging: `defmt-rtt` + `panic-probe` + +## Acceptance Scenarios + +1. Steps `01`-`03`: `cargo run` funktioniert für `task/` und `solution/`. +2. Steps `04`-`08`: `cargo build --release` funktioniert für `task/` und `solution/`. +3. Probe-Verbindung: + - `probe-rs list` erkennt ST-Link. +4. Behavior: + - `05`: LED blinkt + - `06`: Press/Release Logs + - `07`: ADC-Wert reagiert auf Eingang + - `08`: integriertes Verhalten läuft stabil diff --git a/source-map.md b/source-map.md new file mode 100644 index 0000000..41dd44b --- /dev/null +++ b/source-map.md @@ -0,0 +1,39 @@ +# Source Map + +Dieses Mapping dokumentiert, welche gesammelten Quellen in welche Workshop-Bausteine eingeflossen sind. + +## Rust Basics (`01`-`03`) + +1. `sources/rust-by-example/*` + - Schrittweiser Lernstil mit kleinen, lauffähigen Beispielen +2. `sources/Introduction - A Gentle Introduction to Rust.md` + - sanfter Einstieg in Ownership/Borrowing +3. `sources/Learn Rust - Rust Programming Language.md` + - Orientierung an offiziellen Lernpfaden + +## Embedded Foundations (`04`) + +1. `sources/Intro to Rust on Embedded Your First no_std Project with Code Examples – Hubble Network Community.md` + - `no_std`, PAC/HAL/BSP, Einstiegspfad +2. `sources/rust-embedded-book/src/intro/no-std.md` + - saubere no_std-Grundlagen +3. `sources/rust-embedded-book/src/peripherals/singletons.md` + - Ownership auf Hardware-Peripherie übertragen + +## Tooling and Workflow (`00`, scripts) + +1. `sources/Embedded Rust Toolchains – Albert Skog.md` + - Probe-rs/probe-run Workflow-Einordnung +2. `sources/Embassy Book.md` + - praktischer Cargo + probe-rs Runner-Ansatz + +## STM32 Practical Steps (`05`-`08`) + +1. `sources/rust-embassy-stm32/src/bin/{blinky.rs,button.rs,adc.rs}` + - konkrete Zielübungen (LED/Button/ADC + Logs) +2. `sources/STM32F4 Embedded Rust at the HAL *.md` + - didaktische Sequenz: GPIO -> Input -> ADC -> Integration +3. `sources/AnyLeaf articles Writing embedded firmware using Rust.md` + - Register/HAL-Abstraktionslevel, Embedded-Rust Argumentation +4. `sources/Brave new IO Embedded in Rust.md` + - Ownership/Typisierung für sichere Hardwarezugriffe diff --git a/tutorial/00-setup-live/README.md b/tutorial/00-setup-live/README.md new file mode 100644 index 0000000..dc93e64 --- /dev/null +++ b/tutorial/00-setup-live/README.md @@ -0,0 +1,17 @@ +# 00 - Setup Live (10 min) + +## Goal + +Jede Station kann Rust-Code bauen und eine Bluepill über ST-Link erkennen. + +## Steps + +1. install rustup +2. add target +3. add probe-rs + +## Done when + +1. `rustc`, `cargo`, `probe-rs` verfügbar. +2. `thumbv7m-none-eabi` installiert. +3. `probe-rs list` zeigt die Probe. diff --git a/tutorial/00-setup-live/task/checklist.md b/tutorial/00-setup-live/task/checklist.md new file mode 100644 index 0000000..89eb14a --- /dev/null +++ b/tutorial/00-setup-live/task/checklist.md @@ -0,0 +1,7 @@ +# Task Checklist + +- [ ] Rust toolchain installiert (`rustup`, `cargo`, `rustc`) +- [ ] Target `thumbv7m-none-eabi` installiert +- [ ] `probe-rs-tools` installiert +- [ ] ST-Link wird von `probe-rs list` erkannt +- [ ] `probe-rs chip list | grep STM32F103C8` liefert Treffer diff --git a/tutorial/00-setup-live/task/setup.sh b/tutorial/00-setup-live/task/setup.sh new file mode 100755 index 0000000..776e203 --- /dev/null +++ b/tutorial/00-setup-live/task/setup.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[verify-host] rustup" +if ! command -v rustup >/dev/null 2>&1; then + echo "rustup not found. Install from https://rustup.rs/ and rerun." + echo "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh" + exit 1 +fi +rustup --version + +echo "[verify-host] cargo" +cargo --version + +echo "[verify-host] rustc" +rustc --version + +echo "[setup] ensuring stable toolchain..." +rustup toolchain install stable +rustup default stable + +echo "[setup] adding embedded target thumbv7m-none-eabi..." +rustup target add thumbv7m-none-eabi + +echo "[setup] checking probe-rs..." +if ! command -v probe-rs >/dev/null 2>&1; then + echo "[setup] installing probe-rs-tools via cargo..." + cargo install probe-rs-tools + sudo groupadd --system plugdev + sudo usermod -a -G plugdev $USER +else + echo "[setup] probe-rs already installed" + probe-rs --version +fi + +echo "[setup] done" diff --git a/tutorial/01-rust-hello/README.md b/tutorial/01-rust-hello/README.md new file mode 100644 index 0000000..27a3de8 --- /dev/null +++ b/tutorial/01-rust-hello/README.md @@ -0,0 +1,20 @@ +# 01 - Rust Hello (5 min) + +## Goal + +Erstes lauffähiges Rust-Programm, das Daten über `println!` ausgibt. + +## Run + +- `bash scripts/run-step.sh 01 task` + +## Tasks + +1. Öffne `task/src/main.rs`. +2. Ersetze die TODO-Werte. +3. Starte erneut mit `cargo run`. + +## Done when + +1. Das Programm kompiliert. +2. Die Ausgabe enthält euren Namen und Workshop-Titel. diff --git a/tutorial/01-rust-hello/solution/Cargo.toml b/tutorial/01-rust-hello/solution/Cargo.toml new file mode 100644 index 0000000..4cd86be --- /dev/null +++ b/tutorial/01-rust-hello/solution/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "step01_rust_hello_solution" +version = "0.1.0" +edition = "2021" diff --git a/tutorial/01-rust-hello/solution/src/main.rs b/tutorial/01-rust-hello/solution/src/main.rs new file mode 100644 index 0000000..fd51fa1 --- /dev/null +++ b/tutorial/01-rust-hello/solution/src/main.rs @@ -0,0 +1,8 @@ +fn main() { + let participant_name = "Didacta Participant"; + let workshop_title = "Rust on Embedded @ Didacta"; + + println!("Hello, {participant_name}!"); + println!("Welcome to: {workshop_title}"); + println!("Next step: types, control flow, and ownership."); +} diff --git a/tutorial/01-rust-hello/task/Cargo.toml b/tutorial/01-rust-hello/task/Cargo.toml new file mode 100644 index 0000000..cd1abb5 --- /dev/null +++ b/tutorial/01-rust-hello/task/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "step01_rust_hello_task" +version = "0.1.0" +edition = "2021" diff --git a/tutorial/01-rust-hello/task/src/main.rs b/tutorial/01-rust-hello/task/src/main.rs new file mode 100644 index 0000000..e4fff80 --- /dev/null +++ b/tutorial/01-rust-hello/task/src/main.rs @@ -0,0 +1,9 @@ +fn main() { + // TODO: Replace these strings with your own values. + let participant_name = "Your Name"; + let workshop_title = "Rust on Embedded @ Didacta"; + + println!("Hello, {participant_name}!"); + println!("Welcome to: {workshop_title}"); + println!("Next step: types, control flow, and ownership."); +} diff --git a/tutorial/02-rust-types-control/README.md b/tutorial/02-rust-types-control/README.md new file mode 100644 index 0000000..e708044 --- /dev/null +++ b/tutorial/02-rust-types-control/README.md @@ -0,0 +1,20 @@ +# 02 - Types and Control Flow (6 min) + +## Goal + +Mit Typen, `if`, und `match` kleine Entscheidungen ausdrücken. + +## Run + +- `bash scripts/run-step.sh 02 task` + +## Tasks + +1. Passe die Schwellwerte in `classify_adc` an. +2. Teste mit mehreren ADC-Werten. +3. Beobachte die Ausgabe von `pressed/released` und `low/medium/high`. + +## Done when + +1. Das Programm kompiliert. +2. Für unterschiedliche Werte entstehen unterschiedliche Klassifizierungen. diff --git a/tutorial/02-rust-types-control/solution/Cargo.toml b/tutorial/02-rust-types-control/solution/Cargo.toml new file mode 100644 index 0000000..e52602f --- /dev/null +++ b/tutorial/02-rust-types-control/solution/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "step02_rust_types_control_solution" +version = "0.1.0" +edition = "2021" diff --git a/tutorial/02-rust-types-control/solution/src/main.rs b/tutorial/02-rust-types-control/solution/src/main.rs new file mode 100644 index 0000000..9d3af87 --- /dev/null +++ b/tutorial/02-rust-types-control/solution/src/main.rs @@ -0,0 +1,31 @@ +fn classify_button(pressed: bool) -> &'static str { + if pressed { + "pressed" + } else { + "released" + } +} + +fn classify_adc(raw: u16) -> &'static str { + const LOW_MAX: u16 = 1365; + const MID_MAX: u16 = 2730; + + match raw { + 0..=LOW_MAX => "low", + (LOW_MAX + 1)..=MID_MAX => "medium", + _ => "high", + } +} + +fn main() { + let button_samples = [false, true, true, false]; + let adc_samples: [u16; 5] = [120, 900, 1500, 2600, 3900]; + + for pressed in button_samples { + println!("button: {}", classify_button(pressed)); + } + + for raw in adc_samples { + println!("adc raw={raw:4} => {}", classify_adc(raw)); + } +} diff --git a/tutorial/02-rust-types-control/task/Cargo.toml b/tutorial/02-rust-types-control/task/Cargo.toml new file mode 100644 index 0000000..ef1f9dd --- /dev/null +++ b/tutorial/02-rust-types-control/task/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "step02_rust_types_control_task" +version = "0.1.0" +edition = "2021" diff --git a/tutorial/02-rust-types-control/task/src/main.rs b/tutorial/02-rust-types-control/task/src/main.rs new file mode 100644 index 0000000..301b6d7 --- /dev/null +++ b/tutorial/02-rust-types-control/task/src/main.rs @@ -0,0 +1,29 @@ +fn classify_button(pressed: bool) -> &'static str { + if pressed { + "pressed" + } else { + "released" + } +} + +fn classify_adc(raw: u16) -> &'static str { + // TODO: Adjust thresholds if you want a different behavior. + match raw { + 0..=1200 => "low", + 1201..=2800 => "medium", + _ => "high", + } +} + +fn main() { + let button_samples = [false, true, true, false]; + let adc_samples: [u16; 5] = [120, 900, 1500, 2600, 3900]; + + for pressed in button_samples { + println!("button: {}", classify_button(pressed)); + } + + for raw in adc_samples { + println!("adc raw={raw:4} => {}", classify_adc(raw)); + } +} diff --git a/tutorial/03-rust-ownership-borrow/README.md b/tutorial/03-rust-ownership-borrow/README.md new file mode 100644 index 0000000..f57c917 --- /dev/null +++ b/tutorial/03-rust-ownership-borrow/README.md @@ -0,0 +1,20 @@ +# 03 - Ownership and Borrowing (7 min) + +## Goal + +Ownership und Borrowing an einem kleinen Robot-State praktisch verstehen. + +## Run + +- `bash scripts/run-step.sh 03 task` + +## Tasks + +1. Lies die Funktionen mit `&RobotState` und `&mut RobotState`. +2. Ergänze die TODOs in der Task-Datei. +3. Achte auf Compiler-Fehler, wenn mutable/immutable borrows kollidieren. + +## Done when + +1. Das Programm kompiliert. +2. LED-State, Button-Count und ADC-Wert werden korrekt aktualisiert. diff --git a/tutorial/03-rust-ownership-borrow/solution/Cargo.toml b/tutorial/03-rust-ownership-borrow/solution/Cargo.toml new file mode 100644 index 0000000..1063b94 --- /dev/null +++ b/tutorial/03-rust-ownership-borrow/solution/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "step03_rust_ownership_borrow_solution" +version = "0.1.0" +edition = "2021" diff --git a/tutorial/03-rust-ownership-borrow/solution/src/main.rs b/tutorial/03-rust-ownership-borrow/solution/src/main.rs new file mode 100644 index 0000000..ad1b38c --- /dev/null +++ b/tutorial/03-rust-ownership-borrow/solution/src/main.rs @@ -0,0 +1,41 @@ +#[derive(Debug)] +struct RobotState { + led_on: bool, + button_count: u32, + last_adc: u16, +} + +fn print_state(state: &RobotState) { + println!( + "state => led_on={}, button_count={}, last_adc={}", + state.led_on, state.button_count, state.last_adc + ); +} + +fn toggle_led(state: &mut RobotState) { + state.led_on = !state.led_on; +} + +fn record_button_press(state: &mut RobotState) { + state.button_count += 1; +} + +fn update_adc(state: &mut RobotState, raw: u16) { + state.last_adc = raw; +} + +fn main() { + let mut state = RobotState { + led_on: false, + button_count: 0, + last_adc: 0, + }; + + print_state(&state); + + toggle_led(&mut state); + record_button_press(&mut state); + update_adc(&mut state, 2024); + + print_state(&state); +} diff --git a/tutorial/03-rust-ownership-borrow/task/Cargo.toml b/tutorial/03-rust-ownership-borrow/task/Cargo.toml new file mode 100644 index 0000000..c39bc4c --- /dev/null +++ b/tutorial/03-rust-ownership-borrow/task/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "step03_rust_ownership_borrow_task" +version = "0.1.0" +edition = "2021" diff --git a/tutorial/03-rust-ownership-borrow/task/src/main.rs b/tutorial/03-rust-ownership-borrow/task/src/main.rs new file mode 100644 index 0000000..f012f15 --- /dev/null +++ b/tutorial/03-rust-ownership-borrow/task/src/main.rs @@ -0,0 +1,42 @@ +#[derive(Debug)] +struct RobotState { + led_on: bool, + button_count: u32, + last_adc: u16, +} + +fn print_state(state: &RobotState) { + println!( + "state => led_on={}, button_count={}, last_adc={}", + state.led_on, state.button_count, state.last_adc + ); +} + +fn toggle_led(state: &mut RobotState) { + state.led_on = !state.led_on; +} + +fn record_button_press(state: &mut RobotState) { + state.button_count += 1; +} + +fn update_adc(state: &mut RobotState, raw: u16) { + // TODO: keep this function as the single writer for ADC state. + state.last_adc = raw; +} + +fn main() { + let mut state = RobotState { + led_on: false, + button_count: 0, + last_adc: 0, + }; + + print_state(&state); + + toggle_led(&mut state); + record_button_press(&mut state); + update_adc(&mut state, 2024); + + print_state(&state); +} diff --git a/tutorial/04-embedded-no-std-hello/README.md b/tutorial/04-embedded-no-std-hello/README.md new file mode 100644 index 0000000..029e1ad --- /dev/null +++ b/tutorial/04-embedded-no-std-hello/README.md @@ -0,0 +1,20 @@ +# 04 - Embedded no_std Hello (8 min) + +## Goal + +Erstes Bare-Metal-Programm mit `#![no_std]`, `#![no_main]` und RTT Logs. + +## Run + +- `bash scripts/run-step.sh 04 task` + +## Tasks + +1. Lies die Top-Level-Attribute in `task/src/main.rs`. +2. Passe die Log-Ausgaben an. +3. Flashe mit `cargo run --release`. + +## Done when + +1. Firmware läuft auf Bluepill. +2. RTT zeigt zyklisch eine "alive"-Meldung. diff --git a/tutorial/04-embedded-no-std-hello/solution/.cargo/config.toml b/tutorial/04-embedded-no-std-hello/solution/.cargo/config.toml new file mode 100644 index 0000000..d588bc1 --- /dev/null +++ b/tutorial/04-embedded-no-std-hello/solution/.cargo/config.toml @@ -0,0 +1,12 @@ +[target.thumbv7m-none-eabi] +runner = "probe-rs run --chip STM32F103C8" +rustflags = [ + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", +] + +[build] +target = "thumbv7m-none-eabi" + +[env] +DEFMT_LOG = "info" diff --git a/tutorial/04-embedded-no-std-hello/solution/Cargo.toml b/tutorial/04-embedded-no-std-hello/solution/Cargo.toml new file mode 100644 index 0000000..63147d9 --- /dev/null +++ b/tutorial/04-embedded-no-std-hello/solution/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "step04_embedded_no_std_hello_solution" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = "0.7.7" +cortex-m-rt = "0.7.5" +defmt = "0.3.10" +defmt-rtt = "0.4.2" +panic-probe = { version = "0.3.2", features = ["print-defmt"] } +nb = "1.1.0" + +[dependencies.stm32f1xx-hal] +version = "0.11.0" +features = ["rt", "stm32f103", "medium"] + +[profile.release] +codegen-units = 1 +debug = 2 +lto = true +opt-level = "z" diff --git a/tutorial/04-embedded-no-std-hello/solution/memory.x b/tutorial/04-embedded-no-std-hello/solution/memory.x new file mode 100644 index 0000000..19825e7 --- /dev/null +++ b/tutorial/04-embedded-no-std-hello/solution/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 64K + RAM : ORIGIN = 0x20000000, LENGTH = 20K +} diff --git a/tutorial/04-embedded-no-std-hello/solution/rust-toolchain.toml b/tutorial/04-embedded-no-std-hello/solution/rust-toolchain.toml new file mode 100644 index 0000000..e2ff99e --- /dev/null +++ b/tutorial/04-embedded-no-std-hello/solution/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt"] +targets = ["thumbv7m-none-eabi"] diff --git a/tutorial/04-embedded-no-std-hello/solution/src/main.rs b/tutorial/04-embedded-no-std-hello/solution/src/main.rs new file mode 100644 index 0000000..b603c53 --- /dev/null +++ b/tutorial/04-embedded-no-std-hello/solution/src/main.rs @@ -0,0 +1,17 @@ +#![no_std] +#![no_main] + +use cortex_m::asm; +use cortex_m_rt::entry; +use defmt::info; +use {defmt_rtt as _, panic_probe as _}; + +#[entry] +fn main() -> ! { + info!("step04: boot ok"); + + loop { + asm::delay(8_000_000); + info!("step04: alive"); + } +} diff --git a/tutorial/04-embedded-no-std-hello/task/.cargo/config.toml b/tutorial/04-embedded-no-std-hello/task/.cargo/config.toml new file mode 100644 index 0000000..d588bc1 --- /dev/null +++ b/tutorial/04-embedded-no-std-hello/task/.cargo/config.toml @@ -0,0 +1,12 @@ +[target.thumbv7m-none-eabi] +runner = "probe-rs run --chip STM32F103C8" +rustflags = [ + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", +] + +[build] +target = "thumbv7m-none-eabi" + +[env] +DEFMT_LOG = "info" diff --git a/tutorial/04-embedded-no-std-hello/task/Cargo.toml b/tutorial/04-embedded-no-std-hello/task/Cargo.toml new file mode 100644 index 0000000..e3bf052 --- /dev/null +++ b/tutorial/04-embedded-no-std-hello/task/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "step04_embedded_no_std_hello_task" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = "0.7.7" +cortex-m-rt = "0.7.5" +defmt = "0.3.10" +defmt-rtt = "0.4.2" +panic-probe = { version = "0.3.2", features = ["print-defmt"] } +nb = "1.1.0" + +[dependencies.stm32f1xx-hal] +version = "0.11.0" +features = ["rt", "stm32f103", "medium"] + +[profile.release] +codegen-units = 1 +debug = 2 +lto = true +opt-level = "z" diff --git a/tutorial/04-embedded-no-std-hello/task/memory.x b/tutorial/04-embedded-no-std-hello/task/memory.x new file mode 100644 index 0000000..19825e7 --- /dev/null +++ b/tutorial/04-embedded-no-std-hello/task/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 64K + RAM : ORIGIN = 0x20000000, LENGTH = 20K +} diff --git a/tutorial/04-embedded-no-std-hello/task/rust-toolchain.toml b/tutorial/04-embedded-no-std-hello/task/rust-toolchain.toml new file mode 100644 index 0000000..e2ff99e --- /dev/null +++ b/tutorial/04-embedded-no-std-hello/task/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt"] +targets = ["thumbv7m-none-eabi"] diff --git a/tutorial/04-embedded-no-std-hello/task/src/main.rs b/tutorial/04-embedded-no-std-hello/task/src/main.rs new file mode 100644 index 0000000..1ff802d --- /dev/null +++ b/tutorial/04-embedded-no-std-hello/task/src/main.rs @@ -0,0 +1,18 @@ +#![no_std] +#![no_main] + +use cortex_m::asm; +use cortex_m_rt::entry; +use defmt::info; +use {defmt_rtt as _, panic_probe as _}; + +#[entry] +fn main() -> ! { + // TODO: personalize these log lines. + info!("step04: no_std hello on stm32f103c8"); + + loop { + asm::delay(8_000_000); + info!("step04: alive"); + } +} diff --git a/tutorial/05-led-blink/README.md b/tutorial/05-led-blink/README.md new file mode 100644 index 0000000..20bccba --- /dev/null +++ b/tutorial/05-led-blink/README.md @@ -0,0 +1,20 @@ +# 05 - LED Blink (8 min) + +## Goal + +Onboard-LED (PC13, active-low) per Timer toggeln. + +## Run + +- `bash scripts/run-step.sh 05 task` + +## Tasks + +1. Prüfe in `task/src/main.rs`, welche Pegel "LED an/aus" bedeuten. +2. Passe Blinkfrequenz an. +3. Verifiziere LED und Logs. + +## Done when + +1. LED blinkt sichtbar. +2. Logs zeigen den Schaltzustand. diff --git a/tutorial/05-led-blink/solution/.cargo/config.toml b/tutorial/05-led-blink/solution/.cargo/config.toml new file mode 100644 index 0000000..d588bc1 --- /dev/null +++ b/tutorial/05-led-blink/solution/.cargo/config.toml @@ -0,0 +1,12 @@ +[target.thumbv7m-none-eabi] +runner = "probe-rs run --chip STM32F103C8" +rustflags = [ + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", +] + +[build] +target = "thumbv7m-none-eabi" + +[env] +DEFMT_LOG = "info" diff --git a/tutorial/05-led-blink/solution/Cargo.toml b/tutorial/05-led-blink/solution/Cargo.toml new file mode 100644 index 0000000..6c103c8 --- /dev/null +++ b/tutorial/05-led-blink/solution/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "step05_led_blink_solution" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = "0.7.7" +cortex-m-rt = "0.7.5" +defmt = "0.3.10" +defmt-rtt = "0.4.2" +panic-probe = { version = "0.3.2", features = ["print-defmt"] } +nb = "1.1.0" + +[dependencies.stm32f1xx-hal] +version = "0.11.0" +features = ["rt", "stm32f103", "medium"] + +[profile.release] +codegen-units = 1 +debug = 2 +lto = true +opt-level = "z" diff --git a/tutorial/05-led-blink/solution/memory.x b/tutorial/05-led-blink/solution/memory.x new file mode 100644 index 0000000..19825e7 --- /dev/null +++ b/tutorial/05-led-blink/solution/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 64K + RAM : ORIGIN = 0x20000000, LENGTH = 20K +} diff --git a/tutorial/05-led-blink/solution/rust-toolchain.toml b/tutorial/05-led-blink/solution/rust-toolchain.toml new file mode 100644 index 0000000..e2ff99e --- /dev/null +++ b/tutorial/05-led-blink/solution/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt"] +targets = ["thumbv7m-none-eabi"] diff --git a/tutorial/05-led-blink/solution/src/main.rs b/tutorial/05-led-blink/solution/src/main.rs new file mode 100644 index 0000000..4f15bd2 --- /dev/null +++ b/tutorial/05-led-blink/solution/src/main.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::info; +use nb::block; +use stm32f1xx_hal::{pac, prelude::*, timer::Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[entry] +fn main() -> ! { + let cp = cortex_m::Peripherals::take().unwrap(); + let dp = pac::Peripherals::take().unwrap(); + + let mut rcc = dp.RCC.constrain(); + let mut gpioc = dp.GPIOC.split(&mut rcc); + let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); + + let mut timer = Timer::syst(cp.SYST, &rcc.clocks).counter_hz(); + timer.start(2.Hz()).unwrap(); + + loop { + info!("led on (pc13 low)"); + led.set_low(); + block!(timer.wait()).unwrap(); + + info!("led off (pc13 high)"); + led.set_high(); + block!(timer.wait()).unwrap(); + } +} diff --git a/tutorial/05-led-blink/task/.cargo/config.toml b/tutorial/05-led-blink/task/.cargo/config.toml new file mode 100644 index 0000000..d588bc1 --- /dev/null +++ b/tutorial/05-led-blink/task/.cargo/config.toml @@ -0,0 +1,12 @@ +[target.thumbv7m-none-eabi] +runner = "probe-rs run --chip STM32F103C8" +rustflags = [ + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", +] + +[build] +target = "thumbv7m-none-eabi" + +[env] +DEFMT_LOG = "info" diff --git a/tutorial/05-led-blink/task/Cargo.toml b/tutorial/05-led-blink/task/Cargo.toml new file mode 100644 index 0000000..41e2b48 --- /dev/null +++ b/tutorial/05-led-blink/task/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "step05_led_blink_task" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = "0.7.7" +cortex-m-rt = "0.7.5" +defmt = "0.3.10" +defmt-rtt = "0.4.2" +panic-probe = { version = "0.3.2", features = ["print-defmt"] } +nb = "1.1.0" + +[dependencies.stm32f1xx-hal] +version = "0.11.0" +features = ["rt", "stm32f103", "medium"] + +[profile.release] +codegen-units = 1 +debug = 2 +lto = true +opt-level = "z" diff --git a/tutorial/05-led-blink/task/memory.x b/tutorial/05-led-blink/task/memory.x new file mode 100644 index 0000000..19825e7 --- /dev/null +++ b/tutorial/05-led-blink/task/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 64K + RAM : ORIGIN = 0x20000000, LENGTH = 20K +} diff --git a/tutorial/05-led-blink/task/rust-toolchain.toml b/tutorial/05-led-blink/task/rust-toolchain.toml new file mode 100644 index 0000000..e2ff99e --- /dev/null +++ b/tutorial/05-led-blink/task/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt"] +targets = ["thumbv7m-none-eabi"] diff --git a/tutorial/05-led-blink/task/src/main.rs b/tutorial/05-led-blink/task/src/main.rs new file mode 100644 index 0000000..154e66c --- /dev/null +++ b/tutorial/05-led-blink/task/src/main.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::info; +use nb::block; +use stm32f1xx_hal::{pac, prelude::*, timer::Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[entry] +fn main() -> ! { + let cp = cortex_m::Peripherals::take().unwrap(); + let dp = pac::Peripherals::take().unwrap(); + + let mut rcc = dp.RCC.constrain(); + let mut gpioc = dp.GPIOC.split(&mut rcc); + + // Bluepill onboard LED on PC13 is active-low. + let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); + + let mut timer = Timer::syst(cp.SYST, &rcc.clocks).counter_hz(); + timer.start(2.Hz()).unwrap(); + + loop { + // TODO: swap set_high/set_low and observe behavior. + info!("led on (pc13 low)"); + led.set_low(); + block!(timer.wait()).unwrap(); + + info!("led off (pc13 high)"); + led.set_high(); + block!(timer.wait()).unwrap(); + } +} diff --git a/tutorial/06-button-input/README.md b/tutorial/06-button-input/README.md new file mode 100644 index 0000000..dd954b3 --- /dev/null +++ b/tutorial/06-button-input/README.md @@ -0,0 +1,20 @@ +# 06 - Button Input (6 min) + +## Goal + +Button an PA0 (Pull-up) lesen und Press/Release als Log ausgeben. + +## Run + +- `bash scripts/run-step.sh 06 task` + +## Tasks + +1. Verdrahtung prüfen: PA0 -> Button -> GND. +2. Starte `task` und beobachte Logs. +3. Passe Polling-Intervall an. + +## Done when + +1. Pressed/Released wird korrekt erkannt. +2. Keine Flut von Wiederhol-Logs bei gehaltenem Taster. diff --git a/tutorial/06-button-input/solution/.cargo/config.toml b/tutorial/06-button-input/solution/.cargo/config.toml new file mode 100644 index 0000000..d588bc1 --- /dev/null +++ b/tutorial/06-button-input/solution/.cargo/config.toml @@ -0,0 +1,12 @@ +[target.thumbv7m-none-eabi] +runner = "probe-rs run --chip STM32F103C8" +rustflags = [ + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", +] + +[build] +target = "thumbv7m-none-eabi" + +[env] +DEFMT_LOG = "info" diff --git a/tutorial/06-button-input/solution/Cargo.toml b/tutorial/06-button-input/solution/Cargo.toml new file mode 100644 index 0000000..455c0dd --- /dev/null +++ b/tutorial/06-button-input/solution/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "step06_button_input_solution" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = "0.7.7" +cortex-m-rt = "0.7.5" +defmt = "0.3.10" +defmt-rtt = "0.4.2" +panic-probe = { version = "0.3.2", features = ["print-defmt"] } +nb = "1.1.0" + +[dependencies.stm32f1xx-hal] +version = "0.11.0" +features = ["rt", "stm32f103", "medium"] + +[profile.release] +codegen-units = 1 +debug = 2 +lto = true +opt-level = "z" diff --git a/tutorial/06-button-input/solution/memory.x b/tutorial/06-button-input/solution/memory.x new file mode 100644 index 0000000..19825e7 --- /dev/null +++ b/tutorial/06-button-input/solution/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 64K + RAM : ORIGIN = 0x20000000, LENGTH = 20K +} diff --git a/tutorial/06-button-input/solution/rust-toolchain.toml b/tutorial/06-button-input/solution/rust-toolchain.toml new file mode 100644 index 0000000..e2ff99e --- /dev/null +++ b/tutorial/06-button-input/solution/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt"] +targets = ["thumbv7m-none-eabi"] diff --git a/tutorial/06-button-input/solution/src/main.rs b/tutorial/06-button-input/solution/src/main.rs new file mode 100644 index 0000000..35785b2 --- /dev/null +++ b/tutorial/06-button-input/solution/src/main.rs @@ -0,0 +1,37 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::info; +use nb::block; +use stm32f1xx_hal::{pac, prelude::*, timer::Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[entry] +fn main() -> ! { + let cp = cortex_m::Peripherals::take().unwrap(); + let dp = pac::Peripherals::take().unwrap(); + + let mut rcc = dp.RCC.constrain(); + let mut gpioa = dp.GPIOA.split(&mut rcc); + let button = gpioa.pa0.into_pull_up_input(&mut gpioa.crl); + + let mut timer = Timer::syst(cp.SYST, &rcc.clocks).counter_hz(); + timer.start(40.Hz()).unwrap(); + + let mut last_pressed = false; + + loop { + let pressed = button.is_low(); + if pressed != last_pressed { + if pressed { + info!("button: pressed"); + } else { + info!("button: released"); + } + last_pressed = pressed; + } + + block!(timer.wait()).unwrap(); + } +} diff --git a/tutorial/06-button-input/task/.cargo/config.toml b/tutorial/06-button-input/task/.cargo/config.toml new file mode 100644 index 0000000..d588bc1 --- /dev/null +++ b/tutorial/06-button-input/task/.cargo/config.toml @@ -0,0 +1,12 @@ +[target.thumbv7m-none-eabi] +runner = "probe-rs run --chip STM32F103C8" +rustflags = [ + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", +] + +[build] +target = "thumbv7m-none-eabi" + +[env] +DEFMT_LOG = "info" diff --git a/tutorial/06-button-input/task/Cargo.toml b/tutorial/06-button-input/task/Cargo.toml new file mode 100644 index 0000000..f6972d4 --- /dev/null +++ b/tutorial/06-button-input/task/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "step06_button_input_task" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = "0.7.7" +cortex-m-rt = "0.7.5" +defmt = "0.3.10" +defmt-rtt = "0.4.2" +panic-probe = { version = "0.3.2", features = ["print-defmt"] } +nb = "1.1.0" + +[dependencies.stm32f1xx-hal] +version = "0.11.0" +features = ["rt", "stm32f103", "medium"] + +[profile.release] +codegen-units = 1 +debug = 2 +lto = true +opt-level = "z" diff --git a/tutorial/06-button-input/task/memory.x b/tutorial/06-button-input/task/memory.x new file mode 100644 index 0000000..19825e7 --- /dev/null +++ b/tutorial/06-button-input/task/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 64K + RAM : ORIGIN = 0x20000000, LENGTH = 20K +} diff --git a/tutorial/06-button-input/task/rust-toolchain.toml b/tutorial/06-button-input/task/rust-toolchain.toml new file mode 100644 index 0000000..e2ff99e --- /dev/null +++ b/tutorial/06-button-input/task/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt"] +targets = ["thumbv7m-none-eabi"] diff --git a/tutorial/06-button-input/task/src/main.rs b/tutorial/06-button-input/task/src/main.rs new file mode 100644 index 0000000..3373f16 --- /dev/null +++ b/tutorial/06-button-input/task/src/main.rs @@ -0,0 +1,40 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::info; +use nb::block; +use stm32f1xx_hal::{pac, prelude::*, timer::Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[entry] +fn main() -> ! { + let cp = cortex_m::Peripherals::take().unwrap(); + let dp = pac::Peripherals::take().unwrap(); + + let mut rcc = dp.RCC.constrain(); + let mut gpioa = dp.GPIOA.split(&mut rcc); + + // Button wiring: PA0 -> button -> GND + let button = gpioa.pa0.into_pull_up_input(&mut gpioa.crl); + + let mut timer = Timer::syst(cp.SYST, &rcc.clocks).counter_hz(); + timer.start(40.Hz()).unwrap(); + + let mut last_pressed = false; + + loop { + let pressed = button.is_low(); + if pressed != last_pressed { + if pressed { + info!("button: pressed"); + } else { + info!("button: released"); + } + last_pressed = pressed; + } + + // Poll at 25ms to avoid too much log spam. + block!(timer.wait()).unwrap(); + } +} diff --git a/tutorial/07-analog-readout/README.md b/tutorial/07-analog-readout/README.md new file mode 100644 index 0000000..9bdad25 --- /dev/null +++ b/tutorial/07-analog-readout/README.md @@ -0,0 +1,20 @@ +# 07 - Analog Readout (6 min) + +## Goal + +ADC auf PA1 lesen und Werte über RTT streamen. + +## Run + +- `bash scripts/run-step.sh 07 task` + +## Tasks + +1. Verdrahtung prüfen: Analogsignal an PA1. +2. Werte ausgeben und in drei Bereiche klassifizieren. +3. Potentiometer/Sensor bewegen und Änderung beobachten. + +## Done when + +1. Rohwert ändert sich mit Eingangsspannung. +2. Klassifizierung (`low/medium/high`) reagiert sinnvoll. diff --git a/tutorial/07-analog-readout/solution/.cargo/config.toml b/tutorial/07-analog-readout/solution/.cargo/config.toml new file mode 100644 index 0000000..d588bc1 --- /dev/null +++ b/tutorial/07-analog-readout/solution/.cargo/config.toml @@ -0,0 +1,12 @@ +[target.thumbv7m-none-eabi] +runner = "probe-rs run --chip STM32F103C8" +rustflags = [ + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", +] + +[build] +target = "thumbv7m-none-eabi" + +[env] +DEFMT_LOG = "info" diff --git a/tutorial/07-analog-readout/solution/Cargo.toml b/tutorial/07-analog-readout/solution/Cargo.toml new file mode 100644 index 0000000..74bff37 --- /dev/null +++ b/tutorial/07-analog-readout/solution/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "step07_analog_readout_solution" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = "0.7.7" +cortex-m-rt = "0.7.5" +defmt = "0.3.10" +defmt-rtt = "0.4.2" +panic-probe = { version = "0.3.2", features = ["print-defmt"] } +nb = "1.1.0" + +[dependencies.stm32f1xx-hal] +version = "0.11.0" +features = ["rt", "stm32f103", "medium"] + +[profile.release] +codegen-units = 1 +debug = 2 +lto = true +opt-level = "z" diff --git a/tutorial/07-analog-readout/solution/memory.x b/tutorial/07-analog-readout/solution/memory.x new file mode 100644 index 0000000..19825e7 --- /dev/null +++ b/tutorial/07-analog-readout/solution/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 64K + RAM : ORIGIN = 0x20000000, LENGTH = 20K +} diff --git a/tutorial/07-analog-readout/solution/rust-toolchain.toml b/tutorial/07-analog-readout/solution/rust-toolchain.toml new file mode 100644 index 0000000..e2ff99e --- /dev/null +++ b/tutorial/07-analog-readout/solution/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt"] +targets = ["thumbv7m-none-eabi"] diff --git a/tutorial/07-analog-readout/solution/src/main.rs b/tutorial/07-analog-readout/solution/src/main.rs new file mode 100644 index 0000000..663d024 --- /dev/null +++ b/tutorial/07-analog-readout/solution/src/main.rs @@ -0,0 +1,37 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::info; +use nb::block; +use stm32f1xx_hal::{adc::Adc, pac, prelude::*, timer::Timer}; +use {defmt_rtt as _, panic_probe as _}; + +fn classify(raw: u16) -> &'static str { + match raw { + 0..=1365 => "low", + 1366..=2730 => "medium", + _ => "high", + } +} + +#[entry] +fn main() -> ! { + let cp = cortex_m::Peripherals::take().unwrap(); + let dp = pac::Peripherals::take().unwrap(); + + let mut rcc = dp.RCC.constrain(); + let mut gpioa = dp.GPIOA.split(&mut rcc); + + let mut adc = Adc::new(dp.ADC1, &mut rcc); + let mut analog_pin = gpioa.pa1.into_analog(&mut gpioa.crl); + + let mut timer = Timer::syst(cp.SYST, &rcc.clocks).counter_hz(); + timer.start(10.Hz()).unwrap(); + + loop { + let raw: u16 = adc.read(&mut analog_pin).unwrap(); + info!("adc raw={} level={}", raw, classify(raw)); + block!(timer.wait()).unwrap(); + } +} diff --git a/tutorial/07-analog-readout/task/.cargo/config.toml b/tutorial/07-analog-readout/task/.cargo/config.toml new file mode 100644 index 0000000..d588bc1 --- /dev/null +++ b/tutorial/07-analog-readout/task/.cargo/config.toml @@ -0,0 +1,12 @@ +[target.thumbv7m-none-eabi] +runner = "probe-rs run --chip STM32F103C8" +rustflags = [ + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", +] + +[build] +target = "thumbv7m-none-eabi" + +[env] +DEFMT_LOG = "info" diff --git a/tutorial/07-analog-readout/task/Cargo.toml b/tutorial/07-analog-readout/task/Cargo.toml new file mode 100644 index 0000000..289292e --- /dev/null +++ b/tutorial/07-analog-readout/task/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "step07_analog_readout_task" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = "0.7.7" +cortex-m-rt = "0.7.5" +defmt = "0.3.10" +defmt-rtt = "0.4.2" +panic-probe = { version = "0.3.2", features = ["print-defmt"] } +nb = "1.1.0" + +[dependencies.stm32f1xx-hal] +version = "0.11.0" +features = ["rt", "stm32f103", "medium"] + +[profile.release] +codegen-units = 1 +debug = 2 +lto = true +opt-level = "z" diff --git a/tutorial/07-analog-readout/task/memory.x b/tutorial/07-analog-readout/task/memory.x new file mode 100644 index 0000000..19825e7 --- /dev/null +++ b/tutorial/07-analog-readout/task/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 64K + RAM : ORIGIN = 0x20000000, LENGTH = 20K +} diff --git a/tutorial/07-analog-readout/task/rust-toolchain.toml b/tutorial/07-analog-readout/task/rust-toolchain.toml new file mode 100644 index 0000000..e2ff99e --- /dev/null +++ b/tutorial/07-analog-readout/task/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt"] +targets = ["thumbv7m-none-eabi"] diff --git a/tutorial/07-analog-readout/task/src/main.rs b/tutorial/07-analog-readout/task/src/main.rs new file mode 100644 index 0000000..862425a --- /dev/null +++ b/tutorial/07-analog-readout/task/src/main.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::info; +use nb::block; +use stm32f1xx_hal::{adc::Adc, pac, prelude::*, timer::Timer}; +use {defmt_rtt as _, panic_probe as _}; + +fn classify(raw: u16) -> &'static str { + // TODO: tune thresholds for your sensor range. + match raw { + 0..=1365 => "low", + 1366..=2730 => "medium", + _ => "high", + } +} + +#[entry] +fn main() -> ! { + let cp = cortex_m::Peripherals::take().unwrap(); + let dp = pac::Peripherals::take().unwrap(); + + let mut rcc = dp.RCC.constrain(); + let mut gpioa = dp.GPIOA.split(&mut rcc); + + let mut adc = Adc::new(dp.ADC1, &mut rcc); + let mut analog_pin = gpioa.pa1.into_analog(&mut gpioa.crl); + + let mut timer = Timer::syst(cp.SYST, &rcc.clocks).counter_hz(); + timer.start(10.Hz()).unwrap(); + + loop { + // NOTE: read() returns a Result for one-shot conversions. + let raw: u16 = adc.read(&mut analog_pin).unwrap(); + info!("adc raw={} level={}", raw, classify(raw)); + block!(timer.wait()).unwrap(); + } +} diff --git a/tutorial/08-final-combined/README.md b/tutorial/08-final-combined/README.md new file mode 100644 index 0000000..b7305e2 --- /dev/null +++ b/tutorial/08-final-combined/README.md @@ -0,0 +1,22 @@ +# 08 - Final Combined (4 min) + +## Goal + +Blink + Button + ADC in einem Programm kombinieren. + +## Behavior + +1. ADC steuert Blink-Intervall. +2. Button toggelt Betriebsmodus: + - `Mode::Blinking` + - `Mode::ForcedOff` + +## Run + +- `bash scripts/run-step.sh 08 task` + +## Done when + +1. LED-Verhalten ändert sich per Button. +2. ADC beeinflusst Blinkgeschwindigkeit im Blink-Modus. +3. Logs zeigen Mode, ADC und Delay. diff --git a/tutorial/08-final-combined/solution/.cargo/config.toml b/tutorial/08-final-combined/solution/.cargo/config.toml new file mode 100644 index 0000000..d588bc1 --- /dev/null +++ b/tutorial/08-final-combined/solution/.cargo/config.toml @@ -0,0 +1,12 @@ +[target.thumbv7m-none-eabi] +runner = "probe-rs run --chip STM32F103C8" +rustflags = [ + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", +] + +[build] +target = "thumbv7m-none-eabi" + +[env] +DEFMT_LOG = "info" diff --git a/tutorial/08-final-combined/solution/Cargo.toml b/tutorial/08-final-combined/solution/Cargo.toml new file mode 100644 index 0000000..00ad29e --- /dev/null +++ b/tutorial/08-final-combined/solution/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "step08_final_combined_solution" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = "0.7.7" +cortex-m-rt = "0.7.5" +defmt = "0.3.10" +defmt-rtt = "0.4.2" +panic-probe = { version = "0.3.2", features = ["print-defmt"] } +nb = "1.1.0" + +[dependencies.stm32f1xx-hal] +version = "0.11.0" +features = ["rt", "stm32f103", "medium"] + +[profile.release] +codegen-units = 1 +debug = 2 +lto = true +opt-level = "z" diff --git a/tutorial/08-final-combined/solution/memory.x b/tutorial/08-final-combined/solution/memory.x new file mode 100644 index 0000000..19825e7 --- /dev/null +++ b/tutorial/08-final-combined/solution/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 64K + RAM : ORIGIN = 0x20000000, LENGTH = 20K +} diff --git a/tutorial/08-final-combined/solution/rust-toolchain.toml b/tutorial/08-final-combined/solution/rust-toolchain.toml new file mode 100644 index 0000000..e2ff99e --- /dev/null +++ b/tutorial/08-final-combined/solution/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt"] +targets = ["thumbv7m-none-eabi"] diff --git a/tutorial/08-final-combined/solution/src/main.rs b/tutorial/08-final-combined/solution/src/main.rs new file mode 100644 index 0000000..7c08a4e --- /dev/null +++ b/tutorial/08-final-combined/solution/src/main.rs @@ -0,0 +1,83 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::info; +use nb::block; +use stm32f1xx_hal::{adc::Adc, pac, prelude::*, timer::Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Mode { + Blinking, + ForcedOff, +} + +fn interval_from_adc(raw: u16) -> u32 { + 100 + (u32::from(raw) * 800 / 4095) +} + +#[entry] +fn main() -> ! { + let cp = cortex_m::Peripherals::take().unwrap(); + let dp = pac::Peripherals::take().unwrap(); + + let mut rcc = dp.RCC.constrain(); + let mut gpioa = dp.GPIOA.split(&mut rcc); + let mut gpioc = dp.GPIOC.split(&mut rcc); + + let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); + let button = gpioa.pa0.into_pull_up_input(&mut gpioa.crl); + + let mut adc = Adc::new(dp.ADC1, &mut rcc); + let mut analog_pin = gpioa.pa1.into_analog(&mut gpioa.crl); + + let mut tick = Timer::syst(cp.SYST, &rcc.clocks).counter_hz(); + tick.start(100.Hz()).unwrap(); + + let mut mode = Mode::Blinking; + let mut last_pressed = false; + let mut led_on = false; + let mut elapsed_ms: u32 = 0; + + loop { + let pressed = button.is_low(); + if pressed && !last_pressed { + mode = if mode == Mode::Blinking { + info!("mode -> ForcedOff"); + Mode::ForcedOff + } else { + info!("mode -> Blinking"); + Mode::Blinking + }; + } + last_pressed = pressed; + + let raw: u16 = adc.read(&mut analog_pin).unwrap(); + let interval_ms = interval_from_adc(raw); + + match mode { + Mode::Blinking => { + elapsed_ms += 10; + if elapsed_ms >= interval_ms { + elapsed_ms = 0; + led_on = !led_on; + if led_on { + led.set_low(); + } else { + led.set_high(); + } + info!("mode=Blinking adc={} interval_ms={} led_on={}", raw, interval_ms, led_on); + } + } + Mode::ForcedOff => { + led_on = false; + led.set_high(); + elapsed_ms = 0; + info!("mode=ForcedOff adc={} interval_ms={}", raw, interval_ms); + } + } + + block!(tick.wait()).unwrap(); + } +} diff --git a/tutorial/08-final-combined/task/.cargo/config.toml b/tutorial/08-final-combined/task/.cargo/config.toml new file mode 100644 index 0000000..d588bc1 --- /dev/null +++ b/tutorial/08-final-combined/task/.cargo/config.toml @@ -0,0 +1,12 @@ +[target.thumbv7m-none-eabi] +runner = "probe-rs run --chip STM32F103C8" +rustflags = [ + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", +] + +[build] +target = "thumbv7m-none-eabi" + +[env] +DEFMT_LOG = "info" diff --git a/tutorial/08-final-combined/task/Cargo.toml b/tutorial/08-final-combined/task/Cargo.toml new file mode 100644 index 0000000..87555a6 --- /dev/null +++ b/tutorial/08-final-combined/task/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "step08_final_combined_task" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = "0.7.7" +cortex-m-rt = "0.7.5" +defmt = "0.3.10" +defmt-rtt = "0.4.2" +panic-probe = { version = "0.3.2", features = ["print-defmt"] } +nb = "1.1.0" + +[dependencies.stm32f1xx-hal] +version = "0.11.0" +features = ["rt", "stm32f103", "medium"] + +[profile.release] +codegen-units = 1 +debug = 2 +lto = true +opt-level = "z" diff --git a/tutorial/08-final-combined/task/memory.x b/tutorial/08-final-combined/task/memory.x new file mode 100644 index 0000000..19825e7 --- /dev/null +++ b/tutorial/08-final-combined/task/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 64K + RAM : ORIGIN = 0x20000000, LENGTH = 20K +} diff --git a/tutorial/08-final-combined/task/rust-toolchain.toml b/tutorial/08-final-combined/task/rust-toolchain.toml new file mode 100644 index 0000000..e2ff99e --- /dev/null +++ b/tutorial/08-final-combined/task/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt"] +targets = ["thumbv7m-none-eabi"] diff --git a/tutorial/08-final-combined/task/src/main.rs b/tutorial/08-final-combined/task/src/main.rs new file mode 100644 index 0000000..7bbec09 --- /dev/null +++ b/tutorial/08-final-combined/task/src/main.rs @@ -0,0 +1,85 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::info; +use nb::block; +use stm32f1xx_hal::{adc::Adc, pac, prelude::*, timer::Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Mode { + Blinking, + ForcedOff, +} + +fn interval_from_adc(raw: u16) -> u32 { + // Map 0..4095 -> 100..900 ms + 100 + (u32::from(raw) * 800 / 4095) +} + +#[entry] +fn main() -> ! { + let cp = cortex_m::Peripherals::take().unwrap(); + let dp = pac::Peripherals::take().unwrap(); + + let mut rcc = dp.RCC.constrain(); + let mut gpioa = dp.GPIOA.split(&mut rcc); + let mut gpioc = dp.GPIOC.split(&mut rcc); + + let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); + let button = gpioa.pa0.into_pull_up_input(&mut gpioa.crl); + + let mut adc = Adc::new(dp.ADC1, &mut rcc); + let mut analog_pin = gpioa.pa1.into_analog(&mut gpioa.crl); + + // Fast base tick: all app timing derives from this. + let mut tick = Timer::syst(cp.SYST, &rcc.clocks).counter_hz(); + tick.start(100.Hz()).unwrap(); // 10 ms + + let mut mode = Mode::Blinking; + let mut last_pressed = false; + let mut led_on = false; + let mut elapsed_ms: u32 = 0; + + loop { + let pressed = button.is_low(); + if pressed && !last_pressed { + mode = if mode == Mode::Blinking { + info!("mode -> ForcedOff"); + Mode::ForcedOff + } else { + info!("mode -> Blinking"); + Mode::Blinking + }; + } + last_pressed = pressed; + + let raw: u16 = adc.read(&mut analog_pin).unwrap(); + let interval_ms = interval_from_adc(raw); + + match mode { + Mode::Blinking => { + elapsed_ms += 10; + if elapsed_ms >= interval_ms { + elapsed_ms = 0; + led_on = !led_on; + if led_on { + led.set_low(); // active-low + } else { + led.set_high(); + } + info!("mode=Blinking adc={} interval_ms={} led_on={}", raw, interval_ms, led_on); + } + } + Mode::ForcedOff => { + led_on = false; + led.set_high(); + elapsed_ms = 0; + info!("mode=ForcedOff adc={} interval_ms={}", raw, interval_ms); + } + } + + block!(tick.wait()).unwrap(); + } +} diff --git a/verify-probe.sh b/verify-probe.sh new file mode 100755 index 0000000..0cb4266 --- /dev/null +++ b/verify-probe.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[verify-probe] connected probes" +probe-rs list || { + echo "probe-rs list failed" + exit 1 +} + +echo "[verify-probe] known chip check" +if probe-rs chip list | grep -q 'STM32F103C8'; then + echo "chip id available: STM32F103C8" +else + echo "chip id STM32F103C8 not found in probe-rs chip list" + exit 1 +fi + +echo "[verify-probe] done" diff --git a/workshop.html b/workshop.html new file mode 100644 index 0000000..1b30262 --- /dev/null +++ b/workshop.html @@ -0,0 +1,236 @@ + + + + + + Rust on Bluepill Workshop + + + + + + + + +
+
+ +
+

Rust on Embedded

+

Didacta Workshop

+

STM32F103C8 Bluepill + ST-Link + Rust

+
+ +
+

Workshop Goal

+
    +
  • Understand core Rust basics
  • +
  • Build and flash no_std firmware
  • +
  • Blink LED, read button, read analog input
  • +
  • See output with probe-rs RTT logs
  • +
+
+ +
+

Flow (60 min)

+
    +
  • 00 setup, 01-03 Rust basics
  • +
  • 04 first embedded app
  • +
  • 05 LED blink
  • +
  • 06 button input
  • +
  • 07 analog readout
  • +
  • 08 final combined app
  • +
+
+ +
+

How We Work

+
    +
  • Edit only task/ folders
  • +
  • Use solution/ only when stuck
  • +
  • One step at a time, linear slides
  • +
+
+ +
+

Step 00 Task: Setup Live

+

Next task: Prepare host + probe tools.

+

Learn: Embedded Rust toolchain basics.

+
bash scripts/setup-live.sh
+bash scripts/verify-host.sh
+bash scripts/verify-probe.sh
+
+ +
+

Step 00 Solution

+
    +
  • rustup, cargo, probe-rs available
  • +
  • Target thumbv7m-none-eabi installed
  • +
  • probe-rs list sees ST-Link
  • +
  • probe-rs chip list contains STM32F103C8
  • +
+
+ +
+

Step 01 Task: Rust Hello

+

Next task: Edit the TODO values and run.

+

Learn: fn main, variables, println!.

+
bash scripts/run-step.sh 01 task
+
+ +
+

Step 01 Solution

+
    +
  • Program compiles and runs with cargo run
  • +
  • Console output shows your name and workshop title
  • +
  • You can navigate the task/solution structure
  • +
+
+ +
+

Step 02 Task: Types + Control Flow

+

Next task: Tune threshold logic in classify_adc.

+

Learn: explicit types, if, match.

+
bash scripts/run-step.sh 02 task
+
+ +
+

Step 02 Solution

+
    +
  • ADC values map to low/medium/high
  • +
  • Button states map to pressed/released
  • +
  • Program output changes correctly with input values
  • +
+
+ +
+

Step 03 Task: Ownership + Borrowing

+

Next task: Complete/update state mutation functions.

+

Learn: &T vs &mut T, safe mutation design.

+
bash scripts/run-step.sh 03 task
+
+ +
+

Step 03 Solution

+
    +
  • State is read via immutable reference
  • +
  • State changes happen through mutable references
  • +
  • Compiler enforces safe access patterns
  • +
+
+ +
+

Step 04 Task: Embedded no_std Hello

+

Next task: Flash first no_std firmware and watch RTT logs.

+

Learn: #![no_std], #![no_main], #[entry].

+
bash scripts/run-step.sh 04 task
+
+ +
+

Step 04 Solution

+
    +
  • Firmware flashes to Bluepill
  • +
  • RTT shows boot + alive messages
  • +
  • You now have a working embedded Rust baseline
  • +
+
+ +
+

Step 05 Task: LED Blink

+

Next task: Toggle PC13 in a timed loop.

+

Learn: HAL GPIO output + active-low LED behavior.

+
bash scripts/run-step.sh 05 task
+
+ +
+

Step 05 Solution

+
    +
  • PC13 low = LED on, PC13 high = LED off
  • +
  • Timer loop controls blink frequency
  • +
  • RTT logs match LED state changes
  • +
+
+ +
+

Step 06 Task: Button Input

+

Next task: Read PA0 with pull-up and detect transitions.

+

Learn: digital input polling + simple edge detection.

+
bash scripts/run-step.sh 06 task
+
+ +
+

Step 06 Solution

+
    +
  • Pressed/released events are logged correctly
  • +
  • No repeated spam while button is held
  • +
  • Wiring pattern: PA0 -> button -> GND
  • +
+
+ +
+

Step 07 Task: Analog Readout

+

Next task: Read ADC on PA1 and print values.

+

Learn: one-shot ADC + value classification.

+
bash scripts/run-step.sh 07 task
+
+ +
+

Step 07 Solution

+
    +
  • ADC raw value changes with analog input
  • +
  • Classification low/medium/high works
  • +
  • Periodic readout appears in RTT console
  • +
+
+ +
+

Step 08 Task: Final Combined

+

Next task: Integrate LED + button + ADC behavior.

+

Learn: small embedded state machine.

+
bash scripts/run-step.sh 08 task
+
+ +
+

Step 08 Solution

+
    +
  • Button toggles mode: Blinking / ForcedOff
  • +
  • ADC value controls blink interval in Blinking mode
  • +
  • Integrated firmware runs stable with logs
  • +
+
+ +
+

Workshop Wrap-up

+
    +
  • You moved from Rust basics to real MCU firmware
  • +
  • You used no_std, HAL GPIO, button input, and ADC
  • +
  • Next: extend with UART, interrupts, or Embassy async
  • +
+
+ +
+
+ + + + +