This document specifies the Hexagonal Software Architecture (Ports & Adapters) of the tracked_bot firmware for the ATmega328P microcontroller (16 MHz), the directory layout, the contract header specifications (core/interfaces/), and the implementation of SOLID design principles in Embedded C.
core/interfaces/)tracked_motion) & Soft-Start Slew LimitingThe firmware is structured according to Ports & Adapters (Hexagonal Architecture) and SOLID principles tailored for resource-constrained 8-bit AVR microcontrollers:
core/interfaces/.core/ defines what it requires to operate (imotor.h for motor drive control, icomm.h for stream communication, motion_types.h for status and kinematic states).core/ have zero dependencies on AVR-specific hardware headers (<avr/io.h>, <avr/interrupt.h>) or MCU registers.drivers/ - OCP - Open/Closed Principle):
core/interfaces/.drivers/ without touching a single line of domain code in core/.main.c):
main.c file serves as the single Composition Root of the application.wdt_enable(WDTO_2S)).motion_status_t (MOTION_STATUS_OK, MOTION_STATUS_INVALID_ARG, MOTION_STATUS_ERROR) instead of unchecked void returns.NULL pointer validation and early returns to maintain execution predictability.Each functional component is isolated into a dedicated sub-directory containing its corresponding .c and .h pair:
sw/trackedbot/
βββ main.c βββ Composition Root (Dependency Injection, WDT reset)
β
βββ core/ βββ DOMAIN LOGIC (100% hardware-independent C99)
β βββ interfaces/ βββ Port Contracts & Domain Types
β β βββ icomm.h - Communication stream port (comm_stream_t)
β β βββ imotor.h - Motor actuator port (motor_handle_t, motor_dir_t)
β β βββ motion_types.h - Motion commands, kinematic states & status codes
β β
β βββ tracked_motion/ βββ Differential tracked chassis kinematics
β β βββ tracked_motion.h - tracked_motion_t, speed limits, API
β β βββ tracked_motion.c - Skid-steering kinematics, composition with slew_limiter
β β
β βββ slew_limiter/ βββ Soft-start / Slew-rate acceleration ramp
β β βββ slew_limiter.h - slew_limiter_t, API
β β βββ slew_limiter.c - Pure mathematical acceleration/deceleration ramp
β β
β βββ command_parser/ βββ Line-based robust ASCII command parser
β β βββ command_parser.h - command_parser_t, callback handlers, API
β β βββ command_parser.c - Line accumulation, noise filtering, responses
β β
β βββ failsafe_watchdog/ βββ Software auto-stop safety timer
β β βββ failsafe_watchdog.h - failsafe_watchdog_t, configuration, API
β β βββ failsafe_watchdog.c - Automatic motor shutdown on communication loss
β β
β βββ ring_buffer/ βββ Generic FIFO Circular Buffer (SPSC lock-free)
β βββ ring_buffer.h - ring_buffer_t, API
β βββ ring_buffer.c - Single-Producer Single-Consumer circular queue
β
βββ drivers/ βββ HARDWARE ADAPTERS (AVR registers & shield pins)
β βββ l293d_shield/ βββ L293D motor shield adapter
β β βββ l293d_shield.h - l293d_channel_t enum, API, imotor adapter
β β βββ l293d_shield.c - Timer0 PWM registers (OCR0A/OCR0B), H-bridge logic
β β
β βββ uart_avr/ βββ USART0 stream adapter (Non-blocking RX & TX)
β β βββ uart_avr.h - uart_config_t, API, icomm adapter
β β βββ uart_avr.c - USART_RX_vect & USART_UDRE_vect ring buffer ISRs
β β
β βββ shift_reg/ βββ 74HC595 8-bit shift register driver
β β βββ shift_reg.h - Serial bit-bang transmission API
β β βββ shift_reg.c - Uno pin mappings (PD4, PD7, PB0, PB4)
β β
β βββ timer_avr/ βββ Timer1 1 ms millisecond system timebase
β βββ timer_avr.h - Millis timebase API and periodic tick utilities
β βββ timer_avr.c - Timer1 CTC configuration (16 MHz / 64 prescaler)
β
βββ build/ βββ BUILD SYSTEM & MAKEFILES
βββ cflags.mk - Unified compiler flags and modular include paths
βββ subdir.mk - Per-subfolder compilation rules
βββ tools.mk - Toolchain definitions (avr-gcc, avr-objcopy, avrdude)
βββ Makefile - Master build target orchestration
core/interfaces/)The contracts define abstract struct-of-function-pointers that decouple domain interactors from physical hardware peripherals:
icomm.h β Byte Stream PortAbstracts any bidirectional byte-oriented communication medium (UART, USB-CDC, SPI, virtual ring buffer):
typedef struct {
void *context;
bool (*available)(void *context);
uint8_t (*read)(void *context);
void (*write)(void *context, uint8_t byte);
void (*write_str)(void *context, const char *str);
} comm_stream_t;
imotor.h β Actuator Actuation PortAbstracts a single directional motor channel regardless of the underlying H-bridge IC or PWM generation scheme:
typedef struct {
void *context;
motion_status_t (*set_direction)(void *context, motor_dir_t dir);
motion_status_t (*set_speed)(void *context, uint8_t speed);
motion_status_t (*stop)(void *context);
} motor_handle_t;
motion_types.h β Domain Types & Status CodesProvides standard enumeration types for locomotion directions, kinematic states, and return statuses:
typedef enum {
MOTION_STATUS_OK = 0,
MOTION_STATUS_ERROR,
MOTION_STATUS_INVALID_ARG
} motion_status_t;
typedef enum {
MOTION_STOP = 0,
MOTION_FORWARD,
MOTION_BACKWARD,
MOTION_TURN_LEFT,
MOTION_TURN_RIGHT,
MOTION_SPIN_LEFT,
MOTION_SPIN_RIGHT
} motion_cmd_t;
To satisfy the Open/Closed Principle (OCP) and eliminate brittle global macros, hardware-specific constants are strictly confined to their respective adapter modules:
DDRD, PORTD, DDRB, PORTB) and Timer0 fast PWM configuration (TCCR0A, TCCR0B, OCR0A, OCR0B) are defined solely inside drivers/l293d_shield/l293d_shield.c. For electrical pinout details, see ../hw/motor_shield.md.PD4), latch (PB4), serial data (PB0), and output enable (PD7) are isolated inside drivers/shift_reg/shift_reg.c.UBRR0H, UBRR0L), control registers (UCSR0A, UCSR0B, UCSR0C), and interrupt vectors (USART_RX_vect, USART_UDRE_vect) are encapsulated within drivers/uart_avr/uart_avr.c. For Wi-Fi ESP8266 routing via DIP switches, see ../hw/main_board.md.tracked_motion) & Soft-Start Slew LimitingThe tracked_motion module manages differential track velocities for the T101 mechanical chassis:
MOTION_FORWARD / MOTION_BACKWARD: Both tracks operate synchronously at equal target speeds.MOTION_TURN_LEFT / MOTION_TURN_RIGHT: Pivot turn where one track is halted and the opposing track drives forward.MOTION_SPIN_LEFT / MOTION_SPIN_RIGHT: In-place counter-rotation ($R = 0$) where opposing tracks rotate in opposite directions.slew_limiter)Directly switching motor PWM from 0 to 255 produces massive inrush currents (up to 2.5 A per motor at stall), causing battery voltage sag that can brown out the ATmega328P and overheat the L293D drivers (see ../hw/power_supply.md):
slew_limiter ramps current motor speed toward the target speed in fixed increment steps executed every 20 ms.MOTION_STOP command or a failsafe timeout occurs, the slew limiter is bypassed to guarantee an immediate zero-latency stop.The firmware supports two protocol layers:
FWD\n, BWD\n, LEFT\n, RIGHT\n, SPINL\n, SPINR\n, STOP\n, SPD:<val>\n, PING\n.OK:FWD\r\n, OK:SPD\r\n, PONG\r\n, ERR:UNKNOWN\r\n.USART_RX_vect): Incoming bytes are pushed into a lock-free Single-Producer Single-Consumer (SPSC) circular queue (ring_buffer_t). The main loop pulls characters without blocking.USART_UDRE_vect): Transmitted strings are written to s_tx_ring. The Data Register Empty interrupt triggers in the background, feeding the hardware UDR0 register until the buffer is empty, ensuring zero CPU stalls during telemetry transmission.graph TD
subgraph CompositionRoot ["Composition Root"]
MAIN["main.c"]
end
subgraph CoreLayer ["sw/trackedbot/core/ (Hardware-Agnostic Domain Logic)"]
subgraph Interfaces ["interfaces/ (Ports)"]
ICOMM["icomm.h<br/>(comm_stream_t)"]
IMOTOR["imotor.h<br/>(motor_handle_t)"]
TYPES["motion_types.h<br/>(motion_cmd_t, motion_status_t)"]
end
TM["tracked_motion/"]
SLEW["slew_limiter/"]
PARSER["command_parser/"]
WATCHDOG["failsafe_watchdog/"]
RB["ring_buffer/"]
end
subgraph DriversLayer ["sw/trackedbot/drivers/ (AVR Hardware Adapters)"]
L293D["l293d_shield/"]
UART["uart_avr/"]
TIMER["timer_avr/"]
SHIFT["shift_reg/"]
end
%% Dependencies of Core onto Interfaces and Collaborators
TM --> IMOTOR
TM --> TYPES
TM --> SLEW
PARSER --> ICOMM
PARSER --> TM
PARSER --> TYPES
WATCHDOG --> TM
WATCHDOG --> TYPES
%% Dependencies of Drivers onto Interfaces and Core
L293D --> IMOTOR
L293D --> SHIFT
UART --> ICOMM
UART --> RB
%% Composition Root wires everything
MAIN --> TM
MAIN --> PARSER
MAIN --> WATCHDOG
MAIN --> L293D
MAIN --> UART
MAIN --> TIMER
classDiagram
class comm_stream_t {
+void* context
+bool (*available)(void* ctx)
+uint8_t (*read)(void* ctx)
+void (*write)(void* ctx, uint8_t b)
+void (*write_str)(void* ctx, const char* s)
}
class motor_handle_t {
+void* context
+set_direction(void* ctx, motor_dir_t dir) motion_status_t
+set_speed(void* ctx, uint8_t speed) motion_status_t
+stop(void* ctx) motion_status_t
}
class slew_limiter_t {
+uint8_t current_val
+uint8_t target_val
+uint8_t rate_per_step
+slew_limiter_update() motion_status_t
}
class tracked_motion_t {
-motor_handle_t left_motor
-motor_handle_t right_motor
-slew_limiter_t speed_limiter
+set_command(motion_cmd_t cmd) motion_status_t
+set_speed(uint8_t speed) motion_status_t
+update(uint16_t delta_ms) motion_status_t
+stop() motion_status_t
}
class command_parser_t {
-comm_stream_t stream
-tracked_motion_t* tracked_motion
+process() motion_status_t
}
class uart_avr {
+uart_avr_init(baud)
+uart_avr_get_stream() comm_stream_t
}
class l293d_shield {
+l293d_init()
+l293d_get_motor_handle(ch, inv) motor_handle_t
}
uart_avr ..|> comm_stream_t : Produces Adapter
l293d_shield ..|> motor_handle_t : Produces Adapter
command_parser_t --> comm_stream_t : Consumes
command_parser_t --> tracked_motion_t : Controls
tracked_motion_t *-- slew_limiter_t : Composes
tracked_motion_t --> motor_handle_t : Consumes Left & Right
sequenceDiagram
autonumber
actor Client as Remote Client / ESP8266 Wi-Fi
participant USART as USART0 (uart_avr)
participant Parser as command_parser
participant Watchdog as failsafe_watchdog
participant TM as tracked_motion
participant Slew as slew_limiter
participant L293D as l293d_shield
participant ShiftReg as 74HC595 (shift_reg)
participant Motors as DC Motors M3 & M4
Client->>USART: Transmit "FWD\n" command
Note over USART: Circular RX ring buffer stores incoming bytes via ISR
loop main() polling cycle
Parser->>USART: comm_available(&stream)
USART-->>Parser: true
Parser->>USART: comm_read(&stream) -> Accumulates into s_line_buffer
Note over Parser: Delimiter '\n' detected -> Parse token "FWD"
Parser->>Watchdog: on_activity() -> failsafe_feed()
Parser->>TM: tracked_motion_set_command(MOTION_FORWARD)
TM->>Slew: slew_limiter_set_target(target_speed)
TM->>L293D: motor_set_direction(left_motor, FORWARD)
TM->>L293D: motor_set_direction(right_motor, FORWARD)
Parser->>USART: comm_write_str(&stream, "OK:FWD\r\n")
Note over USART: Output queued in TX Ring Buffer (UDRE ISR sends asynchronously)
Note over TM: 20 ms periodic timer tick -> tracked_motion_update()
TM->>Slew: slew_limiter_update() -> Smoothly ramps PWM duty cycle
TM->>L293D: motor_set_speed(OCR0A = cur_speed, OCR0B = cur_speed)
L293D->>Motors: Smooth PWM acceleration drives tracks forward
end
OCR0A/OCR0B), 74HC595 direction multiplexer, and M3/M4 differential channel mapping.EXT_PWR vs. USB), motor EMI filtering, and battery current budgets.