OPEN SOURCE DEEP DIVE
Bimo: a 45 cm Open-Source Hip-Head Biped Trained in Isaac Lab and Distilled onto an RP2040
Bimo is a 45 cm, roughly 1.6 kg hip-head biped kit: eight STS-3215 bus servos, a BNO08x 9-DoF IMU, four VL53L0X rangefinders, two 180-degree cameras, and a custom RP2040 board closing a 20 Hz control loop. The repository publishes the Python control API (1023 lines), three MCU firmware builds (1349 lines), a ROS2 wrapper (1431 lines) and an Isaac Lab training environment (874 lines: six reward terms, dense domain randomization, and a system-identified STS3215Actuator with 3.113 Hz bandwidth, directional gear backlash and a 5 ms bus delay), plus a 26MB Bimo.usd model. The CPG gait's 104 Fourier coefficients and per-joint amplitude gains are hardcoded in both Python and C, and a [256,128,64] PPO teacher distills into a [64,32] student that goes through onnx2c into the firmware, so the robot walks untethered on the MCU. All code is Apache-2.0, but the CAD and electronics the README promises are still marked coming soon: no STL, STEP, schematic, gerbers or BOM exist in the working tree or in git history, and neither release ships pretrained weights (zero assets) or a DIY assembly manual. Currently v1.1.0 with 198 stars, in pre-order status.
A 45 cm hip-head biped
Bimo is a bipedal robot kit from Spanish developer Mykhaylo Ilyin (Mekion). It targets a specific friction: people who want to research bipedal walking usually have to spend serious money on hardware first, or wade through a large simulation stack before touching anything physical. Bimo compresses both into a 45 cm tall, roughly 1.6 kg machine driven by eight STS-3215 bus servos on a custom RP2040 board. The whole repository is 60 files and about 4,680 lines of code, all under Apache-2.0.
Structurally it is a hip-head biped: three joints per leg (hip, knee, ankle), plus two shoulders and a head joint. The head is a hollow cavity with four M3 mounting points and a rated 1 kg payload capacity, though adding that payload means retuning CPG gains or retraining the RL policy. Two form factors ship: a fully assembled SLS kit, and a DIY edition you print and assemble yourself. The gait, firmware, simulation environment and control API are identical for both.
Specs and the custom RP2040 board
The most interesting row in the spec table is not the dimensions but where the compute lives. The same robot can compute on a tethered PC, on an SBC tucked into the head, or with no external compute at all, letting the RP2040 close its own 20 Hz control loop. Those three modes correspond to three different firmware builds in the repository.
| Item | Specification |
|---|---|
| Height / weight | 45 cm / about 1.6 kg with no payload |
| Payload | Up to 1 kg head-mounted (requires CPG gain retuning or RL retraining) |
| Actuators | 8 × STS-3215 bus servos, 12V, 2.943 Nm stall torque, 4.712 rad/s no-load speed, 1:345 gearbox |
| Sensors | BNO08x 9-DoF IMU, 4 × VL53L0X time-of-flight rangefinders, 2 × 180° FOV cameras |
| Controller | Custom RP2040 board, SBC-compatible through data and power connectors |
| Comms / power | USB 2.0, serial at 921600 baud; 9–13V input, banana-plug adapter included, sold without a battery |
| Control loop | 20 Hz (PERIOD = 50 ms in firmware) |
| Telemetry | 118-byte packed StateData struct: IMU quaternion, 4 range readings, position/speed/load/voltage/current/temperature for all 8 servos, system voltage, RP2040 internal temperature |
Both cameras run fixed 30 fps MJPEG. initialize() defaults to 1280×720, and the whitelist offers eight resolutions down to 320×240; capture_image("front"|"top") returns a single BGR numpy frame. Pre-programmed routines are time-based sequences stepping every 50 ms, matching the 20 Hz control rate.
Wiring is documented closely in MCU/README.md: the rangefinders connect in left, front, back, right order on four-pin 3V3/GND/SDA/SCL headers, and the docs explicitly warn that GPIO 2/3 are taken by the IMU and GPIO 4/5 by the I2C mux, so check addresses before adding anything. To run untethered on battery, you feed the RP2040 through the board's buck converter on the DATA port.
Three ways to make it walk
The repository sorts "get it walking" into three tiers by setup cost, and that layering is one of the more practical design decisions here.
Tier one is the built-in CPG, which works immediately with no training. Tier two is the baseline RL policy: train the walking model in the included Isaac Lab environment, then deploy it with the inference loop in BimoAPI/examples/nn_walk.py. Tier three is fully custom: change the reward, change the domain randomization, add payload support, and train your own.
Bimo walking. Both the CPG gait and the RL policy drive forward and lateral motion. The v1.1.0 status note is precise about which is which: the CPG model walks omnidirectionally, while the RL policy has achieved sim-to-real for forward walking only. On pure locomotion capability the CPG is currently the more complete of the two.
The CPG is not a neural network. It is a six-harmonic Fourier-series gait approximation. In BimoAPI/bimo/cpg.py, K = 6 gives 13 coefficients per joint (a0, a1, b1 through a6, b6), so 104 floats across eight joints, hardcoded straight into the source, with a per-joint amplitude correction layer on top: AMP_GAIN of 1.7 at the hips, 1.0 at the shoulders, 2.0 at the knees, 1.5 at the ankles. The README explains where such numbers come from — you can refit a CPG by running a Fourier series over a recorded joint trajectory, including one captured from a newly trained RL policy.
| Index | Joint | Amplitude gain | Position limits (deg) |
|---|---|---|---|
| 0 / 1 | Right / left hip | 1.7 | -90 to 90 |
| 2 / 3 | Right / left shoulder | 1.0 | -12 to 90 / -90 to 12 |
| 4 / 5 | Right / left knee | 2.0 | 0 to 140 |
| 6 / 7 | Right / left ankle | 1.5 | -93 to 93 |
The standing base pose is [-30, -30, 0, 0, 60, 60, 30, 30] degrees: hips back 30, knees bent 60, ankles dorsiflexed 30 — a conventional crouched ready position. Turning is handled plainly: bias the two hip joints in opposite directions on top of the straight-walk offset CENTER_VALUE, with MAX_TURN capping how aggressive that bias can get. cpg_walk.py derives the bias automatically in proportion to heading deviation to hold a straight line; cpg_walk_keyboard.py overrides it from Q/E key state. The docs note that CENTER_VALUE is a per-robot calibration constant, so a build that drifts while walking straight needs it adjusted.
On the Python side, the Bimo class hides the hardware cleanly: initialize() connects the MCU and both cameras, moves to sit, and calibrates IMU offsets; perform("stand") and perform("sit") run built-in routines; request_state_data() unpacks the telemetry struct, reconstructing system voltage as 9 + raw/65535 × 4 over the 9–13V range; send_positions() pushes eight joint angles straight to the servos. routines.py supplies the sit and stand sequences and lets you register your own via add_routine(). One warning in the README carries real field experience: the example sends a standing pose right after sitting down, and getting that order backwards launches the robot off its own feet.
The Isaac Lab environment is the crown jewel
Judged by engineering density, the 874 lines under IsaacLab/ are Bimo's most valuable asset, and they ship with a 26MB Bimo.usd model containing meshes and physics prims, so it drops straight into Isaac Lab 2.3.2 for training.
The environment is written against DirectRLEnv: physics dt = 0.005 s with decimation = 10, so the policy emits an action every 50 ms, exactly matching the real robot's 20 Hz loop. Episodes run 10 s. The observation is only 11-dimensional — 3 IMU orientation angles (roll/pitch/yaw, Gaussian noise σ=0.015, normalized to [-1,1]) plus the 8 previously commanded joint angles, all rounded with torch.round(..., decimals=4). Actions are incremental: the network output is clipped to [-3,3], scaled by 4/3, accumulated onto the current command, then clamped into joint limits and perturbed with σ=0.5° actuator noise. Deliberately absent from the observation are linear velocity and measured joint position, which means everything the policy sees can be reconstructed on hardware from the IMU plus the robot's own last command. That is precisely what makes sim-to-real work here.
| Reward term | How it is computed |
|---|---|
| Orientation | 1 − √(sum/0.95) while |roll| + |pitch| + 0.6|yaw| ≤ 0.95, else a flat −1. The yaw softening factor went from 0.5 to 0.6 in v1.1.0 |
| Body height | Ideal 0.381 m, max deviation 0.3 m, linearly mapped to [0,1] |
| Joint position | Deviation from the standing base pose with per-joint ceilings [90,90,90,90,75,75,90,90]°, 1 − √ then mean, mapped to [−1,1] |
| Foot clearance | Target 2 cm, exponential decay with scale=150; zero reward if both feet are airborne or both are planted |
| Forward velocity | Parabolic tracking: 1 − ((vx − 0.06)/σ)² with σ = 0.03. Target speed was lowered from 0.1 to 0.06 m/s in v1.1.0 |
| Lateral deviation | Distance off the spawn Y axis, 1 − |Δy|/0.2 mapped to [−1,1]. Forward progress no longer lives here at all |
All six weights are 1 and get normalized by their sum at runtime. Termination is equally blunt: body height below 0.1 m, or either |roll| or |pitch| past 0.95 rad (about 54°).
Where the engineering effort really shows is actuators/STS3215.py. Version 1.1.0 dropped Isaac Lab's idealized DCMotorCfg entirely and replaced it with an explicit actuator subclassing ActuatorBase directly, modeled on system-identified dynamics of the physical servo. Every constant carries a note on how it was measured.
| Parameter | Value | Origin |
|---|---|---|
| Stall torque | 2.943 Nm | 30 kg·cm at 12V through a 1:345 gearbox |
| Stiffness | 57.386 Nm/rad | k = Irefl·ωn², ωn = 2π × 3.113 Hz, measured with servo firmware P=30 |
| Damping | 1.339 Nm·s/rad | c = 2ζ·Irefl·ωn, ζ ≈ 0.228 fit from step-response decay, D=35 |
| Coulomb friction | 0.044 Nm | m·g·lCoM·sin θhold with lCoM = 0.19 m, measured at the hip |
| Bandwidth | 3.113 Hz | System identification. The driving position is a first-order lag filter of the command, α = 1 − e−2π·f·dt |
| Backlash | 1.0 to 2.4° | Measured on the real robot. A directional backlash operator tracks a gear contact point, so the deadband only appears right after a reversal, and the band width is resampled per reversal |
| Bus delay | 1 physics step ≈ 5 ms | Request + inference + action-execution round trip, fixed rather than randomized |
Domain randomization is layered on densely. Link mass scales uniformly ±5% with inertia recomputed; terrain keeps only the flat subtype but with ±1 mm height-field noise on top of ground friction (static 0.6, dynamic 0.5); each environment gets its own TPU foot-pad material with static friction drawn from 0.3 to 0.7 (dynamic 0.1 lower), restitution 0 to 0.05, compliant contact stiffness 5e4 and damping 8e2; the head takes a velocity-zeroing push every 2 to 4 s; and stall torque cycles through 24 levels between 2.7 and 2.94 Nm, standing in for a 10 to 12V bus sagging under load.
Training config lives in agents/rsl_rl.py: BimoPPORunnerCfg uses [256,128,64] actor and critic hidden dims, learning rate 3e-3, 16 steps per env, 500 iterations. With 2048 parallel environments headless, the README quotes 5 to 6 minutes to a usable policy. PPO and distillation are registered as two independent Gym task IDs, so switching between them is a --task flag.
Distilling to the MCU: untethered walking
This is the most complete loop in the project. Train the PPO teacher ([256,128,64]), then run the Bimo-Distillation task to train a [64,32] student to imitate it, with observation normalization on both sides. The play script exports ONNX into the bimo_ppo_rsrl directory automatically, and onnx2c turns that small ONNX into C, which compiles straight into micro_bimo_nn.ino.
Bimo demonstrating motion skills: built-in routines and gait transitions.
In the generated firmware you can see the signature entry(const float tensor_obs[1][11], float tensor_actions[1][8]), matching the environment's 11-dim observation and 8-dim action exactly. The firmware reimplements what the host normally does — IMU pitch and roll offset calibration, the stand-up routine, heading lock — because there is no host present anymore. It splits work across both cores via pico/multicore and deletes the host comms protocol outright. The companion micro_bimo_cpg.ino uses the same standalone structure but drives the walk from a C array CPG_COEFFS[8][13], whose numbers are identical to the Python cpg.py table, with no distillation and no onnx2c step in between.
Storing the same gait twice, in Python and in C, means any change has to be made in both places. In exchange, the whole 20 Hz loop runs on a few-dollar RP2040. Carrying sim-to-real all the way down to a microcontroller is rare in small robot projects.
The ROS2 wrapper: one hardware owner
ROS2/ is new in v1.1.0, at 1431 lines. The design admits exactly one node, bimo_comms, as the holder of the hardware: it is the only process touching /dev/ttyACM* or /dev/video*, and everything else in the graph arrives through topics and services. It subscribes to /bimo/cmd_action, publishes /bimo/state, and exposes eight services: alive, perform_routine, add_routine, get_routine, calibrate, lock_heading, unlock_heading and capture_image.
Arbitration runs on a priority worker queue (queue.PriorityQueue). Action commands outrank the periodic state poll, so a generator node's output never waits behind a routine sensor read. Long-running operations such as perform_routine and calibrate take a separate callback path to keep the node responsive, and any cmd_action arriving while they execute is dropped with a logged warning rather than applied late. The CPG walker and ONNX walker in examples/ are explicitly positioned as reference implementations, not as the sanctioned way to drive the robot.
How thorough is the open source, really
This needs a split answer, because the README's promises and the repository's current contents do not fully line up.
On the software side it is thorough. All 60 files carry SPDX headers, there are three LICENSE files (root plus both ROS2 packages), and everything is Apache-2.0: the control API, all three firmware builds, the ROS2 wrapper, the Isaac Lab environment, the actuator model, the PPO and distillation configs, and the 26MB Bimo.usd robot model. The gait itself is fully public too — the 104 Fourier coefficients and per-joint gains are readable in both Python and C, and on the RL side the rewards, termination conditions, domain randomization and actuator physical constants are all in the repo. Reproducing or modifying the walk requires nothing from the author.
| Category | Status | What is in the repository |
|---|---|---|
| Control API / firmware / ROS2 | Open | BimoAPI 1023 lines, MCU 1349 lines, ROS2 1431 lines, all Apache-2.0 |
| Locomotion | Open | CPG Fourier coefficient table in both Python and C, plus Isaac Lab's 874 lines (rewards, DR, actuator model, PPO and distillation) |
| Simulation model | Open | IsaacLab/bimo/assets/Bimo.usd, 26MB, with meshes and physics prims |
| Mechanical CAD | Not released | README line 20 says literally "CAD files (coming soon)". No STL, STEP, F3D or 3MF in the working tree or anywhere in git history |
| Electronics | Not released | No schematic, KiCad project, gerbers or BOM. The only hardware artifact is assets/pcb.png, a pinout render |
| Pretrained weights | Not provided | No policy.onnx or checkpoints. Both releases (v1.0.0 and v1.1.0) carry zero assets, so nn_walk.py needs weights you trained yourself |
| DIY assembly manual | Not released | Marked "DIY Manual (coming soon)" in the README |
So the License section's claim that "All code and CAD designs are, and will, be released under the Apache 2.0 License" is forward-looking: the code is there, the CAD is not. The repository advertises a DIY edition you print and assemble yourself, but with no drawings available that path does not currently work, and the practical entry point is the pre-order at mekion.com/product. The accurate framing is that Bimo is today a project with fully open software and algorithms, and unreleased hardware design.
How much that gap matters depends on what you want from it. For gait research, for validating sim-to-real methodology, or for reading a well-annotated small-robot actuator identification, what is published is already sufficient, and arguably more complete than most comparable projects, many of which release the policy but not the actuator model. If you want to build the whole machine from scratch, you have to wait for the CAD and electronics.
Project status
Current release is v1.1.0 (2026-09-01), last push 2026-09-03, 198 stars and 8 forks. The status note is candid: the CPG model walks omnidirectionally and is stable, the RL policy has achieved sim-to-real for forward walking, and the project is in pre-order, with kits shipping once the pre-order threshold is reached, followed by CE/FCC certification and fulfillment. Partners listed are EOI (Escuela de Organización Industrial) and CNC machining supplier JLCCNC.
The CHANGELOG deserves a read on its own, because it records honest defect fixes rather than only additions. Version 1.1.0 fixed actions winding up past joint limits because cmd_actions accumulated the policy delta each step with no clamp, fixed _reset_idx() indexing base_pose[0] instead of base_pose[env_ids], and fixed last_position leaking the previous episode's endpoint into a freshly reset environment. The velocity reward was reworked from a 30/70 blend of forward speed and yaw penalty into a single parabolic tracking term, the deviation reward stopped rewarding forward progress, and terrain was simplified from mixed rough and slope to flat only. For a small project still in pre-order, a changelog that spells out what was broken is worth more than a line reading "performance improved".