start tutorial

This commit is contained in:
2026-03-08 19:41:38 +01:00
commit a48ba2963d
81 changed files with 1738 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@@ -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

31
HARDWARE.md Normal file
View File

@@ -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.

33
README.md Normal file
View File

@@ -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)

41
TROUBLESHOOTING.md Normal file
View File

@@ -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.

71
WORKSHOP.md Normal file
View File

@@ -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/<name>`
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 <step-id> <task|solution>`
- `scripts/check-step.sh <step-id>`
## 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

39
source-map.md Normal file
View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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"

View File

@@ -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.

View File

@@ -0,0 +1,4 @@
[package]
name = "step01_rust_hello_solution"
version = "0.1.0"
edition = "2021"

View File

@@ -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.");
}

View File

@@ -0,0 +1,4 @@
[package]
name = "step01_rust_hello_task"
version = "0.1.0"
edition = "2021"

View File

@@ -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.");
}

View File

@@ -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.

View File

@@ -0,0 +1,4 @@
[package]
name = "step02_rust_types_control_solution"
version = "0.1.0"
edition = "2021"

View File

@@ -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));
}
}

View File

@@ -0,0 +1,4 @@
[package]
name = "step02_rust_types_control_task"
version = "0.1.0"
edition = "2021"

View File

@@ -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));
}
}

View File

@@ -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.

View File

@@ -0,0 +1,4 @@
[package]
name = "step03_rust_ownership_borrow_solution"
version = "0.1.0"
edition = "2021"

View File

@@ -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);
}

View File

@@ -0,0 +1,4 @@
[package]
name = "step03_rust_ownership_borrow_task"
version = "0.1.0"
edition = "2021"

View File

@@ -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);
}

View File

@@ -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.

View File

@@ -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"

View File

@@ -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"

View File

@@ -0,0 +1,5 @@
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["rustfmt"]
targets = ["thumbv7m-none-eabi"]

View File

@@ -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");
}
}

View File

@@ -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"

View File

@@ -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"

View File

@@ -0,0 +1,5 @@
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["rustfmt"]
targets = ["thumbv7m-none-eabi"]

View File

@@ -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");
}
}

View File

@@ -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.

View File

@@ -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"

View File

@@ -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"

View File

@@ -0,0 +1,5 @@
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["rustfmt"]
targets = ["thumbv7m-none-eabi"]

View File

@@ -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();
}
}

View File

@@ -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"

View File

@@ -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"

View File

@@ -0,0 +1,5 @@
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["rustfmt"]
targets = ["thumbv7m-none-eabi"]

View File

@@ -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();
}
}

View File

@@ -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.

View File

@@ -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"

View File

@@ -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"

View File

@@ -0,0 +1,5 @@
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["rustfmt"]
targets = ["thumbv7m-none-eabi"]

View File

@@ -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();
}
}

View File

@@ -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"

View File

@@ -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"

View File

@@ -0,0 +1,5 @@
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["rustfmt"]
targets = ["thumbv7m-none-eabi"]

View File

@@ -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();
}
}

View File

@@ -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.

View File

@@ -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"

View File

@@ -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"

View File

@@ -0,0 +1,5 @@
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["rustfmt"]
targets = ["thumbv7m-none-eabi"]

View File

@@ -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();
}
}

View File

@@ -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"

View File

@@ -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"

View File

@@ -0,0 +1,5 @@
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["rustfmt"]
targets = ["thumbv7m-none-eabi"]

View File

@@ -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();
}
}

View File

@@ -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.

View File

@@ -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"

View File

@@ -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"

View File

@@ -0,0 +1,5 @@
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["rustfmt"]
targets = ["thumbv7m-none-eabi"]

View File

@@ -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();
}
}

View File

@@ -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"

View File

@@ -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"

View File

@@ -0,0 +1,5 @@
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["rustfmt"]
targets = ["thumbv7m-none-eabi"]

View File

@@ -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();
}
}

18
verify-probe.sh Executable file
View File

@@ -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"

236
workshop.html Normal file
View File

@@ -0,0 +1,236 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rust on Bluepill Workshop</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@5/dist/reset.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@5/dist/reveal.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@5/dist/theme/white.css" />
<style>
.reveal h1, .reveal h2, .reveal h3 {
text-transform: none;
}
.reveal pre code {
max-height: 420px;
font-size: 0.75em;
}
.small {
font-size: 0.72em;
}
</style>
</head>
<body>
<div class="reveal">
<div class="slides">
<section>
<h1>Rust on Embedded</h1>
<h3>Didacta Workshop</h3>
<p>STM32F103C8 Bluepill + ST-Link + Rust</p>
</section>
<section>
<h2>Workshop Goal</h2>
<ul>
<li>Understand core Rust basics</li>
<li>Build and flash no_std firmware</li>
<li>Blink LED, read button, read analog input</li>
<li>See output with probe-rs RTT logs</li>
</ul>
</section>
<section>
<h2>Flow (60 min)</h2>
<ul>
<li>00 setup, 01-03 Rust basics</li>
<li>04 first embedded app</li>
<li>05 LED blink</li>
<li>06 button input</li>
<li>07 analog readout</li>
<li>08 final combined app</li>
</ul>
</section>
<section>
<h2>How We Work</h2>
<ul>
<li>Edit only <code>task/</code> folders</li>
<li>Use <code>solution/</code> only when stuck</li>
<li>One step at a time, linear slides</li>
</ul>
</section>
<section>
<h2>Step 00 Task: Setup Live</h2>
<p><strong>Next task:</strong> Prepare host + probe tools.</p>
<p><strong>Learn:</strong> Embedded Rust toolchain basics.</p>
<pre><code class="language-bash">bash scripts/setup-live.sh
bash scripts/verify-host.sh
bash scripts/verify-probe.sh</code></pre>
</section>
<section>
<h2>Step 00 Solution</h2>
<ul>
<li><code>rustup</code>, <code>cargo</code>, <code>probe-rs</code> available</li>
<li>Target <code>thumbv7m-none-eabi</code> installed</li>
<li><code>probe-rs list</code> sees ST-Link</li>
<li><code>probe-rs chip list</code> contains <code>STM32F103C8</code></li>
</ul>
</section>
<section>
<h2>Step 01 Task: Rust Hello</h2>
<p><strong>Next task:</strong> Edit the TODO values and run.</p>
<p><strong>Learn:</strong> <code>fn main</code>, variables, <code>println!</code>.</p>
<pre><code class="language-bash">bash scripts/run-step.sh 01 task</code></pre>
</section>
<section>
<h2>Step 01 Solution</h2>
<ul>
<li>Program compiles and runs with <code>cargo run</code></li>
<li>Console output shows your name and workshop title</li>
<li>You can navigate the task/solution structure</li>
</ul>
</section>
<section>
<h2>Step 02 Task: Types + Control Flow</h2>
<p><strong>Next task:</strong> Tune threshold logic in <code>classify_adc</code>.</p>
<p><strong>Learn:</strong> explicit types, <code>if</code>, <code>match</code>.</p>
<pre><code class="language-bash">bash scripts/run-step.sh 02 task</code></pre>
</section>
<section>
<h2>Step 02 Solution</h2>
<ul>
<li>ADC values map to low/medium/high</li>
<li>Button states map to pressed/released</li>
<li>Program output changes correctly with input values</li>
</ul>
</section>
<section>
<h2>Step 03 Task: Ownership + Borrowing</h2>
<p><strong>Next task:</strong> Complete/update state mutation functions.</p>
<p><strong>Learn:</strong> <code>&amp;T</code> vs <code>&amp;mut T</code>, safe mutation design.</p>
<pre><code class="language-bash">bash scripts/run-step.sh 03 task</code></pre>
</section>
<section>
<h2>Step 03 Solution</h2>
<ul>
<li>State is read via immutable reference</li>
<li>State changes happen through mutable references</li>
<li>Compiler enforces safe access patterns</li>
</ul>
</section>
<section>
<h2>Step 04 Task: Embedded no_std Hello</h2>
<p><strong>Next task:</strong> Flash first no_std firmware and watch RTT logs.</p>
<p><strong>Learn:</strong> <code>#![no_std]</code>, <code>#![no_main]</code>, <code>#[entry]</code>.</p>
<pre><code class="language-bash">bash scripts/run-step.sh 04 task</code></pre>
</section>
<section>
<h2>Step 04 Solution</h2>
<ul>
<li>Firmware flashes to Bluepill</li>
<li>RTT shows boot + alive messages</li>
<li>You now have a working embedded Rust baseline</li>
</ul>
</section>
<section>
<h2>Step 05 Task: LED Blink</h2>
<p><strong>Next task:</strong> Toggle PC13 in a timed loop.</p>
<p><strong>Learn:</strong> HAL GPIO output + active-low LED behavior.</p>
<pre><code class="language-bash">bash scripts/run-step.sh 05 task</code></pre>
</section>
<section>
<h2>Step 05 Solution</h2>
<ul>
<li>PC13 low = LED on, PC13 high = LED off</li>
<li>Timer loop controls blink frequency</li>
<li>RTT logs match LED state changes</li>
</ul>
</section>
<section>
<h2>Step 06 Task: Button Input</h2>
<p><strong>Next task:</strong> Read PA0 with pull-up and detect transitions.</p>
<p><strong>Learn:</strong> digital input polling + simple edge detection.</p>
<pre><code class="language-bash">bash scripts/run-step.sh 06 task</code></pre>
</section>
<section>
<h2>Step 06 Solution</h2>
<ul>
<li>Pressed/released events are logged correctly</li>
<li>No repeated spam while button is held</li>
<li>Wiring pattern: PA0 -> button -> GND</li>
</ul>
</section>
<section>
<h2>Step 07 Task: Analog Readout</h2>
<p><strong>Next task:</strong> Read ADC on PA1 and print values.</p>
<p><strong>Learn:</strong> one-shot ADC + value classification.</p>
<pre><code class="language-bash">bash scripts/run-step.sh 07 task</code></pre>
</section>
<section>
<h2>Step 07 Solution</h2>
<ul>
<li>ADC raw value changes with analog input</li>
<li>Classification low/medium/high works</li>
<li>Periodic readout appears in RTT console</li>
</ul>
</section>
<section>
<h2>Step 08 Task: Final Combined</h2>
<p><strong>Next task:</strong> Integrate LED + button + ADC behavior.</p>
<p><strong>Learn:</strong> small embedded state machine.</p>
<pre><code class="language-bash">bash scripts/run-step.sh 08 task</code></pre>
</section>
<section>
<h2>Step 08 Solution</h2>
<ul>
<li>Button toggles mode: Blinking / ForcedOff</li>
<li>ADC value controls blink interval in Blinking mode</li>
<li>Integrated firmware runs stable with logs</li>
</ul>
</section>
<section>
<h2>Workshop Wrap-up</h2>
<ul>
<li>You moved from Rust basics to real MCU firmware</li>
<li>You used no_std, HAL GPIO, button input, and ADC</li>
<li>Next: extend with UART, interrupts, or Embassy async</li>
</ul>
</section>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/reveal.js@5/dist/reveal.js"></script>
<script>
Reveal.initialize({
hash: true,
controls: true,
progress: true,
center: true,
transition: 'slide'
});
</script>
</body>
</html>