Overview

Real-time firmware for Raspberry Pi Pico running FreeRTOS with micro-ROS for ROS2 communication. Handles motor control, sensor reading, and odometry calculation.

System Architecture

graph TB
    subgraph "Raspberry Pi Pico"
        direction TB

        subgraph "FreeRTOS"
            TASKS[Agent Tasks<br/>100 Hz]
            SCHED[Scheduler<br/>Priority-based]
        end

        subgraph "micro-ROS"
            UROS[uRosBridge<br/>Singleton]
            PUBS[Publishers]
            SUBS[Subscribers]
        end

        subgraph "Application Agents"
            DDD[DDD Agent<br/>Odometry & Control]
            MOTORS[Motors Agent<br/>PID Control]
            IMU[IMU Agent<br/>ICM20948]
            TOF[VL6180X Agent<br/>ToF Sensor]
            US[HCSR04 Agent<br/>Ultrasonic]
        end

        subgraph "HAL"
            PWM[PWM Manager]
            ENC[Encoder Manager]
            SPI[SPI Driver]
            I2C[I2C Driver]
        end

        TASKS --> DDD
        TASKS --> MOTORS
        TASKS --> IMU
        TASKS --> TOF
        TASKS --> US

        DDD --> UROS
        MOTORS --> UROS
        IMU --> UROS
        TOF --> UROS
        US --> UROS

        UROS --> PUBS
        UROS --> SUBS

        MOTORS --> PWM
        MOTORS --> ENC
        IMU --> SPI
        TOF --> I2C
        US --> GPIO
    end

    subgraph "Hardware"
        MOTOR_HW[4x DC Motors<br/>with Encoders]
        IMU_HW[ICM20948<br/>9-DOF IMU]
        TOF_HW[VL6180X<br/>ToF Sensor]
        US_HW[HC-SR04<br/>Ultrasonic]
    end

    PWM --> MOTOR_HW
    ENC --> MOTOR_HW
    SPI --> IMU_HW
    I2C --> TOF_HW
    GPIO --> US_HW

    UROS <-->|USB Serial| ROS2[ROS2 Host<br/>micro-ROS Agent]

    style UROS fill:#4CAF50
    style DDD fill:#2196F3
    style MOTORS fill:#FF9800

Agent Architecture

classDiagram
    class Agent {
        <<abstract>>
        #name: string
        #priority: uint
        #stack_size: uint
        +init() bool
        +run() void*
        +getName() string
        +getPriority() uint
    }

    class BlinkAgent {
        -led_pin: uint
        -blink_rate: uint
        +init() bool
        +run() void*
    }

    class DDD {
        -odom_publisher: rcl_publisher_t
        -cmd_vel_subscriber: rcl_subscription_t
        -twist_msg: Twist
        -odom_msg: Odometry
        -position: Vector3f
        -orientation: float
        +init() bool
        +run() void*
        +calculateOdometry() void
        +publishOdometry() void
        +cmdVelCallback() void
    }

    class MotorsAgent {
        -joint_state_publisher: rcl_publisher_t
        -motor_mgr: MotorMgr*
        -pid_controllers: MotorPID[4]
        +init() bool
        +run() void*
        +updatePID() void
        +publishJointStates() void
    }

    class ImuAgent {
        -imu_publisher: rcl_publisher_t
        -imu_sensor: ICM20948*
        -imu_msg: Imu
        +init() bool
        +run() void*
        +readSensor() void
        +publishImuData() void
    }

    class vl6180xAgent {
        -range_publisher: rcl_publisher_t
        -illuminance_publisher: rcl_publisher_t
        -sensor: VL6180X*
        +init() bool
        +run() void*
        +readRange() void
        +readIlluminance() void
    }

    Agent <|-- BlinkAgent
    Agent <|-- DDD
    Agent <|-- MotorsAgent
    Agent <|-- ImuAgent
    Agent <|-- vl6180xAgent

Data Flow Diagram

flowchart TB
    subgraph "ROS2 Host"
        AGENT[micro-ROS Agent]
        CTRL[Controller]
    end
    subgraph "Pico Firmware"
        direction TB

        subgraph "micro-ROS Layer"
            UROS[uRosBridge]
            SUB[/rt/cmd_vel\nSubscriber/]
            PUB_JS[/rt/joint_states\nPublisher/]
            PUB_IMU[/rt/imu/data_raw\nPublisher/]
            PUB_ODOM[/rt/odom\nPublisher/]
            PUB_SENS[/rt/sensors/*\nPublishers/]
        end

        subgraph "Control Layer"
            DDD_A[DDD Agent\nOdometry]
            MOTOR_A[Motors Agent\nPID Control]
        end

        subgraph "Sensor Layer"
            IMU_A[IMU Agent]
            TOF_A[ToF Agent]
        end

        subgraph "Hardware Layer"
            PWM_M[PWM Manager]
            ENC_M[Encoder Manager]
            SPI_D[SPI Driver]
            I2C_D[I2C Driver]
        end
    end

    subgraph "Hardware"
        MOTORS[DC Motors]
        ENCODERS[Encoders]
        IMU_HW[ICM20948]
        TOF_HW[VL6180X]
    end

    CTRL -->|/cmd_vel| AGENT
    AGENT <-->|USB Serial| UROS

    UROS --> SUB
    SUB --> DDD_A
    DDD_A --> MOTOR_A

    MOTOR_A --> PWM_M
    PWM_M --> MOTORS

    ENCODERS --> ENC_M
    ENC_M --> MOTOR_A
    MOTOR_A --> PUB_JS

    MOTOR_A --> DDD_A
    DDD_A --> PUB_ODOM

    IMU_HW --> SPI_D
    SPI_D --> IMU_A
    IMU_A --> PUB_IMU

    TOF_HW --> I2C_D
    I2C_D --> TOF_A
    TOF_A --> PUB_SENS

    PUB_JS --> UROS
    PUB_IMU --> UROS
    PUB_ODOM --> UROS
    PUB_SENS --> UROS

    UROS --> AGENT
    AGENT --> CTRL

Sequence Diagram: Initialization

sequenceDiagram
    participant Main
    participant FreeRTOS
    participant uRosBridge
    participant Agents
    participant Hardware

    Main->>Hardware: Initialize GPIO, PWM, SPI, I2C
    Main->>uRosBridge: Initialize micro-ROS
    uRosBridge->>uRosBridge: Create node
    uRosBridge->>uRosBridge: Create executor

    Main->>Agents: Create agent instances
    loop For each agent
        Main->>Agents: agent->init()
        Agents->>uRosBridge: Create publishers/subscribers
        Agents->>Hardware: Initialize hardware
        Agents-->>Main: Success
    end

    Main->>FreeRTOS: Create agent tasks
    loop For each agent
        FreeRTOS->>Agents: xTaskCreate(agent->run)
    end

    Main->>FreeRTOS: Start scheduler

    Note over FreeRTOS,Agents: Tasks running at 100 Hz

Sequence Diagram: Control Loop

sequenceDiagram
    participant Host as ROS2 Host
    participant uRos as uRosBridge
    participant DDD as DDD Agent
    participant Motors as Motors Agent
    participant PWM as PWM Manager
    participant Enc as Encoders

    loop 100 Hz Control Loop
        Host->>uRos: /cmd_vel (Twist)
        uRos->>DDD: cmdVelCallback()
        DDD->>DDD: Store target velocities

        DDD->>Motors: Set target wheel velocities<br/>(inverse kinematics)

        Enc->>Motors: Read encoder positions
        Motors->>Motors: Calculate velocities
        Motors->>Motors: PID control
        Motors->>PWM: Set PWM duty cycles
        PWM->>PWM: Update motor outputs

        Motors->>uRos: Publish /joint_states
        uRos->>Host: Forward joint states

        Motors->>DDD: Provide wheel velocities
        DDD->>DDD: Calculate odometry<br/>(forward kinematics)
        DDD->>uRos: Publish /odom
        uRos->>Host: Forward odometry
    end

Task Scheduling

gantt
    title FreeRTOS Task Scheduling (10ms period)
    dateFormat X
    axisFormat %L ms

    section High Priority
    Motors Agent (100Hz)    :0, 2
    DDD Agent (100Hz)       :2, 4

    section Medium Priority
    IMU Agent (50Hz)        :4, 6

    section Low Priority
    ToF Agent (Variable)    :6, 7
    Ultrasonic (Variable)   :7, 8
    Blink Agent (1Hz)       :8, 9

    section Idle
    Idle Task               :9, 10

Memory Layout

graph TB
    subgraph "Pico Memory (264 KB RAM)"
        direction TB

        STACK[FreeRTOS Stacks<br/>~64 KB]
        HEAP[FreeRTOS Heap<br/>~128 KB]
        UROS[micro-ROS Buffers<br/>~32 KB]
        STATIC[Static Data<br/>~16 KB]
        BSS[BSS<br/>~8 KB]
        FREE[Free<br/>~16 KB]
    end

    style STACK fill:#4CAF50
    style HEAP fill:#2196F3
    style UROS fill:#FF9800

Pin Configuration

graph LR
    subgraph "GPIO Pins"
        direction TB

        subgraph "Motors"
            M_FL[GP20/21<br/>Front Left PWM]
            M_FR[GP4/5<br/>Front Right PWM]
            M_RL[GP14/15<br/>Rear Left PWM]
            M_RR[GP22/28<br/>Rear Right PWM]
        end

        subgraph "Encoders"
            E_FL[GP6/7<br/>Front Left Enc]
            E_FR[GP8/9<br/>Front Right Enc]
            E_RL[GP10/11<br/>Rear Left Enc]
            E_RR[GP12/13<br/>Rear Right Enc]
        end

        subgraph "Sensors"
            IMU_SPI[GP16-19<br/>IMU SPI0]
            TOF_I2C[GP2/3<br/>ToF I2C1]
        end

        subgraph "Status"
            LED[GP26<br/>Status LED]
        end
    end

    style M_FL fill:#4CAF50
    style M_FR fill:#4CAF50
    style M_RL fill:#4CAF50
    style M_RR fill:#4CAF50

PID Control Flow

flowchart TB
    START[Control Loop Start] --> READ[Read Encoder Position]
    READ --> CALC_VEL[Calculate Velocity<br/>position delta / dt]
    CALC_VEL --> ERROR[Calculate Error<br/>target - actual]

    ERROR --> P[Proportional Term<br/>Kp * error]
    ERROR --> I[Integral Term<br/>Ki * Σerror]
    ERROR --> D[Derivative Term<br/>Kd * Δerror]

    P --> SUM[Sum PID Terms]
    I --> SUM
    D --> SUM

    SUM --> LIMIT[Apply Limits<br/>-100% to +100%]
    LIMIT --> PWM[Set PWM Duty Cycle]
    PWM --> PUBLISH[Publish Joint State]
    PUBLISH --> WAIT[Wait 10ms]
    WAIT --> START

    style ERROR fill:#4CAF50
    style SUM fill:#2196F3
    style PWM fill:#FF9800

Odometry Calculation

flowchart TB
    START[Get Wheel Velocities] --> FK[Forward Kinematics<br/>Mecanum Drive]

    FK --> VX[Calculate vx<br/>Σwheel_vel / 4]
    FK --> VY[Calculate vy<br/>weighted sum]
    FK --> OMEGA[Calculate ω<br/>weighted sum]

    VX --> INTEGRATE[Integrate Velocities<br/>Δt = 10ms]
    VY --> INTEGRATE
    OMEGA --> INTEGRATE

    INTEGRATE --> UPDATE_POS[Update Position<br/>x, y, θ]
    UPDATE_POS --> CREATE_MSG[Create Odometry Message]
    CREATE_MSG --> PUBLISH[Publish /odom]

    style FK fill:#4CAF50
    style INTEGRATE fill:#2196F3

Communication Protocol

sequenceDiagram
    participant Pico as Pico Firmware
    participant USB as USB CDC
    participant Agent as micro-ROS Agent
    participant ROS2 as ROS2 Network

    Note over Pico,Agent: Initialization
    Pico->>USB: Open USB CDC
    Agent->>USB: Connect to /dev/ttyACM0
    Pico->>Agent: micro-ROS handshake
    Agent-->>Pico: Connection established

    Note over Pico,ROS2: Runtime Communication

    loop Every 10ms
        Pico->>Agent: Publish /joint_states
        Agent->>ROS2: Forward to ROS2 network

        ROS2->>Agent: /cmd_vel command
        Agent->>Pico: Forward to firmware
    end

    loop Every 20ms
        Pico->>Agent: Publish /imu/data_raw
        Pico->>Agent: Publish /odom
        Agent->>ROS2: Forward to ROS2 network
    end

Build System

graph TB
    subgraph "Source Files"
        SRC[src/*.cpp]
        HDR[src/*.h]
        HAL[src/hal/*.cpp]
        APP[src/application/*.cpp]
    end

    subgraph "Dependencies"
        PICO_SDK[Pico SDK]
        FREERTOS[FreeRTOS]
        MICROROS[micro-ROS]
        EIGEN[Eigen]
    end

    subgraph "Build Process"
        CMAKE[CMakeLists.txt]
        MAKE[Makefile Wrapper]
    end

    subgraph "Output"
        ELF[firmware.elf]
        UF2[firmware.uf2]
        BIN[firmware.bin]
    end

    SRC --> CMAKE
    HDR --> CMAKE
    HAL --> CMAKE
    APP --> CMAKE

    PICO_SDK --> CMAKE
    FREERTOS --> CMAKE
    MICROROS --> CMAKE
    EIGEN --> CMAKE

    CMAKE --> MAKE
    MAKE --> ELF
    ELF --> UF2
    ELF --> BIN

    style CMAKE fill:#4CAF50
    style UF2 fill:#2196F3

Configuration Parameters

Parameter Location Default Description
Control Loop Rate MotorsAgent 100 Hz PID update frequency
IMU Rate ImuAgent 50 Hz IMU reading frequency
PID Gains (Kp, Ki, Kd) MotorPID Tuned Per-motor PID parameters
Wheel Radius DDD 0.047 m For odometry calculation
Wheel Base DDD 0.220 m For odometry calculation
Encoder CPR MotorMgr 1440 Counts per revolution
PWM Frequency PWMManager 20 kHz Motor PWM frequency

Performance Characteristics

  • Control Loop: 100 Hz (10 ms period)
  • IMU Reading: 50 Hz (20 ms period)
  • Odometry Publishing: 50 Hz
  • Joint States Publishing: 100 Hz
  • USB Latency: < 2 ms
  • PID Computation: < 0.5 ms
  • Total CPU Usage: ~60% (with all agents)

Error Handling

Error Condition Detection Action Recovery
micro-ROS connection lost Timeout Log error, continue operation Auto-reconnect
Encoder read failure Invalid value Use last valid value Retry next cycle
IMU read failure SPI error Skip publish Retry next cycle
Motor stall Zero velocity despite command Reduce PWM Automatic
Watchdog timeout No host communication Stop motors Wait for reconnect

Dependencies

  • Pico SDK: Hardware abstraction, USB, GPIO
  • FreeRTOS: Real-time task scheduling
  • micro-ROS: ROS2 communication
  • Eigen: Linear algebra for odometry

Build Commands

# Debug build
cd firmware && make build

# Release build
cd firmware && make build_release

# Flash to Pico
make flash

# Monitor output
./monitor_firmware.sh

# Run tests
cd tests && mkdir -p build && cd build
cmake .. && make && ctest

Troubleshooting

Issue Possible Cause Solution
Firmware won't flash Pico not in BOOTSEL mode Hold BOOTSEL while connecting
No USB device Driver issue Check /dev/ttyACM* exists
Motors don't respond PWM not initialized Check pin configuration
Encoders not counting Interrupt not working Verify encoder connections
IMU data all zeros SPI not working Check SPI wiring
High CPU usage Too many agents Reduce agent frequencies