# 🐭 Toshiba Micromouse Firmware ``` ╔══════════════════════════════════════════╗ β•‘ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β•‘ β•‘ β”‚ LEFT β”‚ β”‚ RIGHT β”‚ β•‘ β•‘ β”‚ MOTOR β”‚ β”‚ MOTOR β”‚ β•‘ β•‘ β”‚ T32A0 β”‚ β”‚ T32A3 β”‚ β•‘ β•‘ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β•‘ β•‘ β”‚ β”‚ β•‘ β•‘ β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β” β•‘ β•‘ β”‚ TMPM4KNF10AFG β”‚ β•‘ β•‘ β”‚ Cortex-M4 @ 160MHz β”‚ β•‘ β•‘ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β•‘ β•‘ β”‚ β”‚ 1kHz Control β”‚ β”‚ β•‘ β•‘ β”‚ β”‚ Loop (T32A1) β”‚ β”‚ β•‘ β•‘ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β•‘ β•‘ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β•‘ β•‘ β”‚ β”‚ β•‘ β•‘ β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β” β•‘ β•‘ β”‚ ENC0 β”‚ β”‚ ENC2 β”‚ β•‘ β•‘ β”‚ Left β”‚ β”‚ Right β”‚ β•‘ β•‘ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β•‘ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• ``` --- ## Architecture Everything is driven by a single 1 kHz control tick. Each tick runs five stages in order, and each stage consumes the output of the one above it. The maze planner (`FloodFill`) is pure logic and never touches hardware. `Navigator` owns all motion and calls the planner for decisions. `Navigator` runs before `Motion` so the setpoint it produces is acted on within the same tick rather than one tick late. ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ main.c β”‚ β”‚ while(1) { β”‚ β”‚ if (Timebase_GetAndClear()) { ◄── 1 kHz tick β”‚ β”‚ Encoder_Update(); // read encoders, filter speed β”‚ β”‚ Odometry_Update(); // pose (x, y, heading) β”‚ β”‚ IR_SampleStep(); // advance the IR sampler β”‚ β”‚ Navigator_Update(); // plan/turn/drive FSM step β”‚ β”‚ Motion_Update(); // PID -> motor PWM (non-blocking)β”‚ β”‚ } β”‚ β”‚ } β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ once per tick β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β–Ό β–Ό β–Ό β–Ό β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚Encoder β”‚ β”‚Odometryβ”‚ β”‚ Motion β”‚ β”‚ Navigator β”‚ β”‚ FloodFillβ”‚ β”‚speed + β”‚ β”‚pose β”‚ β”‚PID + β”‚ β”‚ FSM: │◄─ BFS β”‚ β”‚positionβ”‚ β”‚x,y,ΞΈ β”‚ β”‚motor β”‚ β”‚ planβ†’turn β”‚ β”‚ Algorithmβ”‚ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ β†’drive β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”‚ β–Ό β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ENC0/2 β”‚ β”‚ β”‚ Motor β”‚ β”‚ β”‚ IrSensor β”‚ β”‚A-ENC32 β”‚ └────►│ TB67H450β”‚ └──────►│ ADC β”‚ β”‚quad in β”‚ β”‚ +T32A0/3β”‚ β”‚ 4Γ— wall β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ PWM PPG β”‚ β”‚ sensing β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Control flow summary** | Stage | Module | Role | |-------|--------|------| | 1 | `Encoder` | Read A-ENC32 counters(CPS) | | 2 | `Odometry` | Integrate differential-drive pose from encoder deltas | | 3 | `IrSensor` | Advance one phase of the ambient-cancelling sample cycle | | 4 | `Navigator` | Non-blocking FSM. Asks `FloodFill` for moves, executes them | | 5 | `Motion` | Per-wheel PID speed loop β†’ `Motor` PWM duty | | .. | `Profile` | Trapezoidal velocity profile. Called in `Navigator` | | .. | `FloodFill` | Pure BFS planner over discovered walls (no hardware) | --- ## Project Structure ``` toshiba-micromouse/ β”œβ”€β”€ keil/ # Keil Β΅Vision project files β”œβ”€β”€ hardware/ # Hardware overview for Mouse β”œβ”€β”€ docs/ # Images and documentation assets β”œβ”€β”€ README.md # Project landing page └── src/ β”œβ”€β”€ README.md # This file. Firmware architecture β”œβ”€β”€ main.c # Entry point, 1 kHz control loop β”œβ”€β”€ ModuleTest.C # Testing file β”‚ β”œβ”€β”€ drivers/ # Register-level hardware layer β”‚ β”œβ”€β”€ timer32A.c/h # T32A: 20 kHz PWM (ch0/3) + 1 kHz tick (ch1) β”‚ β”œβ”€β”€ gpio.c/h # Port configuration + emitter GPIO β”‚ β”œβ”€β”€ encoder32A.c/h # A-ENC32 quadrature hardware read β”‚ β”œβ”€β”€ adc.c/h # ADC-I units A & C (IR receivers) β”‚ β”œβ”€β”€ dma.c/h # DMAC-B burst transfer for ADC (Optional) β”‚ β”œβ”€β”€ uart.c/h # UART-C serial debug output β”‚ └── systick.c/h # Blocking Β΅s/ms delay (stateless) β”‚ └── modules/ # Application logic layer β”œβ”€β”€ Timebase.c/h # 1 kHz tick flag service β”œβ”€β”€ Encoder.c/h # Filtered speed + signed position β”œβ”€β”€ Odometry.c/h # Differential-drive pose (x, y, ΞΈ) β”œβ”€β”€ PID.c/h # Generic PID controller math β”œβ”€β”€ Motor.c/h # TB67H450 H-bridge direction + duty β”œβ”€β”€ Motion.c/h # PID speed loop bridging Encoderβ†’Motor β”œβ”€β”€ Profile.c/h # Trapezoidal velocity profile (mm, mm/s) β”œβ”€β”€ IrSensor.c/h # IR sampling, ambient cancel, distance β”œβ”€β”€ FloodFill.c/h # BFS flood-fill maze planner └── Navigator.c/h # Cell-level motion sequencer (FSM) ``` --- ## Hardware Specs | Component | Part | Interface | |-----------|------|-----------| | **MCU** | TMPM4KNF10AFG | Arm Cortex-M4, 160 MHz, 5 V | | **Motors** | TB67H450AFNG + Pololu #5211 N20 30:1 | T32A PPG PWM @ 20 kHz | | **Encoders** | A-ENC32 (on-chip) | Quadrature, 12 CPR Γ— 29.89 β‰ˆ 359/rev | | **IR Sensors** | IR LED + phototransistor Γ—4 | ADC-I | | **Debug** | CMSIS-DAP + Level Shifter (TXB0104) | SWD | ### Pin Map ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ PORT A β”‚ PA3 ──► T32A00OUTA (Left PWM) β”‚ β”‚ β”‚ PA4 ──► T32A00OUTB (Left PWM) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ PORT C β”‚ PC2 ──► T32A30OUTA (Right PWM) β”‚ β”‚ β”‚ PC3 ──► T32A30OUTB (Right PWM) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ PORT N β”‚ PN0 ──► ENC0A (Left encoder) β”‚ β”‚ β”‚ PN1 ──► ENC0B (Left encoder) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ PORT D β”‚ PD3 ──► ENC2A (Right encoder) β”‚ β”‚ β”‚ PD4 ──► ENC2B (Right encoder) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ PORT L β”‚ PL0 ──► AINA16 (Far Left IR) β”‚ β”‚ β”‚ PL1 ──► AINA15 (Left IR) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ PORT J β”‚ PJ0 ──► AINC00 (Far Right IR) β”‚ β”‚ β”‚ PJ1 ──► AINC01 (Right IR) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ PORT U β”‚ PU0 ──► Left IR Emitter β”‚ β”‚ β”‚ PU1 ──► Far Left IR Emitter β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ PORT G β”‚ PG4 ──► Far Right IR Emitter β”‚ β”‚ β”‚ PG5 ──► Right IR Emitter β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ## Timer Configuration ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ T32A0 (Left Motor) β”‚ T32A3 (Right Motor) β”‚ β”‚ ───────────────────── β”‚ ───────────────────── β”‚ β”‚ Mode: 16-bit PPG β”‚ Mode: 16-bit PPG β”‚ β”‚ Prescaler: 1:1 β”‚ Prescaler: 1:1 β”‚ β”‚ Frequency: 20 kHz β”‚ Frequency: 20 kHz β”‚ β”‚ Period: 4000 counts β”‚ Period: 4000 counts β”‚ β”‚ Pins: PA3, PA4 β”‚ Pins: PC2, PC3 β”‚ β”‚ Duty: RG0 = 0-4000 β”‚ Duty: RG0 = 0-4000 β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ T32A1 (Control Loop) β”‚ β”‚ ───────────────────── β”‚ β”‚ Mode: 32-bit Interval β”‚ β”‚ Prescaler: 1:1 β”‚ β”‚ Frequency: 1 kHz β”‚ β”‚ Period: 80000 counts (80 MHz Ξ¦T0) β”‚ β”‚ Interrupt: INTT32A01AC (NVIC) β”‚ β”‚ Flag: T32A01AC_IRQ_Fire (volatile bool) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ## Navigation Model `FloodFill` and `Navigator` use **relative** moves at their boundary, so only one of them needs a compass. - `FloodFill_Plan()` returns one of `FORWARD`, `TURN_LEFT`, `TURN_RIGHT`, `TURN_AROUND`, `STOP`. It owns the grid cell and heading, and it is the only module that does. - `Navigator` holds **no heading**. Every move is a *segment*, a fixed length of wheel path measured from encoder positions latched at segment start. - Odometry is not read by `Navigator` at all. It uses `Odometry.h` for the geometry constants and nothing else. **Segments** | Type | Wheels | Length | |------|--------|--------| | Drive | both forward | `CELL_SIZE_MM` | | Turn | opposed | `ΞΈ Γ— WHEELBASE_MM / 2` per wheel | A pivot is an arc, so it is a straight-line profile in disguise. One `Profile` serves both, which is why there is no separate angular controller. **FSM** ``` NAV_PLAN β†’ NAV_TURN β†’ NAV_DRIVE β†’ NAV_SETTLE β†’ NAV_PLAN ↓ NAV_FINISHED ``` `NAV_TURN` is skipped when the action is `FORWARD`. `NAV_SETTLE` holds the robot still so the IR filter converges on the cell it stopped in before `NAV_PLAN` reads walls from it. **Correction** Dead reckoning drifts, so the maze walls are the absolute reference. - Side IR keeps the robot centred in a corridor, damped by encoder skew - Front IR resets distance error at any cell facing a wall - Both fall back to encoder-only when no wall is in range > **Invariant (load-bearing):** `FloodFill`'s direction enum must stay clockwise > (N=0, E=1, S=2, W=3). `Dir_Right()` is `(dir + 1) & 3`. Renumbering it breaks > every turn. --- ## Call Graphs Generated with [Doxygen](https://www.doxygen.nl/) and rendered by [Graphviz](https://graphviz.org/). Doxygen parses the source comments and emits the browsable API docs; Graphviz draws the call and caller graphs. Full documentation, with every function, its callers, and cross-referenced source, opens at: **[`docs/doxygen/html/index.html`](../docs/doxygen/html/index.html)** Regenerate after changing the source: ``` doxygen Doxyfile ``` **`main()`** main call graph **`Navigator_Update()`** Navigator_Update call graph The graph shows every call the function can make, not which state makes it. The FSM order is in [Navigation Model](#navigation-model). --- ## Build & Flash ```text IDE: Keil Β΅Vision Target: TMPM4KNF10AFG Debug: CMSIS-DAP Flash: On-chip 512 KB ``` --- ## Deployment Calibration Before real runs, these must be measured/tuned on the actual robot: | Where | What | Why | |-------|------|-----| | `Encoder` | Forward β†’ **increasing** position | Reversed sign turns PID into positive feedback (runaway) | | `Odometry.h` | `WHEEL_DIAMETER_MM`, `COUNTS_PER_REV` | Sets `MM_PER_COUNT`. Wrong value scales every distance | | `Odometry.h` | `WHEELBASE_MM` | Sets the arc length of a turn. Wrong value over or under rotates | | `IrSensor.c` | `ir_cal[]` ADCβ†’distance points | One table per channel. Wall detection and wall following both read it | | `Navigator.c` | `IR_SIDE_TARGET_MM` | Side reading when centred. Only used when one wall is visible | | `Navigator.c` | `IR_FRONT_TARGET_MM` | Front reading at a cell centre. Sets where a drive stops against a wall | | `PID.h` | Gains for CPS-scaled error | Error is ~thousands (CPS); default `Kp` saturates instantly | Angle depends on the *ratio* `MM_PER_COUNT / WHEELBASE_MM`, distance on `MM_PER_COUNT` alone. Change the wheel and the wheelbase has to move with it or every turn shifts by the same percentage. Which test measures each value, and whether it has been done, is tracked in [`docs/evidence/README.md`](../docs/evidence/README.md). --- ## Module Status | Module | Status | Description | |--------|--------|-------------| | `Timebase` | Done | 1 kHz control tick flag | | `Encoder` | Done | Filtered speed + signed position | | `Odometry` | Done | Differential-drive pose estimate | | `PID` | Done | Generic PID controller | | `Motor` | Done | H-bridge direction + duty | | `Motion` | Done | PID speed loop (Encoderβ†’Motor) | | `Profile` | Done | Trapezoidal velocity profile | | `IrSensor` | Done | IR sampling, ambient cancel, distance | | `UART` | Done | Serial debug output (TX) | | `FloodFill` | Done | BFS flood-fill planner | | `Navigator` | Done | Segment FSM + wall following | --- ## References - [TMPM4KNF10AFG Product Page](https://toshiba.semicon-storage.com/us/semiconductor/product/microcontrollers/txz4aplus-series/m4k-group.html) - [RM-T32A-C Timer Reference](https://toshiba.semicon-storage.com/info/RM-T32A-C_en_20241129.pdf) - [RM-A-ENC32-A Encoder Reference](https://toshiba.semicon-storage.com/info/RM-A-ENC32-A_en_20250221.pdf) - [RM-ADC-I Reference](https://toshiba.semicon-storage.com/info/RM-ADC-I_en_20251205.pdf) - [RM-DMAC-B DMA Reference](https://toshiba.semicon-storage.com/info/RM-DMAC-B_en_20241031.pdf) - [Pololu #5211 Motor Datasheet](https://www.pololu.com/file/0J1487/pololu-micro-metal-gearmotors-rev-6-2.pdf) - [Doxygen](https://www.doxygen.nl/) by Dimitri van Heesch, GPL-2.0 - [Graphviz](https://graphviz.org/), EPL-1.0 --- **Author:** Kevin Le  β€’  2026