STEERING CENTER.
When you call rc.drive.set_speed_angle(speed, 0) the wheels should point dead ahead. Out of the box they usually lean a touch, because the steering linkage sits a few counts off true center. The fix is a single software trim, and you do not have to guess it: the car measures its own drift with the gyro and lidar, then prints the exact value to set.
HOW IT WORKS.
Your steering angle is normalized in the range [-1, 1] all the way through the drive pipeline. The controller node writes the per-tick command v <m/s> <deg> over USB-CDC to the ESP32, where the angle in degrees is what the servo eventually sees. Before that send, the controller adds one YAML value, steering_trim_deg, from config/controller.yaml. That value starts at 0.0. Nudge it a degree or two and the wheels at zero angle shift with it.
WHAT YOU NEED.
A straight line
Motor checked first
SSH to the Jetson
config/controller.yaml on the car. You edit it over SSH, covered in Networking.THE PROCEDURE.
bash# 1. SSH into the Jetson and park the car facing a wall, 2 to 6 m of clear floor. ssh racecar@neoracer # 2. Run the calibration lab. Flip the transmitter to autonomy when it says so. cd ~/jupyter_ws/neoracer-os/labs python3 lab_trim_cal.py # The car drives straight for up to three seconds, measures its heading drift # with the gyro and the distance with the front lidar, then prints: # # current steering_trim_deg: +1.0 measured correction: -2.6 # # === RUN THESE TWO COMMANDS, THEN RERUN ME === # # sed -i -E 's/steering_trim_deg: *-?[0-9.]+/steering_trim_deg: -1.6/' # ~/ros2_ws/install/neoracer_ros2_driver/share/neoracer_ros2_driver/config/controller.yaml # ~/ros2_ws/src/neoracer_ros2_driver/neoracer_ros2_driver/config/controller.yaml # sudo systemctl restart neoracer-teleop # 3. Paste those two commands, then run the lab again. # Repeat until it prints === CALIBRATED === (usually two or three passes).
VERIFY THE CENTER.
No tools handy, or want a second opinion? This holds the servo at center and the motor at zero so you can roll the car by hand along a metre of tape and watch the drift:
pythonimport racecar_core rc = racecar_core.create_racecar() HOLD_S = 8.0 timer = 0.0 def start(): global timer timer = 0.0 rc.drive.set_speed_angle(0, 0) print("Servo verify: holding straight, push the car 1 m along the tape") def update(): global timer timer += rc.get_delta_time() rc.drive.set_speed_angle(0, 0) # motor at zero, servo centered if timer > HOLD_S: rc.drive.stop() rc.set_start_update(start, update) rc.go()
A pass is finishing within a wheel's width of the line after a metre. A bigger gap means another nudge to the offset and a rebuild.
WHERE IT LIVES.
The trim is a YAML value the controller node reads on launch, so a teleop restart is what applies it. There is no colcon build to wait on. The commands the lab prints edit both copies of controller.yaml, the installed one the car runs and the source one, so the value survives a rebuild.
yaml# ~/ros2_ws/src/neoracer_ros2_driver/neoracer_ros2_driver/config/controller.yaml controller_node: ros__parameters: port_name: /dev/osrbot_base max_speed_mps: 6.0 max_steering_angle_deg: 30.0 steering_trim_deg: 0.0 # your trim, in degrees; positive steers left throttle_channel: 2 steering_channel: 0 mode_channel: 4
- A positive
steering_trim_degsteers the zero-angle wheels left, a negative one right. The calibration lab handles the sign for you. - The top-speed cap is
max_speed_mpson the same file; leave it alone if you only meant to trim the steering. - Every script that sends a steering angle of 0 now points true ahead, no per-program tweak needed.
