The Problem with Wheels in the Wild
In the aftermath of a disaster, environments are chaotic. Collapsed structures, rubble, and uneven terrain make it incredibly difficult for rescue personnel to navigate safely. Traditional wheeled or tracked robots, while excellent on flat surfaces, often fail the moment they encounter stairs, debris, or severe inclines.
Enter BOLT
To solve this, we developed BOLT (Bio-inspired Omni-terrain Locomotion Technology). BOLT is a fully untethered, 12-DoF quadruped robotic dog designed specifically for hazardous environment inspection, search-and-rescue operations, and agricultural monitoring. By replicating the dynamic walking mechanisms of four-legged animals, BOLT can step over obstacles and dynamically maintain its balance on uneven ground.
A Dual-Brain Architecture
Building a robot that can both walk and "think" requires a strict division of labor to ensure real-time stability:
- The Brain (High-Level): An NVIDIA Jetson Orin Nano sits at the core, handling all computationally heavy environmental perception. It processes 32,000 points per second from an RP LIDAR S2E for real-time Cartographer SLAM mapping, and runs edge-AI models on an Intel RealSense D555 camera to detect human survivors, check for PPE compliance, and identify agricultural pests.
- The Brainstem (Low-Level): A Teensy 4.1microcontroller handles the real-time motion control. It takes high-level directional commands, fuses them with data from a BNO085 IMU, and crunches the complex inverse kinematics (IK) math at 100 Hz. Thanks to the SPI absolute encoders on the ODrives, the Teensy instantly knows the robot's physical posture upon boot, allowing BOLT to automatically "home" and stand up from any folded position without manual zeroing.
Off-Grid Survival
Because disaster zones rarely have working cellular or Wi-Fi networks, BOLT operates entirely off-grid. Teleoperation is handled locally via a PS4 controller over a 2.4 GHz RF link. Furthermore, BOLT acts as a mobile relay in a decentralized LoRa Mesh Network (powered by Meshtastic). Whenever the Jetson's AI detects a human survivor, the system pairs that event with data from the onboard GPS Neo M8N, geo-tags the location, and broadcasts the coordinates across the LoRa network directly to the rescue team's trackers.
Hardware Components:
- 1x NVIDIA Jetson Orin Nano (or Super)
- 1x Teensy 4.1 Microcontroller
- 12x 90KV Brushless DC (BLDC) Motors
- 12x MKS ODrive Mini (FoC Drivers)
- 1x Intel RealSense D555 Depth Camera
- 1x RP LIDAR S2E
- 1x Adafruit BNO085 9-DOF IMU
- 1x u-blox NEO-M8N GPS Module
- 1x ESP32-based Meshtastic LoRa Node
- 1x SenseCAP Card Tracker T1000-E (for personnel tracking)
- 1x 6S 16000mAh Lithium Polymer (LiPo) Battery
- 1x 19V DC-DC Buck Converter (for Jetson)
- 1x 5V DC-DC Buck Converter (for Teensy/Logic)
- 1x PS4 DualShock Controller with 2.4 GHz USB RF Dongle
- 2x High-Current Emergency Kill Switches
- PLA++ and PLA+HS 3D Printer Filamen
- Carbon-fibre tubing (for central chassis spine)
- Various M3/M4 fasteners and bearings
- Seeed studio Mission Pack
Software Apps and Tools:
- Robot Operating System 2 (ROS 2)
- Google Cartographer (SLAM)
- Nav2 (Navigation Stack)
- ODriveTool
- Autodesk Fusion 360 (for CAD)
- Node-red
To walk, trot, and carry a massive 6S LiPo battery payload, a quadruped needs serious torque. Based on our static gravitational loading models for the leg links, the critical ABAD (abduction/adduction) joint requires approximately 16 Nm of torque to lift the robot and maintain dynamic locomotion.
Standard hobby servos simply cannot provide this without burning out. Instead, we engineered custom actuators by combining 90KV BLDC motors (rated at ~1.8 Nm pre-reduction) with custom-designed 9:1 planetary gearboxes. To drive them precisely, we are using 12 MKS ODrive Mini Field Oriented Control (FoC) boards.
Step 1.0: Software Environment Setup (ODriveTool)Before we can calibrate the motors, we need to set up the host computer to communicate with the MKS ODrive Mini. The ODrive ecosystem relies on a Python-based command-line interface called ODriveTool.
- Install Python and Pip: Ensure your PC (Windows, macOS, or Linux) has Python 3 installed.
- Install ODriveTool: Open your command prompt or terminal and run the following command to download the required packages:
pip install odriveUSB Driver Configuration (Crucial):
- If on Windows: You will likely need to use a tool called Zadig to change the ODrive's USB driver to
libusb-win32so Python can recognize it.
- for a more detailed explanation on windows installation go to : here
- If on Linux (or the Jetson): You must set up
udevrules to grant your user account permission to access the USB port without needingsudo. Run:
sudo bash -c "curl https://cdn.odriverobotics.com/files/odrive-udev-rules.rules > /etc/udev/rules.d/91-odrive.rules"
sudo udevadm control --reload-rules
sudo udevadm triggerLaunch the Tool: Connect your MKS ODrive Mini via USB, power it with a lab bench supply (set to 24V with a low current limit of ~2A for safety during initial setup), and type odrivetool into your terminal. You should see it connect and drop you into a Python prompt
Before assembling any mechanical parts, you must verify the electrical integrity of your motors and drivers. The MKS ODrive Mini requires precise calibration to measure the phase resistance, inductance, and encoder offsets of your specific motors.
We wrote a comprehensive Python script to run in ODriveTool. This script configures the baseline ODrive parameters, applies the required high-current limits (45A range) and aggressive PID gains for the hip/knee joints, and runs the full calibration sequence.
- Wire a single 90KV BLDC motor and its SPI absolute encoder (AS5047P) to the MKS ODrive Mini board.
- Connect the driver to your PC via USB and launch
odrivetoolin your terminal. - Copy and paste the following configuration script into the interactive terminal cell by cell.
# ── Cell 1: Imports ───────────────────────────────────────────────────────────
from math import pi
import time
# ── Cell 2: Base ODrive config ────────────────────────────────────────────────
odrv0.config.brake_resistance = 2.0
odrv0.config.enable_brake_resistor = True
odrv0.config.dc_bus_undervoltage_trip_level = 8.0
odrv0.config.dc_bus_overvoltage_trip_level = 56.0
odrv0.config.dc_max_positive_current = 25.0
odrv0.config.dc_max_negative_current = -8.0
odrv0.config.max_regen_current = 0.0
odrv0.config.enable_dc_bus_overvoltage_ramp = False
# ── Cell 3: CAN config ────────────────────────────────────────────────────────
odrv0.axis0.config.can_node_id = 12
odrv0.axis0.config.can_node_id_extended = False
odrv0.axis0.config.can_heartbeat_rate_ms = 100
odrv0.can.set_baud_rate(500000)
# ── Cell 4: Calibration lock-in ──────────────────────────────────────────────
odrv0.axis0.config.calibration_lockin.current = 10.0
odrv0.axis0.config.calibration_lockin.ramp_time = 0.4
odrv0.axis0.config.calibration_lockin.ramp_distance = pi
odrv0.axis0.config.calibration_lockin.accel = 20.0
odrv0.axis0.config.calibration_lockin.vel = 40.0
# ── Cell 5: Motor config ──────────────────────────────────────────────────────
odrv0.axis0.motor.config.motor_type = MOTOR_TYPE_HIGH_CURRENT
odrv0.axis0.motor.config.pole_pairs = 20
odrv0.axis0.motor.config.calibration_current = 5.0
odrv0.axis0.motor.config.resistance_calib_max_voltage = 2.0
odrv0.axis0.motor.config.current_lim = 30.0
odrv0.axis0.motor.config.current_lim_margin = 8.0
odrv0.axis0.motor.config.requested_current_range = 45.0
odrv0.axis0.motor.config.torque_constant = 0.0919
odrv0.axis0.motor.config.current_control_bandwidth = 2000.0
odrv0.axis0.motor.config.inverter_temp_limit_lower = 100.0
odrv0.axis0.motor.config.inverter_temp_limit_upper = 120.0
# ── Cell 6: Encoder config ────────────────────────────────────────────────────
# Onboard AS5047P — SPI absolute encoder
odrv0.axis0.encoder.config.mode = ENCODER_MODE_SPI_ABS_AMS
odrv0.axis0.encoder.config.cpr = 16384
odrv0.axis0.encoder.config.abs_spi_cs_gpio_pin = 7
odrv0.axis0.encoder.config.bandwidth = 1000.0
odrv0.axis0.encoder.config.calib_range = 0.02
odrv0.axis0.encoder.config.calib_scan_distance = 50.26548
odrv0.axis0.encoder.config.calib_scan_omega = 12.56637
odrv0.axis0.encoder.config.enable_phase_interpolation = True
# ── Cell 7: Controller config ─────────────────────────────────────────────────
odrv0.axis0.controller.config.control_mode = CONTROL_MODE_POSITION_CONTROL
odrv0.axis0.controller.config.input_mode = 3 # INPUT_MODE_POS_FILTER
odrv0.axis0.controller.config.pos_gain = 60.0
odrv0.axis0.controller.config.vel_gain = 0.20
odrv0.axis0.controller.config.vel_integrator_gain = 2.0
odrv0.axis0.controller.config.vel_limit = 50.0
odrv0.axis0.controller.config.vel_limit_tolerance = 1.5
odrv0.axis0.controller.config.input_filter_bandwidth = 20.0
odrv0.axis0.controller.config.enable_overspeed_error = False
odrv0.axis0.controller.config.enable_vel_limit = True
odrv0.axis0.controller.config.enable_current_mode_vel_limit = False
# ── Cell 8: Trap trajectory ───────────────────────────────────────────────────
odrv0.axis0.trap_traj.config.vel_limit = 20.0
odrv0.axis0.trap_traj.config.accel_limit = 20.0
odrv0.axis0.trap_traj.config.decel_limit = 5.0
# ── Cell 9: Startup flags — all off (manual calibration first) ───────────────
odrv0.axis0.config.startup_motor_calibration = False
odrv0.axis0.config.startup_encoder_offset_calibration = False
odrv0.axis0.config.startup_encoder_index_search = False
odrv0.axis0.config.startup_closed_loop_control = False
odrv0.axis0.config.startup_sensorless_control = False
odrv0.save_configuration()
print(">> Config saved. Rebooting...")
time.sleep(4)
# ── Cell 10: Motor calibration ────────────────────────────────────────────────
odrv0.axis0.requested_state = AXIS_STATE_MOTOR_CALIBRATION
time.sleep(10)
print("Motor calibrated: ", odrv0.axis0.motor.is_calibrated)
# ── Cell 11: Encoder offset calibration ──────────────────────────────────────
# !! Motor must be completely FREE TO ROTATE during this step !!
odrv0.axis0.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION
time.sleep(15)
print("Encoder ready: ", odrv0.axis0.encoder.is_ready)
# ── Cell 12: Enter closed loop control & Save ────────────────────────────────
if odrv0.axis0.encoder.is_ready and hex(odrv0.axis0.error) == '0x0':
odrv0.axis0.controller.input_pos = odrv0.axis0.encoder.pos_estimate
odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
time.sleep(2)
odrv0.axis0.motor.config.pre_calibrated = True
odrv0.axis0.encoder.config.pre_calibrated = True
odrv0.save_configuration()
print(">> Final config saved. Motor is ready for integration.")
else:
print(">> ERROR: Calibration failed. Check SPI wiring.")👉 Tip: Repeat this exact calibration process for all 12 motors individually before moving them to the mechanical assembly line. Ensure you change the
can_node_idin Cell 3 for each respective leg/joint (e.g., 0, 2, 4) to avoid network collisions later!
Step 1.2: 3D Printing the Planetary GearboxWe designed a custom planetary gearbox to keep the actuator profile compact while distributing the massive mechanical load concentrically across multiple gears.
- Export your CAD files (Sun gear, Planet Carrier, Ring Gear casing) to your slice
- Material Warning: You must print these components using PLA++ or PLA+HS. Standard PLA or PETG will likely strip or deform under the high shear stress of the 16 Nm output.
- Use high infill (80-100%) for the gear teeth and carrier pins.
Once the gears are printed, assemble the planetary gearbox. Press-fit the bearings, apply generous lithium grease to the gear teeth, and mount the BLDC motor directly into the rear of the casing.
- Connect the fully assembled mechanical actuator back to the MKS ODrive Mini.
- Use
ODriveToolto command the motor in velocity control mode at low speeds (e.g.,odrv0.axis0.controller.input_vel = 2). - Listen and observe: The gear mesh should be smooth. If you hear harsh grinding, notice the motor stalling, or see power spikes in the terminal, you have mechanical binding. Check your print tolerances, remove any elephant's foot from the 3D prints, or add more lubrication.
During our initial prototyping phase, we realized the original chassis design had a fatal flaw: it lacked a dedicated internal bay for the massive 6S 16000mAh LiPo battery. Strapping the battery to the top of the robot would dangerously raise the center of gravity (CoG), making dynamic balancing nearly impossible.
To solve this, we redesigned the torso around a central carbon-fibre spine with a low-slung, integrated battery compartment. This ensures structural rigidity while keeping the heaviest component as close to the ground as possible.
Step 2.1: Assembling the 3-DoF LegsEach leg consists of three Degrees of Freedom (3-DoF): Abduction/Adduction (ABAD), Hip Flexion/Extension, and Knee Flexion
- Proximal Actuator Placement: Mount your tested actuator modules (from Step 1) as close to the main torso as possible. The ABAD and Hip motors should be co-located at the shoulder joint, with torque transmitted to the knee via a belt or linkage. Keeping the heavy motors near the body reduces the swing inertia of the leg, allowing for faster, more efficient steps.
- Linkage Assembly: Connect the 3D-printed thigh and calf links. Ensure bearings are seated fully to prevent lateral play.
- Hardware Assembly: Bolt the linkages to the gearbox output shafts.
👉 Tip: Quadruped robots generate intense, high-frequency vibrations during locomotion. You MUST use blue threadlocker (Loctite 242) on all metal-to-metal bolts, especially on the motor shafts and linkages. If you skip this, your robot will literally shake itself apart during a trot gait.
Step 2.2: Single-Leg Validation (The "Sine Wave" Test)Before assembling all four legs and wiring the entire robot, you must validate your kinematics and CAN bus communication on a single leg. If one leg doesn't work perfectly, four certainly won't.
- Wire the CAN Bus Network (Teensy to ODrives):Unlike simple PWM or UART, CAN bus requires a specific "daisy-chain" physical topology and an external transceiver. The Teensy 4.1 has a built-in CAN controller, but it cannot generate the differential voltages required for the physical CAN network without a transceiver module (like the SN65HVD230).
- Wiring the Transceiver to the Teensy:
- Connect Teensy Pin 22 (CTX1) to the Transceiver TX pin.
- Connect Teensy Pin 23 (CRX1) to the Transceiver RX pin.
- Connect Teensy 3.3V and GND to the Transceiver 3V3 and GND pins. (Do not use 5V, as the Teensy logic pins are 3.3V only).
- The Daisy Chain (ODrive Connections): You must use twisted-pair wiring (like Ethernet cable pairs) for the CAN lines to reject the massive electromagnetic interference (EMI) generated by the BLDC motors.
- Route a wire from the Transceiver
CAN_Hto ODrive 1CAN_H. From that exact same terminal on ODrive 1, route a wire to ODrive 2CAN_H, and finally from ODrive 2 to ODrive 3CAN_H. - Repeat this exact continuous loop for the
CAN_Lline. Do not wire them in a star topology (where all wires meet at a central hub); it must be a single continuous line. - Common Grounding: The differential CAN signal will fail if the devices experience a ground loop. Ensure a signal ground wire connects the Teensy/Transceiver GND to the signal GND pins on all three ODrive Minis.
- Bus Termination (CRITICAL): A CAN network MUST have a 120-ohm resistor at both physical ends of the wire bus to prevent data signals from echoing back and corrupting the network.
Ensure your CAN transceiver module has a built-in 120Ω resistor (most breakout boards do).
Manually solder or crimp a 120Ω resistor across theCAN_HandCAN_Lterminals of the last ODrive in the chain (ODrive 3). - Run the Sine Trajectory: Upload a basic test script to the Teensy 4.1 that commands the leg to move through a continuous sine-wave trajectory in the air.
- Observe and Tune: Watch the physical movement. Is the leg lifting smoothly? If the leg fails to lift properly, or if the motors overheat quickly, you may have an imbalance in your torque distribution or your ODrive position/velocity gains (set in Step 1.1) are too aggressive. Tune them now.
Once your single-leg test is successful, replicate the leg assembly three more times (mirroring the CAD for the right side).
- The Carbon-Fibre Spine: Cut your carbon-fibre tubes to length. These serve as the primary load-bearing rails of the robot.
- Mounting the Torso Blocks: Slide and bolt the 3D-printed front and rear shoulder blocks onto the carbon-fibre rails.
- The Battery Bay: Attach the redesigned central belly pan. Slide the 6S LiPo battery into the compartment to verify clearance. Ensure the retention strap engages securely—a loose battery acting as a pendulum inside the robot will destroy your gait stability.
- Attach the Legs: Bolt all four validated 3-DoF leg assemblies to the front and rear shoulder blocks
A 6S 16000mAh Lithium Polymer (LiPo) battery is not a standard power supply—it is effectively a small welding machine. At full charge, it rests at 25.2V and is capable of dumping over 400 Amps of peak discharge current.
If a short circuit occurs, wires will vaporize instantly. Furthermore, the 12 BLDC motors generate massive voltage spikes (back-EMF) when braking. If the delicate logic boards (Jetson and Teensy) share an unprotected power line with the motors, they will be destroyed. Therefore, BOLT requires a strict, multi-tiered power architecture.
Step 3.1: The High-Current Motor TrunkYou must route raw battery power (22.2V nominal) directly to the 12 MKS ODrive Mini boards to minimize voltage drop and conversion losses.
- The Main Trunk: Cut two lengths of 10 AWG silicone wire (one red, one black) to run the length of the central carbon-fibre spine. Solder a high-current XT90-S (Anti-Spark) connector to the end that will mate with the battery. Do not use standard XT60s; they will melt under the 12-motor load.
- The Branches: At each shoulder/hip joint, strip a small section of the 10 AWG trunk. Solder 14 AWG silicone wire branches that route to the three ODrive Minis for that specific leg.
- Insulation: Wrap every exposed solder joint in heavy-duty heat shrink tubing.
- Brake Resistors: Ensure the included 2-ohm, 50W brake resistors are firmly wired into the
AUXterminals of every ODrive. These dissipate the back-EMF spikes as heat.
The NVIDIA Jetson Orin Nano requires a stable 19V supply, while the Teensy 4.1, CAN transceiver, and sensors require 5V.
- Jetson 19V Supply: Splice a 16 AWG wire from the main battery trunk into a high-efficiency DC-DC Buck-Boost converter rated for at least 65W. Tune the output potentiometer using a multimeter until it reads exactly 19.0V. Solder a barrel jack to the output to plug into the Jetson.
- Teensy 5V Supply: Power Teensy directly from jetson orin nano and we power it using usb so it can act as a power supply and data sharing way.
- Ground Isolation: Ensure the grounds (V-) of both buck converters are tied back to the main battery ground to maintain a common reference for the data lines.
👉 Tip: Mount the buck converters as far away from the ODrive motor drivers as physically possible. The electromagnetic interference (EMI) from the switching motor drivers can induce noise in your logic power lines.
Step 3.3: Dual Emergency Kill Switches (E-Stops)When a 15-20kg robotic dog starts thrashing due to a PID calculation error, you cannot safely reach underneath it to unplug the battery. You must have external, top-mounted kill switches.
- Motor E-Stop: Wire a heavy-duty, high-current battery disconnect switch (rated for at least 200A) directly inline with the positive (Red) 10 AWG trunk line. Place the physical red button on the top rear of the chassis. Pressing this kills power to the legs instantly.
- Logic Switch: Wire a smaller, standard toggle switch inline with the input to your buck converters. This allows you to reboot the Jetson and Teensy without having to initialize the massive motor drivers.
⚠️ WARNING: DO NOT PLUG IN THE JETSON, TEENSY, OR ODRIVES YET
- Plug the 6S battery into the XT90 connector.
- Turn on the Logic Switch. Use a multimeter to probe the barrel jack meant for the Jetson. It MUST read exactly 19V. Probe the wires meant for the Teensy. They MUST read exactly 5V.
- Turn on the Motor E-Stop. Probe the terminal blocks meant for the ODrives. They should read the exact current battery voltage (~24V).
- Turn everything off. Only now is it safe to plug the logic boards and motor drivers into their respective power cables.
With the power distributed safely, we face a fundamental robotics problem: The high-level autonomous brain (NVIDIA Jetson) thinks in 3D Cartesian space and publishes standard ROS 2 cmd_vel messages (e.g., "Walk forward at 0.5 m/s"). However, the MKS ODrives only understand raw joint angles (e.g., "Move motor 2 to 45 degrees").
To bridge this gap, we split the software stack. The Teensy 4.1 acts as the "Brainstem" running Inverse Kinematics (IK), while the Jetson handles the high-level ROS 2 Nav2 autonomy.
Step 4.1: The Brainstem (Teensy 4.1 IK Firmware)The Teensy 4.1 must calculate the exact joint angles for all 12 motors at 100 Hz to maintain a smooth trot gait, while simultaneously feeding telemetry back to the Jetson for the SLAM algorithms.
- Deploy the IK Math: Using the Arduino IDE or PlatformIO, upload your custom C++ quadruped firmware to the Teensy. This code contains the geometric matrix transformations that convert target Cartesian velocities into Abduction, Hip, and Knee angles.
- Integrate the IMU (Crucial for SLAM): Wire the Adafruit BNO085 9-DOF IMU to the Teensy via I2C or UART. Your Teensy firmware must do two things with this data:
Use the hardware-fused quaternion data internally to actively adjust the IK target planes (keeping the robot's body level on uneven ground).
Package the raw IMU data to be sent up to the Jetson. Cartographer will desperately need this data later to prevent the map from drifting. - Establish CAN Broadcasting: Ensure your code translates the calculated IK angles into CAN messages and broadcasts them to the 12 specific CAN IDs mapped to your ODrives.
The Jetson Orin Nano and the Teensy 4.1 need a robust, bi-directional data link. We opted for a hardwired USB Serial link.
- Connect a high-quality, shielded USB cable from the Jetson to the Teensy. (Do not use cheap power-only cables).
- The Downlink (ROS 2 to Teensy): On the Jetson, write a ROS 2 node (or use
micro-ROS) that subscribes to the/cmd_veltopic. It must pack these velocity commands into a lightweight serial packet (e.g.,<Vx, Vy, Yaw_rate>\n) and push it to the Teensy at a high baud rate (e.g., 500000). - The Uplink (Teensy to ROS 2): The Teensy must continuously push the BNO085 IMU data back up the USB cable. The Jetson node will read this serial packet and publish it to the ROS 2
/imu/datatopic at a high frequency (e.g., 200 Hz).
Before letting a 15-20kg robot try to walk on the ground, you must test the ROS 2 communication bridge and the new auto-homing sequence in the air.
- Prop it up: Place BOLT on a sturdy crate or stand so that all four legs are dangling freely. Ensure the legs are folded randomly to test the absolute encoders.
- Power Sequence: Turn on the Logic Power switch and let the Jetson and Teensy boot.
- The Auto-Home (WARNING): Keep your hands completely clear of the leg linkages. Turn on the high-current Motor E-Stop. The Teensy will read the absolute SPI encoders, calculate the difference between the current leg angles and the target IK "Standing" matrix, and smoothly command the ODrives to unfold the legs into the squared home position.
- Publish a Test Command: Once standing in the air, manually publish a ROS 2 velocity command from the Jetson terminal:
ros2 topic pub /cmd_vel geometry_msgs/Twist "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}" - Observe: The Teensy should instantly transition from the static home pose into the dynamic trot trajectory.
Before handing the keys over to the autonomous Nav2 stack, you must test BOLT's walking gait under manual control. We use a PS4 DualShock controller connected via a 2.4 GHz USB RF dongle (standard Bluetooth drops out too easily around high-power BLDC motors and carbon fibre).
- Hardware Connection: Plug the 2.4 GHz USB receiver into one of the NVIDIA Jetson Orin Nano's USB ports. Turn on the PS4 controller and ensure it pairs to the dongle.
- Install ROS 2 Joy Packages: On the Jetson, install the standard ROS 2 joystick drivers:
sudo apt install ros-humble-joy ros-humble-teleop-twist-joy - Configure the Node: Create a launch file that starts the
joy_node(which reads raw button/axis data from/dev/input/js0) and theteleop_twist_joynode. - Map the Axes: Configure the
teleop_twist_joyparameters to map the PS4 controller's analog sticks to the/cmd_veltopic:
Left Stick (Up/Down): Linear Velocity X (Forward/Backward Trot)
Left Stick (Left/Right): Linear Velocity Y (Lateral Strafe)
Right Stick (Left/Right): Angular Velocity Z (Yaw Turn) - The Ground Test: Place BOLT on the ground. Hold the "Enable" button on your PS4 controller (usually L1 or R1, required by
teleop_twist_joyfor safety) and slowly push the left stick forward. BOLT will take its first untethered steps!
Teleoperating a robotic dog is great, but true disaster-response robotics requires autonomy. To achieve this, BOLT doesn't just need to know how to move its legs; it needs to understand its environment, plot a course, and avoid dynamic obstacles without human intervention.
We achieved full autonomy using the ROS 2 Nav2 stack, fusing our 2D LiDAR data with high-frequency IMU telemetry, and relying on the Intel RealSense D555 for 3D depth perception.
While the RP LIDAR S2E is excellent for generating a 2D map, a walking quadruped experiences harsh, sudden rotational jolts that a wheeled robot does not. If you rely purely on LiDAR scan-matching, the map will tear or drift during a fast trot.
- Publish IMU Data: Ensure the Teensy 4.1 is actively publishing the BNO085's quaternion data to a ROS 2
sensor_msgs/Imutopic at a high rate (e.g., 200 Hz). - Fuse in Cartographer: Open your Cartographer
.luaconfiguration file. - Enable the IMU: Set
use_imu_data = trueandTRAJECTORY_BUILDER_2D.use_imu_data = true. - Cartographer will now use the IMU to calculate rotational gravity vectors between LiDAR scans, resulting in an incredibly crisp, drift-free occupancy grid even when the robot is shaking violently over rough terrain.
A 2D LiDAR only sees a thin "slice" of the world. It cannot detect a deep hole in the ground, nor can it see a low-hanging branch—both of which are catastrophic for a walking robot.
- Mounting: Secure the Intel RealSense D555 depth camera to the front of the carbon-fibre chassis, angling it slightly downward to capture the immediate terrain in front of the robot's front feet.
- Launch the ROS 2 Wrapper: Install the
realsense2_cameraROS 2 package on the Jetson Orin Nano. - Stream Depth Data: Configure the launch file to publish the
depth/color/pointstopic. This generates a dense 3D point cloud of the environment directly in front of the robot.
Now we tie the map and the vision system together using the Navigation 2 (Nav2) stack.
- The Global Costmap (LiDAR): Configure Nav2 to use the 2D map generated by Cartographer for its Global Planner. This allows the Jetson to calculate the most efficient route from point A to point B across the entire room.
- The Local Costmap (RealSense): This is where the magic happens. Configure the Nav2 Local Planner to subscribe to the RealSense D555 point cloud.
- Voxel Grid Setup: Set up a
VoxelLayerin your Nav2 parameters. This takes the 3D depth data from the RealSense and projects it down into the local costmap. - Behavior Verification: When you send a standard Nav2
MapsToPosegoal, BOLT will now walk toward the destination. If a person steps in front of the robot, or if there is rubble below the LiDAR's line of sight, the RealSense will detect it, flag that area as a lethal obstacle in the local costmap, and the Jetson will dynamically calculate a safe detour around it.
A disaster response robot is only as useful as the data it can send back to the rescue team. However, collapsed buildings and remote agricultural fields share one common trait: zero Wi-Fi or cellular connectivity.
BOLT cannot rely on cloud servers to process images or transmit alerts. All AI perception must happen on the edge (locally on the Jetson Orin Nano's GPU), and all critical alerts must be broadcast over an independent, decentralized radio network.
Step 6.1: Edge-AI Vision (The Intel RealSense D555)We already used the depth sensor of the D555 for obstacle avoidance. Now, we will use its high-resolution RGB sensor for object detection.
- Install Inference Software: On the Jetson Orin Nano, install a lightweight, GPU-accelerated inference engine like TensorRT or use the Ultralytics YOLOv8 library.
- Deploy the Models: Load your pre-trained AI models into the ROS 2 workspace. For BOLT, we focused on three specific detection profiles:
- Search & Rescue: Detecting human forms and faces in rubble.
- Industrial Inspection: Checking for PPE compliance (hard hats, high-vis vests).
- Agricultural Scouting: Classifying specific crop pests.
- Run the Node: Write a ROS 2 node that subscribes to the
/camera/color/image_rawtopic, runs the YOLOv8 inference, and publishes bounding box coordinates and confidence scores to a new/ai_detectionstopic.
When the AI detects a survivor, the robot needs to know exactly where it is in the real world (global coordinates), not just where it is on its internal SLAM map.
- Hardware Connection: Connect the u-blox NEO-M8N GPS module to one of the Jetson's UART ports (or via a USB-to-TTL adapter). Ensure the ceramic antenna has a clear view of the sky (mount it high on the carbon-fibre chassis).
- ROS 2 Integration: Use the
nmea_navsat_driverpackage to parse the standard NMEA sentences coming from the GPS module and publish them to/fix. - The Logic Bridge: Write a simple Python script: Whenever a high-confidence detection appears on the
/ai_detectionstopic, grab the latest latitude and longitude from the/fixtopic and package them into an "Alert String" (e.g.,ALERT: Human Detected at Lat: 11.75, Lon: 75.49).
To get the Alert String out of the disaster zone, BOLT acts as a mobile relay node in a Meshtastic LoRa mesh network.
- Hardware Setup: Flash an ESP32-based LoRa board (like a Heltec V3 or LILYGO T-Beam) with the standard Meshtastic firmware. Mount this module on BOLT and connect it to the Jetson via USB.
- Configure the Node: Use the Meshtastic CLI on the Jetson to set the node's role to
CLIENT_HIDDENorROUTER, ensuring it automatically relays packets from other rescue workers. - Serial API Communication: Write a Python script using the
meshtasticPython library. When the Logic Bridge (from Step 6.2) generates an Alert String, this script pushes that string over the serial port to the ESP32. - The Broadcast: The ESP32 instantly broadcasts the alert over the 868MHz/915MHz LoRa band.
- The Receiver: Rescue personnel wearing SenseCAP Card Trackers or carrying their own Meshtastic nodes paired to their smartphones will receive the alert and the exact GPS coordinates instantly, even if they are miles away without cell service.
USE THIS FOR DETAILED SETUP OF MISSION PACK AND RELATED TRACKERS :
HERE
Simulation environments like Gazebo are perfect; reality is not. In the real world, 3D-printed linkages flex under the 30kg weight, carbon fibre vibrates, and ground friction constantly changes.
Before BOLT takes its first fully autonomous steps outside, you must perform strict pre-flight checks and real-world PID tuning. Skipping these steps usually results in burned-out motors or shattered gearboxes.
Step 7.1: Pre-Flight Hardware VerificationMake this a checklist that you run through every single time you power the robot on.
- Battery Bounds: Check the 6S LiPo with a battery alarm or multimeter. It should read ~25.2V fully charged. Never attempt to run the robot if the battery is below 21.5V, as sudden current spikes from the motors will cause massive voltage sag, browning out the Jetson.
- Mechanical Fasteners: Manually wiggle every joint. If a thigh link feels loose, tighten it. Ensure the threadlocker (applied in Step 2) has fully cured.
- Wire Clearance: Physically move each leg through its maximum range of motion by hand. Ensure that no 14 AWG motor wires or CAN bus cables get pinched in the hip joints.
In early prototypes, quadruped robots had to be manually folded into a perfect "Zero" position before power-up so the IK math had a static reference. Because we properly configured the SPI Absolute Encoders (AS5047P) back in Step 1, BOLT's Teensy always knows exactly where its legs are the millisecond it boots.
- Set it Down: Place BOLT on the ground. It doesn't matter if its legs are splayed out or tucked underneath it; the absolute encoders retain their positions through power cycles.
- Boot the Brains: Turn on the Logic Power switch. Wait ~45 seconds for the Jetson Orin Nano to initialize the ROS 2 stack and establish the serial bridge with the Teensy.
- Engage Actuators: Turn on the high-current Motor E-Stop switch.
- The Stand-Up Sequence: Send the initialization command via your PS4 controller or ROS terminal. The ODrives will enter
AXIS_STATE_CLOSED_LOOP_CONTROL. You will hear the motors whine as the Teensy smoothly interpolates the leg angles from their current rested state into the nominal standing posture. BOLT will dynamically push itself off the ground and lock into a rigid stance, ready for Nav2 commands.
During early testing, you might encounter a "Single-Leg Lift Failure." The robot's IK calculates the math to lift a leg, but the physical leg just drags on the ground. This happens because the ODrive's Position PID loop isn't aggressive enough to overcome the robot's physical mass and ground friction.
- Increase
pos_gain: Connect to your ODrives viaODriveTool. Slowly increase thepos_gain(e.g., from 60.0 to 80.0). This tells the motor to fight harder to reach its target angle. - Adjust
vel_integrator_gain: If the leg gets close to the target but stops just short (steady-state error due to gravity pulling it down), slightly increase the velocity integrator gain to force it over the line. - Tune Stride Height: In your Teensy IK firmware, increase the Z-axis clearance (step height) parameter by a few centimeters. A higher step clears uneven ground better but requires more torque. Find the balance.
It is time.
- Take BOLT outside to a flat, open area (like a parking lot or a packed dirt trail).
- Launch the full ROS 2 stack from your laptop via SSH over the Jetson's local Wi-Fi hotspot.
- Verify that Cartographer is building the map and the RealSense is publishing the 3D obstacle point cloud.
- Open RViz, click "2D Goal Pose", and select a destination 10 meters away.
- Watch as the Nav2 stack generates a path, the Jetson streams velocities down the USB cable, the Teensy calculates the IK, the ODrives spin up, and BOLT walks completely autonomously!
While BOLT is currently a highly capable autonomous scout, a disaster response robot is fundamentally limited if it can only look at a scene but cannot interact with it.
Our primary engineering focus for Phase 3 is the design, fabrication, and software integration of a 5-DoF Robotic Manipulator—effectively giving BOLT a "5th limb" to open doors, move lightweight rubble, and deliver medical payloads directly to trapped survivors.
Adding an arm to a wheeled rover is relatively simple; adding an arm to a dynamically balancing, walking quadruped introduces a cascade of severe mechanical and control challenges. Here is our technical roadmap for making it happen:
1. Mechanical Design and Hardware SelectionThe arm must be strong enough to lift a 2kg payload at full extension, but light enough not to crush the quadruped's front suspension.
- Actuation: We cannot use the massive 90KV BLDC motors used in the legs. We plan to use highly compact, quasi-direct drive actuators (like the unhoused MIT Cheetah-style pancake motors) for the shoulder and elbow joints to maintain high torque density with minimal weight.
- Chassis Integration: The manipulator will be mounted directly to the front-center of the carbon-fibre spine. We are designing a reinforced 3D-printed PLA-CF (Carbon Fiber infused PLA) baseplate to distribute the twisting torsional forces of the arm across the main rails.
- End Effector: We will deploy a compliant, 2-finger underactuated gripper driven by a single high-torque servo, prioritizing grip reliability over complex finger dexterity.
This is the hardest control challenge of Phase 3. A quadruped balances by keeping its Center of Gravity (CoG) within its support polygon (the geometric shape formed by its feet on the ground).
- The Danger: If BOLT extends a 2kg payload forward on a 0.5-meter arm, the robot's CoG will shift drastically past its front feet, causing it to face-plant instantly.
- The Solution (Dynamic Whole-Body Control): We will rewrite the Teensy 4.1's Inverse Kinematics firmware to treat the robot not as a 12-DoF system, but as a unified 17-DoF system (12 leg joints + 5 arm joints). As the arm extends forward, the Teensy will autonomously calculate the CoG shift and command the quadruped to dynamically bend its rear knees and shift its hips backward—effectively acting as an active counterweight, exactly like a human leaning back while holding a heavy box.
To control the arm autonomously, we will leverage the Jetson Orin Nano's processing power.
- Motion Planning: We will integrate the MoveIt 2 framework into our ROS 2 workspace.
- Vision-to-Grasp: By utilizing the Intel RealSense D555 depth camera already mounted on the front deck, we will generate 3D collision meshes of the environment. When the YOLOv8 model detects a target object (e.g., a medical kit or debris), MoveIt 2 will calculate a collision-free trajectory for the 5-DoF arm to reach out and grasp it, while simultaneously telling the Nav2 stack to step the robot closer if the object is out of reach.
This project was a massive collaborative effort. A huge thank you to the core development team: Abhinav Krishna N, Achinth PM, Annu Johns, and Sapnil M.
We also want to extend our deep gratitude to the Department of Electronics & Communication Engineering at Mar Athanasius College of Engineering (MACE) for their continuous support, lab space, and academic guidance throughout this build.
🛠️ Build Your Own!All of our CAD files, ROS 2 workspaces, and Teensy firmware are open-source and linked above. If you decide to build your own BOLT, or if you want to help us solve the Whole-Body Control equations for the upcoming robotic manipulator, please share your thoughts in the comments below!
















_Jpb9vQDRzx.png)
_t9PF3orMPd.png?auto=compress%2Cformat&w=40&h=40&fit=fillmax&bg=fff&dpr=2)








Comments