# Chapter Examples This file describes the workshop examples in implementation detail. The goal is not to create isolated Rust toy snippets. Each example is one step in a build-along journey toward the software core of a RoboCup Junior Soccer-style robot: ```text hardware input ↓ robot event ↓ robot state ↓ decision ↓ motion command ↓ actuator output ``` The examples must be small, independently runnable, and easy to modify during the workshop. Participants should be able to run one chapter, change one thing, break it, fix it, and then move on. The examples should be implemented as separate binaries under: ```text src/bin/ ``` The intended file list is: ```text 00_check.rs 01_rust_basics.rs 02_ownership.rs 03_types.rs 04_first_firmware.rs 05_rgb_output.rs 06_button_events.rs 07_events.rs 08_state_machine.rs 09_sensor_pipeline.rs 10_motor_commands.rs 11_kinematics.rs 12_embassy.rs ``` Host examples run with `cargo run` or `cargo test` on the participant laptop. Embedded examples build on the STM32 Blue Pill with the workshop aliases, for example `build-04` and `run-04`. Rustup-only users can use direct `cargo build --target thumbv7m-none-eabi --bin ...` commands and `probe-rs run`. ## General implementation principles Each example should follow these rules: 1. Keep the example focused on exactly one concept. 2. Include a working baseline. 3. Include one or more clearly marked `TODO` sections for participants. 4. Print or show enough feedback that participants know whether it works. 5. Keep robotics terminology consistent across chapters. 6. Reuse domain names where possible: `RobotEvent`, `RobotMode`, `MotionCommand`, `BallObservation`, `ObstacleObservation`. 7. Prefer explicit and readable code over clever abstractions. 8. Avoid advanced lifetime-heavy Rust unless it is absolutely necessary. 9. Keep all host-side examples testable without STM32 hardware. 10. Keep all embedded examples small enough to flash and debug quickly. The workshop should feel like building the same robot controller step by step, not like jumping between unrelated exercises. --- # 00_check.rs — Toolchain and sanity check ## Purpose This example verifies that the participant environment is usable before the real workshop content starts. It answers: - Can Rust compile a host-side binary? - Can the repository be opened and built? - Can the participant run a simple command? - Optionally: can the STM32 target be built? This example should not teach a new Rust concept. It is a confidence and troubleshooting checkpoint. ## Conceptual role in the robot journey Before a robot can do anything useful, the development loop must work. ```text edit code -> build -> run -> observe result ``` For embedded work this later becomes: ```text edit code -> build -> flash -> observe hardware ``` ## Code behaviour The host-side program should print a short confirmation message: ```text Rust on Robots toolchain check Host program runs correctly Next step: build the embedded firmware ``` If useful, also print the selected workshop target/chip as a constant string. ## Participant tasks Participants should: 1. Run the example. 2. Confirm that the output appears. 3. Change one printed line. 4. Run it again. ## Suggested command ```text cargo run --bin 00_check ``` ## Expected result The program compiles and prints the configured sanity-check message. ## Implementation notes for the code bot - Keep this example host-side and `std`-based. - No STM32 code here. - No complicated dependencies. - Use simple `println!` output. - This file should be nearly impossible to fail except for toolchain setup problems. ## Acceptance criteria - `cargo run --bin 00_check` works. - The output is obvious and friendly. - Participants can change one string and rerun it. --- # 01_rust_basics.rs — Values, functions and visible mutation ## Purpose This example introduces only the Rust basics needed for the rest of the workshop: - values - immutable-by-default variables - `mut` - functions - simple structs or enums only if helpful It should not become a full Rust introduction. ## Conceptual role in the robot journey Robot software should not mutate everything from everywhere. This example introduces the idea that values are stable by default and mutation must be intentional. ```text sensor reading -> event -> state transition -> command ``` That model only works if state changes are controlled. ## Code behaviour The program should model a tiny status calculation, not generic arithmetic. Example behaviour: - Start with a fixed `current_speed`. - Compute a `target_speed` with a function. - Print both values. - Show one intentionally mutable variable, such as `status_message` or `target_speed`. Possible output: ```text current speed: 0 selected target speed: 40 status: searching ``` ## Suggested code shape The code should include: ```rust fn select_target_speed(searching: bool) -> i32 { if searching { 40 } else { 0 } } ``` Then in `main`: ```rust let current_speed = 0; let mut target_speed = select_target_speed(true); target_speed = target_speed + 5; ``` The participant should see where mutation happens. ## Participant tasks Participants should: 1. Run the example. 2. Change the search speed. 3. Add a second function, for example `select_status_name`. 4. Change one immutable variable and observe the compiler error. 5. Decide whether the variable should actually be mutable. ## Suggested command ```text cargo run --bin 01_rust_basics ``` ## Expected result The participant sees how Rust separates stable values from mutable values. ## Implementation notes for the code bot - Keep the code host-side. - Use `println!` for feedback. - Include one commented-out line that would fail because it tries to mutate an immutable value. - Avoid ownership examples here; that belongs to the next chapter. ## Acceptance criteria - The example compiles in the baseline state. - Participants can modify a constant or function return value. - There is one clear optional compiler-error experiment about mutation. --- # 02_ownership.rs — Ownership and borrowing as resource responsibility ## Purpose This example introduces ownership and borrowing through robot commands. It should teach: - moving a value - borrowing a value with `&T` - mutably borrowing a value with `&mut T` - why this matters for hardware resources and robot state ## Conceptual role in the robot journey A robot controller should not have five different parts silently changing the same command or peripheral. Ownership answers: ```text Who is responsible for this value or resource? ``` Borrowing answers: ```text Who may temporarily inspect or modify it? ``` ## Code behaviour The program should define a simple robot command type: ```rust #[derive(Debug)] struct MotionCommand { forward: f32, strafe: f32, rotate: f32, } ``` The program should then: 1. Create a command. 2. Print it by borrowing it. 3. Modify it through a mutable borrow. 4. Print it again. ## Suggested code shape Include functions like: ```rust fn print_command(command: &MotionCommand) { println!("command = {command:?}"); } fn stop(command: &mut MotionCommand) { command.forward = 0.0; command.strafe = 0.0; command.rotate = 0.0; } ``` Also include a commented-out move example: ```rust // let next_command = command; // print_command(&command); // This would no longer compile after the move. ``` ## Participant tasks Participants should: 1. Run the baseline. 2. Uncomment the move example and read the compiler error. 3. Revert it. 4. Add a function `rotate_left(command: &mut MotionCommand)`. 5. Call it before `stop`. 6. Print the command after each step. ## Suggested command ```text cargo run --bin 02_ownership ``` ## Expected result Participants should understand ownership as responsibility, not as abstract syntax. ## Implementation notes for the code bot - Keep this host-side. - Use `#[derive(Debug, Clone, Copy)]` only if needed, but avoid hiding ownership too early. - For the first version, do not make `MotionCommand` `Copy`; the move error is useful. - If the example gets too frustrating, a later comment may explain when `Copy` would be acceptable. ## Acceptance criteria - Baseline compiles and prints command changes. - There is a safe mutable-borrow update function. - There is one optional ownership error experiment. --- # 03_types.rs — Domain types, enums and Result ## Purpose This example shows how Rust types can model robot concepts better than raw numbers and booleans. It should teach: - newtype structs for units - enums for meaningful alternatives - `Result` for sensor failure - `match` for explicit handling ## Conceptual role in the robot journey Robots often fail because raw values are treated as meaning. Bad: ```text 42 ``` Better: ```text DistanceCm(42) BallObservation::Left SensorError::Timeout ``` ## Code behaviour The program should simulate reading a fake ultrasonic sensor and a fake camera result. It should: 1. Convert a raw echo value into `DistanceCm`. 2. Classify distance as clear, warning or too close. 3. Represent ball position with `BallObservation`. 4. Print the resulting interpretation. ## Suggested types ```rust #[derive(Debug, Clone, Copy, PartialEq)] struct RawEchoMicros(u32); #[derive(Debug, Clone, Copy, PartialEq)] struct DistanceCm(u16); #[derive(Debug, Clone, Copy, PartialEq)] enum SensorError { Timeout, OutOfRange, NotCalibrated, } #[derive(Debug, Clone, Copy, PartialEq)] enum ObstacleObservation { Clear, Warning, TooClose, } #[derive(Debug, Clone, Copy, PartialEq)] enum BallObservation { NotSeen, Left, Center, Right, TooClose, } ``` ## Suggested functions ```rust fn echo_to_distance(raw: RawEchoMicros) -> Result { if raw.0 == 0 { return Err(SensorError::Timeout); } let cm = raw.0 / 58; if cm > 400 { Err(SensorError::OutOfRange) } else { Ok(DistanceCm(cm as u16)) } } fn classify_obstacle(distance: DistanceCm) -> ObstacleObservation { match distance.0 { 0..=15 => ObstacleObservation::TooClose, 16..=35 => ObstacleObservation::Warning, _ => ObstacleObservation::Clear, } } ``` ## Participant tasks Participants should: 1. Run the example. 2. Change the fake echo value. 3. Observe how the classification changes. 4. Add a new `BallObservation` variant, for example `FarAway`. 5. Add or update one `match` arm. 6. Trigger a fake sensor error and handle it. ## Suggested command ```text cargo run --bin 03_types ``` ## Expected result Participants should see that type design is robot design. ## Implementation notes for the code bot - Keep this host-side. - Add at least two sample raw echo values. - Print both successful and failing cases. - Use `match`, not `unwrap`, so error handling is visible. ## Acceptance criteria - Baseline compiles and prints at least one valid distance classification. - Baseline demonstrates one `Err` path. - Participants can change thresholds and variants easily. --- # 04_first_firmware.rs — First STM32 firmware ## Purpose This is the first embedded example. It introduces the structure of a minimal STM32 firmware program. It should teach: - embedded firmware is not a desktop application - `no_std` means no normal standard library - the program initializes hardware and then stays in a loop - flashing is part of the development loop ## Conceptual role in the robot journey Before a robot can read sensors or drive motors, the firmware must boot reliably. This chapter establishes the embedded loop: ```text init hardware -> loop forever -> observe behaviour ``` ## Code behaviour The program should: 1. Start on the STM32. 2. Initialize the minimal required runtime/HAL setup. 3. Enter an endless loop and keep the setup minimal. There does not need to be visible LED output yet. That comes in the next chapter. ## Participant tasks Participants should: 1. Build the firmware. 2. Flash it to the STM32. 3. Confirm that flashing succeeds. 4. Confirm that the firmware reaches the loop. ## Suggested command ```text build-04 ``` ## Suggested run ```text run-04 ``` ## Rustup fallback ```text cargo build --target thumbv7m-none-eabi --bin 04_first_firmware probe-rs run --chip STM32F103C8 target/thumbv7m-none-eabi/debug/04_first_firmware ``` ## Expected result The binary builds for the embedded target and can be flashed to the board. ## Implementation notes for the code bot - This file is embedded-target code. - Use the HAL/runtime style already selected for the repo. - Keep initialization minimal. - Do not introduce LED or button logic here. - Keep the loop empty or insert a `cortex_m::asm::nop()`. ## Acceptance criteria - The firmware builds for STM32. - The firmware flashes successfully with the selected command. - The code clearly shows the initialization and endless loop structure. --- # 05_rgb_output.rs — RGB LED as actuator and status output ## Purpose This example introduces output control. The RGB LED is treated as a real actuator and status display, not as a toy. It should teach: - configure GPIO as output - set pins high/low - use timing/delay - represent robot state visibly ## Conceptual role in the robot journey A robot needs visible status. Without a screen or logs, a status LED can show: ```text Idle Searching Driving Obstacle Error ``` This is useful during real floor debugging. ## Code behaviour The firmware should blink or switch the RGB LED according to a status pattern. Recommended mapping: ```text Off -> Idle Blue blink -> SearchBall Green -> DriveToBall Yellow -> AlignToBall Red blink -> AvoidObstacle or Error ``` The baseline should at least implement two states: ```text Idle SearchBall ``` ## Suggested types ```rust #[derive(Debug, Clone, Copy, PartialEq)] enum RobotMode { Idle, SearchBall, } ``` A helper function should map mode to LED output: ```rust fn show_status(mode: RobotMode /* plus LED pins */) { // Set RGB pins according to mode. } ``` The exact signature depends on the HAL pin types. ## Participant tasks Participants should: 1. Flash the baseline. 2. Confirm the LED behaviour. 3. Change the blink timing. 4. Add a second or third status pattern. 5. Change the mode in code and observe the LED. ## Suggested command ```text run-05 ``` ## Rustup fallback ```text cargo build --target thumbv7m-none-eabi --bin 05_rgb_output probe-rs run --chip STM32F103C8 target/thumbv7m-none-eabi/debug/05_rgb_output ``` ## Expected result Participants can see that firmware controls physical output. ## Implementation notes for the code bot - This file is embedded-target code. - The actual RGB LED pin mapping must match the workshop hardware. - Define constants or comments for pin mapping. - Keep active-high vs active-low behaviour explicit. - Avoid hiding all pin operations behind too much abstraction; participants should still see what is happening. ## Acceptance criteria - The firmware flashes. - The LED visibly changes state. - Participants can change timing or status mapping in one obvious place. --- # 06_button_events.rs — Button input as fake sensor rig ## Purpose This example introduces input. The 5-way button is used as a proxy for real robot perception: ```text Center -> start / stop Left -> ball left Right -> ball right Up -> ball centered Down -> obstacle detected ``` It should teach: - configure GPIO as input - read button state - map raw hardware state to a domain event - separate hardware input from robot meaning ## Conceptual role in the robot journey In the real robot, `BallLeft` may come from a camera pipeline. In the workshop, it comes from a button. The controller should not care. ```text button direction -> RobotEvent camera result -> RobotEvent simulator input -> RobotEvent ``` Same interface, different source. ## Code behaviour The firmware should: 1. Read the 5-way button. 2. Convert the pressed direction to `RobotEvent`. 3. Show feedback through LED or logs. 4. Keep a neutral `NoEvent` state when nothing is pressed. ## Suggested types ```rust #[derive(Debug, Clone, Copy, PartialEq)] enum ButtonDirection { None, Center, Up, Down, Left, Right, } #[derive(Debug, Clone, Copy, PartialEq)] enum RobotEvent { NoEvent, StartStop, BallLeft, BallRight, BallCentered, ObstacleDetected, } ``` ## Suggested function ```rust fn direction_to_event(direction: ButtonDirection) -> RobotEvent { match direction { ButtonDirection::None => RobotEvent::NoEvent, ButtonDirection::Center => RobotEvent::StartStop, ButtonDirection::Left => RobotEvent::BallLeft, ButtonDirection::Right => RobotEvent::BallRight, ButtonDirection::Up => RobotEvent::BallCentered, ButtonDirection::Down => RobotEvent::ObstacleDetected, } } ``` ## Participant tasks Participants should: 1. Flash the baseline. 2. Press each button direction. 3. Observe LED/log feedback. 4. Change the mapping, for example swap left and right. 5. Add a new event such as `BallLost` if the hardware interaction allows it. ## Suggested command ```text run-06 ``` ## Rustup fallback ```text cargo build --target thumbv7m-none-eabi --bin 06_button_events probe-rs run --chip STM32F103C8 target/thumbv7m-none-eabi/debug/06_button_events ``` ## Expected result Participants understand that raw input must be interpreted before robot logic uses it. ## Implementation notes for the code bot - This file is embedded-target code. - The button pin mapping must match the workshop hardware. - If the button hardware is analog or multiplexed, keep the reading logic isolated in one function. - If debounce is implemented, keep it simple and visible. - If debounce is not implemented, mention in a comment that mechanical inputs may bounce. ## Acceptance criteria - Each button direction maps to a visible or logged event. - `NoEvent` is handled explicitly. - The mapping function is easy to modify. --- # 07_events.rs — Event priority and safety-first interpretation ## Purpose This example moves from single input events to event selection and priority. It should teach: - not all events are equally important - safety-relevant events should win - event processing can be tested without hardware ## Conceptual role in the robot journey A soccer robot may see the ball and detect an obstacle at the same time. The robot should not blindly drive forward because the ball is centered. ```text BallCentered + ObstacleDetected -> Stop / AvoidObstacle ``` ## Code behaviour The host-side program or test should combine possible observations and choose one dominant `RobotEvent`. The baseline should include: - ball event - obstacle event - no event - priority logic ## Suggested types Use the existing `RobotEvent` and add if needed: ```rust #[derive(Debug, Clone, Copy, PartialEq)] enum RobotEvent { NoEvent, StartStop, BallLeft, BallRight, BallCentered, BallLost, ObstacleDetected, Timeout, } ``` ## Suggested function ```rust fn prioritize_event(events: &[RobotEvent]) -> RobotEvent { if events.contains(&RobotEvent::ObstacleDetected) { return RobotEvent::ObstacleDetected; } if events.contains(&RobotEvent::StartStop) { return RobotEvent::StartStop; } for event in events { if *event != RobotEvent::NoEvent { return *event; } } RobotEvent::NoEvent } ``` ## Participant tasks Participants should: 1. Run the tests. 2. Add a test where `BallCentered` and `ObstacleDetected` happen together. 3. Ensure `ObstacleDetected` wins. 4. Add `Timeout` as another high-priority event. 5. Decide whether `StartStop` should beat `ObstacleDetected` or not. ## Suggested command ```text cargo test --bin 07_events ``` ## Expected result Participants understand that event handling is a design decision, not just syntax. ## Implementation notes for the code bot - Keep this host-side. - Add several unit tests. - Do not require STM32 hardware. - Keep priorities simple and explicit. - Avoid overengineering with priority queues or traits. ## Acceptance criteria - `ObstacleDetected` wins over ball events. - `NoEvent` does not trigger behaviour. - The priority rules are covered by tests. --- # 08_state_machine.rs — Robot mode and behaviour ## Purpose This is the main robotics software example. It combines: - robot events - robot modes - motion commands - transition logic - LED status output on STM32 It should teach that robot behaviour should be explicit and inspectable. ## Conceptual role in the robot journey A soccer robot needs memory. It must know whether it is: - idle - searching for the ball - aligning to the ball - driving to the ball - avoiding an obstacle - in an error state This is a state machine. ## Code behaviour The firmware should: 1. Read button input. 2. Convert it to `RobotEvent`. 3. Update `RobotMode` through a `decide` or `transition` function. 4. Produce a `MotionCommand`. 5. Show the current mode with the RGB LED. The baseline does not need motors. The LED is the visible actuator. ## Suggested types ```rust #[derive(Debug, Clone, Copy, PartialEq)] enum RobotMode { Idle, SearchBall, AlignToBall, DriveToBall, AvoidObstacle, Error, } #[derive(Debug, Clone, Copy, PartialEq)] enum MotionCommand { Stop, Forward, RotateLeft, RotateRight, StrafeLeft, StrafeRight, Avoid, } ``` ## Suggested transition function ```rust fn decide( mode: RobotMode, event: RobotEvent, ) -> (RobotMode, MotionCommand) { match (mode, event) { (_, RobotEvent::ObstacleDetected) => { (RobotMode::AvoidObstacle, MotionCommand::Stop) } (RobotMode::Idle, RobotEvent::StartStop) => { (RobotMode::SearchBall, MotionCommand::RotateLeft) } (RobotMode::SearchBall, RobotEvent::BallLeft) => { (RobotMode::AlignToBall, MotionCommand::RotateLeft) } (RobotMode::SearchBall, RobotEvent::BallRight) => { (RobotMode::AlignToBall, MotionCommand::RotateRight) } (RobotMode::SearchBall, RobotEvent::BallCentered) => { (RobotMode::DriveToBall, MotionCommand::Forward) } (RobotMode::DriveToBall, RobotEvent::BallLost) => { (RobotMode::SearchBall, MotionCommand::RotateLeft) } (_, RobotEvent::StartStop) => { (RobotMode::Idle, MotionCommand::Stop) } (mode, _) => { (mode, MotionCommand::Stop) } } } ``` ## LED status mapping Recommended mapping: ```text Idle -> Off or slow blue SearchBall -> blue blink AlignToBall -> yellow DriveToBall -> green AvoidObstacle -> red blink Error -> fast red blink ``` ## Participant tasks Participants should: 1. Flash the baseline. 2. Press `Center` to leave `Idle`. 3. Use `Left`, `Right`, and `Up` to simulate ball observations. 4. Use `Down` to simulate an obstacle. 5. Add or fix one transition. 6. Add one unit test for transition logic if the code is split into testable pure functions. ## Suggested command ```text run-08 ``` ## Rustup fallback ```text cargo build --target thumbv7m-none-eabi --bin 08_state_machine probe-rs run --chip STM32F103C8 target/thumbv7m-none-eabi/debug/08_state_machine ``` Optional host-side tests: ```text cargo test --bin 08_state_machine ``` ## Expected result Participants see that button input can drive a simplified robot behaviour loop. ## Implementation notes for the code bot - This is embedded-target code, but the transition logic should be pure and testable. - Keep hardware reading and state transition separate. - If unit tests are awkward in the embedded binary, move shared logic into a library module later. For now, make the pure functions copyable or testable in a host-side companion if needed. - Do not introduce motors yet. ## Acceptance criteria - Button input changes robot mode. - Robot mode changes LED status. - Obstacle handling has priority. - The transition function is visible and easy to edit. --- # 09_sensor_pipeline.rs — Camera and ultrasonic sensor model ## Purpose This example models real robot perception without requiring actual camera or ultrasonic hardware in the workshop. It should teach: - camera frames are not decisions - ultrasonic echo values are not decisions - raw data must become observations - observations become robot events - multiple observations can be fused into one behaviour-relevant event ## Conceptual role in the robot journey The real RoboCup-style robot might have: ```text camera frame -> ball detection -> BallObservation ultrasonic echo -> distance -> ObstacleObservation ``` The controller should only consume domain observations or events, not raw pixels or echo timings. ## Code behaviour The host-side program should simulate: 1. A fake camera input. 2. A fake ultrasonic input. 3. Conversion into `BallObservation` and `ObstacleObservation`. 4. Conversion into one or more `RobotEvent` values. 5. Event priority. 6. A final call into `decide` or a simplified decision function. ## Suggested types ```rust #[derive(Debug, Clone, Copy, PartialEq)] enum BallObservation { NotSeen, Left, Center, Right, TooClose, } #[derive(Debug, Clone, Copy, PartialEq)] struct RawEchoMicros(u32); #[derive(Debug, Clone, Copy, PartialEq)] struct DistanceCm(u16); #[derive(Debug, Clone, Copy, PartialEq)] enum ObstacleObservation { Clear, Warning, TooClose, } ``` ## Suggested fake camera model Use a simple fake input type: ```rust #[derive(Debug, Clone, Copy, PartialEq)] struct FakeBallPixelX(Option); ``` Then classify it: ```rust fn classify_ball(pixel_x: FakeBallPixelX) -> BallObservation { match pixel_x.0 { None => BallObservation::NotSeen, Some(0..=200) => BallObservation::Left, Some(201..=440) => BallObservation::Center, Some(_) => BallObservation::Right, } } ``` The numbers do not need to be realistic; they only show the principle. ## Suggested ultrasonic model Reuse `echo_to_distance` and `classify_obstacle` from chapter 03 or reimplement them locally for clarity. ## Suggested fusion function ```rust fn observations_to_event( ball: BallObservation, obstacle: ObstacleObservation, ) -> RobotEvent { match obstacle { ObstacleObservation::TooClose => RobotEvent::ObstacleDetected, _ => match ball { BallObservation::NotSeen => RobotEvent::BallLost, BallObservation::Left => RobotEvent::BallLeft, BallObservation::Center => RobotEvent::BallCentered, BallObservation::Right => RobotEvent::BallRight, BallObservation::TooClose => RobotEvent::BallCentered, }, } } ``` ## Participant tasks Participants should: 1. Run the tests. 2. Change fake ball positions. 3. Change distance thresholds. 4. Add a warning behaviour for `ObstacleObservation::Warning`. 5. Add a test where the ball is centered but the obstacle is too close. 6. Ensure the resulting event is `ObstacleDetected`. ## Suggested command ```text cargo test --bin 09_sensor_pipeline ``` Optional run output: ```text cargo run --bin 09_sensor_pipeline ``` ## Expected result Participants understand how a real camera or ultrasonic sensor can later be swapped in without rewriting the robot behaviour layer. ## Implementation notes for the code bot - Keep this host-side. - Include unit tests. - Use deterministic fake inputs. - Avoid image processing libraries. - Avoid hardware dependencies. - This chapter is about the interface between perception and behaviour. ## Acceptance criteria - Fake camera values become `BallObservation`. - Fake ultrasonic values become `ObstacleObservation`. - Observation fusion produces `RobotEvent`. - Obstacle priority is tested. --- # 10_motor_commands.rs — Motion intent to wheel speeds ## Purpose This example models the transition from robot decision to motor-level command. It should teach: - robot behaviour should not directly produce PWM - symbolic commands can become motion vectors - motion vectors can be mixed into wheel speeds - real motor control needs normalization and calibration ## Conceptual role in the robot journey The state machine may decide: ```text RotateLeft Forward Stop ``` But a four-wheel omni-style robot needs four wheel speeds: ```text front_left front_right rear_left rear_right ``` This example builds that translation. ## Code behaviour The host-side program should: 1. Convert `MotionCommand` to `MotionVector`. 2. Convert `MotionVector` to `WheelSpeeds`. 3. Normalize wheel speeds to `-1.0..=1.0`. 4. Test basic command mappings. ## Suggested types ```rust #[derive(Debug, Clone, Copy, PartialEq)] enum MotionCommand { Stop, Forward, RotateLeft, RotateRight, StrafeLeft, StrafeRight, Avoid, } #[derive(Debug, Clone, Copy, PartialEq)] struct MotionVector { forward: f32, strafe: f32, rotate: f32, } #[derive(Debug, Clone, Copy, PartialEq)] struct WheelSpeeds { front_left: f32, front_right: f32, rear_left: f32, rear_right: f32, } ``` ## Suggested command mapping ```rust fn command_to_vector(command: MotionCommand) -> MotionVector { match command { MotionCommand::Stop => MotionVector { forward: 0.0, strafe: 0.0, rotate: 0.0 }, MotionCommand::Forward => MotionVector { forward: 0.6, strafe: 0.0, rotate: 0.0 }, MotionCommand::RotateLeft => MotionVector { forward: 0.0, strafe: 0.0, rotate: -0.4 }, MotionCommand::RotateRight => MotionVector { forward: 0.0, strafe: 0.0, rotate: 0.4 }, MotionCommand::StrafeLeft => MotionVector { forward: 0.0, strafe: -0.5, rotate: 0.0 }, MotionCommand::StrafeRight => MotionVector { forward: 0.0, strafe: 0.5, rotate: 0.0 }, MotionCommand::Avoid => MotionVector { forward: -0.3, strafe: 0.0, rotate: 0.3 }, } } ``` ## Suggested four-wheel mixer Use a simple omni/mecanum-style mixer: ```rust fn mix(cmd: MotionVector) -> WheelSpeeds { WheelSpeeds { front_left: cmd.forward + cmd.strafe + cmd.rotate, front_right: cmd.forward - cmd.strafe - cmd.rotate, rear_left: cmd.forward - cmd.strafe + cmd.rotate, rear_right: cmd.forward + cmd.strafe - cmd.rotate, } } ``` Important: document in comments that signs and wheel layout depend on the actual robot. ## Suggested normalization ```rust fn normalize(speeds: WheelSpeeds) -> WheelSpeeds { let max = speeds.front_left .abs() .max(speeds.front_right.abs()) .max(speeds.rear_left.abs()) .max(speeds.rear_right.abs()); if max <= 1.0 { speeds } else { WheelSpeeds { front_left: speeds.front_left / max, front_right: speeds.front_right / max, rear_left: speeds.rear_left / max, rear_right: speeds.rear_right / max, } } } ``` ## Participant tasks Participants should: 1. Run the tests. 2. Add tests for `Stop`, `Forward`, and `RotateLeft`. 3. Change the speed for `Forward`. 4. Add a new command such as `KickApproach` or `SlowAlign`. 5. Confirm that normalization keeps all wheel speeds within range. ## Suggested command ```text cargo test --bin 10_motor_commands ``` Optional run output: ```text cargo run --bin 10_motor_commands ``` ## Expected result Participants understand the separation between robot intent and actuator-level command. ## Implementation notes for the code bot - Keep this host-side. - Do not require motor hardware. - Include comments about calibration and real wheel orientation. - Keep `f32` acceptable for the workshop model. - Tests involving floats should use small tolerance helpers instead of direct equality where needed. ## Acceptance criteria - `MotionCommand` maps to `MotionVector`. - `MotionVector` maps to `WheelSpeeds`. - Speeds can be normalized. - At least three unit tests exist. --- # 11_kinematics.rs — Geometry, forward kinematics and inverse kinematics ## Purpose This final example gives a conceptual and code-level outlook into kinematics. It should not become a full robotics math lecture. It should teach: - geometry enters when movement depends on robot shape - forward kinematics asks where the robot/tool ends up - inverse kinematics asks which commands are needed to reach a target - invalid movement should fail before hardware moves ## Conceptual role in the robot journey For the soccer base: ```text motion intent -> wheel speeds ``` For a robot arm: ```text target position -> joint angles -> motor commands ``` Both are translations from desired behaviour into actuator commands. ## Code behaviour The host-side program should model a very small 2D or simplified 3-axis arm calculation. Keep it simple enough for workshop participants to read. Recommended approach: - Define target position. - Define joint angles. - Define possible kinematics errors. - Implement a simplified reachability check. - Optionally return placeholder angles for reachable targets. The purpose is not physically perfect kinematics. The purpose is modelling safe movement. ## Suggested types ```rust #[derive(Debug, Clone, Copy, PartialEq)] struct Millimeters(f32); #[derive(Debug, Clone, Copy, PartialEq)] struct Degrees(f32); #[derive(Debug, Clone, Copy, PartialEq)] struct ArmTarget { x: Millimeters, y: Millimeters, z: Millimeters, } #[derive(Debug, Clone, Copy, PartialEq)] struct JointAngles { base: Degrees, shoulder: Degrees, elbow: Degrees, } #[derive(Debug, Clone, Copy, PartialEq)] enum KinematicsError { TargetOutOfReach, JointLimitExceeded, CollisionRisk, } ``` ## Suggested inverse kinematics shape ```rust fn inverse_kinematics(target: ArmTarget) -> Result { let horizontal_distance = (target.x.0 * target.x.0 + target.y.0 * target.y.0).sqrt(); let distance = (horizontal_distance * horizontal_distance + target.z.0 * target.z.0).sqrt(); const MAX_REACH_MM: f32 = 250.0; if distance > MAX_REACH_MM { return Err(KinematicsError::TargetOutOfReach); } Ok(JointAngles { base: Degrees(0.0), shoulder: Degrees(45.0), elbow: Degrees(45.0), }) } ``` This is intentionally simplified. It teaches the API shape and safety boundary. ## Participant tasks Participants should: 1. Run the tests. 2. Change the target position. 3. Trigger `TargetOutOfReach`. 4. Add a `JointLimitExceeded` rule. 5. Add one test for reachable and one test for unreachable targets. ## Suggested command ```text cargo test --bin 11_kinematics ``` Optional run output: ```text cargo run --bin 11_kinematics ``` ## Expected result Participants understand that robot geometry should be encoded as explicit logic with explicit failure modes. ## Implementation notes for the code bot - Keep this host-side. - Avoid heavy math and external crates. - Use simplified geometry and be honest in comments. - The key concept is `Result`. - Do not imply that placeholder angles are a complete inverse kinematics solution. ## Acceptance criteria - Reachable targets return `Ok(JointAngles)`. - Unreachable targets return `Err(KinematicsError::TargetOutOfReach)`. - At least two tests exist. - The example closes the workshop story by connecting safe typed APIs to physical robot movement. --- # Cross-chapter consistency The implementation should keep names and mental models consistent across examples. ## Shared vocabulary Use these names consistently: ```text RobotEvent RobotMode MotionCommand MotionVector WheelSpeeds BallObservation ObstacleObservation DistanceCm SensorError ``` ## Shared architecture The whole workshop should repeatedly reinforce this structure: ```text raw hardware input ↓ domain observation ↓ robot event ↓ state machine ↓ motion command ↓ hardware output ``` ## Host-side vs embedded examples Host-side examples: ```text 00_check.rs 01_rust_basics.rs 02_ownership.rs 03_types.rs 07_events.rs 09_sensor_pipeline.rs 10_motor_commands.rs 11_kinematics.rs ``` Embedded examples: ```text 04_first_firmware.rs 05_rgb_output.rs 06_button_events.rs 08_state_machine.rs 12_embassy.rs ``` This split is intentional. If hardware setup fails for a participant, they can still learn and run most of the robot logic locally. ## Suggested workshop flow Participants should experience the examples in this order: 1. Run host sanity check. 2. Learn explicit values and mutation. 3. Learn ownership and borrowing through commands. 4. Learn domain types and sensor errors. 5. Flash first firmware. 6. Control LED as robot status output. 7. Read button as fake robot sensor. 8. Add event priority. 9. Build the robot state machine. 10. Model camera and ultrasonic processing. 11. Convert decisions to wheel speeds. 12. Close with kinematics and safe movement APIs. 13. Show Embassy async timers, tasks, and printing. ## Final learning outcome At the end of the examples, participants should be able to explain this sentence: ```text Rust helps us build robot firmware as explicit transformations from physical signals into typed observations, events, states, commands and actuator outputs. ``` --- # 12_embassy.rs — Async Embassy loop, timers, and printing ## Purpose This example shows why a small async framework is useful on embedded Rust. It should teach: - one manual loop can become multiple async tasks - timers replace ad-hoc delay code - printing can happen while other work keeps running - setup stays explicit, but the scheduling gets simpler ## Conceptual role in the robot journey The workshop has mostly shown hand-written loops. This chapter shows the next step: ```text init hardware -> spawn tasks -> await timers -> print progress ``` ## Code behaviour The firmware should: 1. Initialize Embassy for the Blue Pill. 2. Spawn one periodic async task. 3. Print a heartbeat from that task. 4. Print a second message from the main task. 5. Use `Timer` or `Ticker` instead of a manual busy loop delay. ## Suggested types and tools ```rust use embassy_executor::Spawner; use embassy_time::{Duration, Ticker, Timer}; use cortex_m_semihosting::hprintln; ``` ## Participant tasks Participants should: 1. Run the baseline. 2. Change the print text. 3. Change the timer period. 4. Add a second async task. 5. Compare this flow with the hand-written loop from chapter 04. ## Suggested command ```text run-12 ``` ## Rustup fallback ```text cargo build --target thumbv7m-none-eabi --bin 12_embassy probe-rs run --chip STM32F103C8 target/thumbv7m-none-eabi/debug/12_embassy ``` ## Expected result Participants see that Embassy can handle waiting and concurrent work without manual loop plumbing. ## Implementation notes for the code bot - Keep the example short and readable. - Use `Timer` or `Ticker` for waiting. - Use a small print message for visible progress. - Avoid adding hardware drivers unless they help the lesson. - Keep the example focused on the async model, not on full application logic. ## Acceptance criteria - The firmware builds for the Blue Pill. - At least one async task runs. - Timer-driven waiting is visible in the code. - The output shows that the firmware stays alive while work continues.