Understanding the Vehicle Controller
The CPM Lab vehicle controller turns a high-level driving request into low-level actuator commands for a muCar. In the source code it is still called the mid-level controller, because it sits between the high-level planner and the hardware interface on the vehicle.
The controller runs on the muCar itself. The installation and deployment of the vehicle computer are described in muCar, while this page focuses on the controller logic and the command types it accepts.
Controller overview
The controller sits in the middle of the CPM Lab driving stack and connects the planner, the vehicle observation stream, and the hardware interface.
---
config:
layout: elk
startOnLoad: false
---
flowchart TD
subgraph Second [Domain ID 21]
subgraph First [Vehicle]
direction TD
MC[Vehicle Controller]
HW[muCar hardware]
end
direction LR
IPS[IPS]
TRIG[System trigger]
end
subgraph Third [Domain ID 1]
direction TD
HL[Motion Planner]
end
BR[Bridge]
HL -->|command| BR
BR -->|command| MC
IPS -->|observation| MC
TRIG -->|trigger| MC
MC -->|motor_throttle, steering_servo| HW
MC -->|state| BR
BR -->|states| HL
Communication flow between the vehicle controller, planner, and hardware
It receives commands and observations, selects the active control mode, computes actuator outputs, and publishes the current vehicle state back to the rest of the system.
What the controller does, how it chooses a mode, and what the command types mean
At runtime the controller combines the current vehicle state, a vehicle command from the higher-level software, and system trigger messages such as ready, stop, or LED requests. The state includes pose, speed, IMU values, battery voltage, motor current, and the age of the latest IPS update.
The command type selects the control mode:
DIRECTmaps to direct actuator control,SPEED_CURVATUREmaps to speed and curvature control,PATH_TRACKINGmaps to geometric path tracking,TRAJECTORYmaps to time-parameterized trajectory tracking,INVALID(0) leaves the controller without a valid driving request; the vehicle ignores the command.
Each control cycle produces two actuator outputs:
motor_throttlein the range[-1, 1],steering_servoin the range[-1, 1].
The selected mode determines how those outputs are computed. Stop brings
the vehicle to a halt and centers the steering, so it also serves as the safety
fallback. Direct passes the requested throttle and steering through almost
unchanged before clamping them to the valid actuator range. SpeedCurvature
interprets the command as a desired speed and curvature, regulates speed with a
PI controller, and converts curvature into a steering servo command using a
fitted steady-state mapping. PathTracking follows a geometric path by
finding the closest point, computing lateral and heading errors, and applying a
Stanley-style steering law together with speed regulation. Trajectory uses
a time-parameterized trajectory and an MPC-based controller to predict the
future vehicle motion and track the reference trajectory.
Those values are then forwarded to the hardware layer that drives the muCar.
How the control loop works
The ROS2 node in mid_level_controller_node.cpp runs a periodic timer at
20 ms. In each cycle it:
---
config:
layout: elk
startOnLoad: false
---
flowchart TD
subgraph Second [Main Loop]
direction TD
STEP1[1. Update internal vehicle model]
STEP2[2. Compute control output]
STEP3[3. Publish current vehicle state]
STEP4[4. Publish simulated observations]
style STEP4 stroke-dasharray: 5
end
OUT[Actuator outputs]
OBS[Latest observation]
CMD[Vehicle command]
TRIG[System trigger]
STEP4 -.-> OBS
linkStyle 0 stroke-dasharray: 5
OBS --> STEP1
CMD --> STEP2
TRIG --> STEP2
STEP1 --> STEP2 --> STEP3 --> STEP1
STEP3 --> STEP4
STEP2 --> OUT
Main control loop of muCars
The node subscribes to:
/lab/vehicle_<id>/vehicleObservationfor localization and sensor updates,/lab/vehicle_<id>/commandfor driving commands,/lab/system_triggerfor ready, stop, and LED control.
It publishes:
/lab/vehicle_<id>/vehicleStatewith the current estimated state,/lab/system_triggerwhen the vehicle reports that it is ready,/lab/vehicle_<id>/vehicleObservationin simulation mode to emulate IPS updates.
Safety behavior
The controller contains a few safety checks that are important for users:
If a stop trigger is received, the vehicle is held in stop mode for
1.0seconds.If the IPS update age becomes greater than
3.0seconds while the vehicle is following aPATH_TRACKINGorTRAJECTORYcommand, the controller switches to stop mode, clears the active command, and holds the stop state for1.0seconds.All actuator outputs are clamped to the valid range
[-1.0, 1.0]before they are sent to the hardware layer.
This means that the controller is designed to fail safe: when localization is unreliable or a stop request arrives, it prefers to stop rather than continue driving.
Simulation mode
The same node can also run in simulation mode. In that case it initializes a simulated IPS source and can publish synthetic vehicle observations. This makes it possible to test the controller logic without a physical muCar, while still using the same ROS2 interfaces.