Sean's Engineering Portfolio

Todos + Current Issues (for quick personal reference)

Controlled torque pulses with live telemetry of control input and gyroscope measurement.

Overview

github: https://github.com/slin1923/cublii

updated 9/16/2026

CUBLI

Cubli is the first of my projects for which I will use my portfolio as running documentation for. Cubli is inspired by the project of the same name developed at ETH Zurich by the Institute for Dynamic Systems and Control. Using a set of 3 reaction wheels, this robot should be able to balance on a corner. While on a corner, it should also be able to slowly rotate cw/ccw. ReM-RC’s video demonstrates what this balancing should look like, though he does not track yaw angles. As someone who wants to fly satellites, I figured this project was a decent way to get my hands dirty and get some fun ADCS and firmware experience under my belt.

The emphasis of this project is solely on dynamics, state estimation, and controls. I want to give a HUGE thank you to ReM-RC for open-sourcing the hardware for this awesome project. His version is fundamentally similar to the research-grade hardware of ETH Zurich, except redesigned with cheap, accessible COTS parts. All of the part files and schematics for his project you can find on his github repo. I did make a few hardware modifications to this project which you can find on my repo.

I didn’t just follow ReM-RC’s documentation and work instructions to a T. That would make this project meaningless and no more of an exercise in engineering than a lego set is. I am building custom controllers. A quick glance at RemRC’s controller firmware shows that he used a simple, effective, model-free, and well-tuned proportional controller on both gyroscope and accelerometer readings. While that certainly works, I’m an idiot and intentionally want to shoehorn in advanced controllers. I want to model the dynamics of the cube and at least TRY to implement literally every type of controller I know: P, PI, PID, pole-placement, LQR, MPC, H-$\infty$, Adaptive Control, hell if I design a rig that automatically stands the cube back up to its equilibrium point even a RL policy is on the table! Such ambition… get to work!

When completed, I want this to be my magnum opus personal project. Unlike many of my other posts, the intended audience for this post are people in the robotics/GNC field and contains a lot of deep technical concepts sans explanation.

Mechanical Parts

ReM-RC designed 90% of my cube. Annoyingly, all his original Thingiverse files are already non-parametric .STLs. I reverse-designed them to be parametric Onshape files :). I also designed some additional parts of my own you can find below.

If you have an Onshape account (free), you can export, or copy + modify any of the parts below.

Most parts FDM printed in PLA and joined via heatset threaded inserts. Larger flat plate-like parts laser cut out of aluminum.

some of my custom parts

Here’s a quick gif to appreciate ReM-RC’s design: the flywheel runs clear and true

Flywheel face of cubli demonstrating flywheel clearance spin

Electronics

Again, credit to ReM-RC for hardware scoping and design. Also, a quick shoutout to oshwlab user malebuffy for design of the cubli breakout board and saving me the headache of soldering a thousand pins myself on a DIY PCB. Manufactured by JLCPCB.

Cubli breakout board. On the right was the shit I had to deal with my first iteration
Some pictures of my hardware and where they sit inside the fully assembled cubli

Firmware

This section onward is where my own work truly begins and I diverge from ReM-RC.

In the past, I always flashed to ESP32s using the USB cable and also used the same cable as my data connection for Serial. I would have gone crazy if I needed to do that for Cubli. Not only because I iterate and flash frequently, but more critically because I would need IMU data streamed back to me without the mechanical attachment of a cable affecting Cubli’s dynamics. Platform.IO is a way to flash over wifi and mosquiTTo is a way to publish and subscribe to OTA messages (not dissimilar to ROS).

Platform.IO and mosquiTTo reminders

The workflow for OTA flashing

  1. Open the project in Platform.io (the folder must contain a file platformio.ini)
  2. Make sure platformio.ini contains at least the following
    [env:esp32dev]
     platform = espressif32
     board = esp32dev
     framework = arduino
    
     # comment the next two lines to upload via USB
     upload_protocol = espota
     upload_port = 192.168.0.220
    
  3. compile and upload (check mark and arrow in bottom left of VSCode Window)

The workflow for OTA IMU data readout

  1. Open a terminal and run & "C:\Program Files\mosquitto\mosquitto.exe" -c "C:\mosquitto-config\mosquitto.conf" -v. All this does is start mosquiTTo
  2. Open a new terminal and run & "C:\Program Files\mosquitto\mosquitto_sub" -h localhost -t esp32/accel for live IMU readouts.
  3. ALTERNATIVELY, run python log_imu.py and then run python analyze_imu.py if running a proper system ID test.

Reminders

Modeling and Dynamics

This is where the fun begins. A lot of dynamic modeling is analytical derivation. Full derivations can be found on the document linked below. On this page you will find key design decisions and results. Another large chunk of modeling is empirical, and involve experimental design and data collection. You will also find those experiments and their results here.

Nomenclature and Values (for quick reference)

Experimental Values (Cubli intrinsics)

Analytical Variables

Frames of Reference

I defined 4 frames of reference for this project. All are outlined below. All are right-hand convention cartesian.

The frames of reference for Cubli (side view). Flywheels shown in gray for reference

A few extra notes for clarity

Desired State-Space Realization

At the center of EVERYTHING for cubli is its state space (SS) realization. Let’s rip the band aid off.

\[\begin{align*} x &:= [\theta, \phi, \psi, \dot{\theta}, \dot{\phi}, \dot{\psi}, \tau_1, \tau_2, \tau_3]^T \in \mathbf{R}_{9\times 1}\\ y &:= [a_x, a_y, a_z, g_x, g_y, g_z]^T_{AF} \in \mathbf{R}_{6\times 1}\\ u &:= [\tau_{1c}, \tau_{2c}, \tau_{3c}]^T_{TF} \in \mathbf{R}_{3\times 1}\\ \dot{x} &= [A_{9\times 9}]x + [B_{9\times 3}]u\\ y &= [C_{6\times 9}]x + [D_{6\times 3}]u \end{align*}\]

Getting the easy out of the way first, the definition for $y, u, A ,B, C, D$ should not require too much explanation.

Justifying my choice of state vector definition $x$ requires deeper clarification and rationale.

Dynamics Model $A, B$

Now let’s derive $A$ and $B$ matrices. This is where I hide all the algebra, rotation matrices, and partial derivatives in my pdf document and spare you the eyesore. In short, the process is

  1. Convert Euler Angle rates $[\dot{\theta}, \dot{\phi}, \dot{\psi}]$ to a body-frame angular velocity $\omega_{O’}$.
  2. Set up and solve the resulting Lagrangian Mechanics problem which will yield a set of equations where $\ddot{\theta} = f(x)$. And likewise for $\ddot{\phi}$ and $\ddot{\psi}$.
  3. Take a small-angle approximation on $\theta, \phi$.
  4. Jacobian linearization about ($\theta = \phi = \dot{\theta} = \dot{\phi} = 0$)

The final system dynamics written out explicitly is

\[\begin{bmatrix} \dot{\theta} \\ \dot{\phi} \\ \dot{\psi} \\ \ddot{\theta} \\ \ddot{\phi} \\ \ddot{\psi} \\ \dot{\tau_1} \\ \dot{\tau_2} \\ \dot{\tau_3} \end{bmatrix} = \begin{bmatrix} 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0\\ \frac{mgr}{I_1} & 0 & 0 & 0 & -\frac{I_{zz}}{I_1}\dot{\psi} & 0 & -\frac{\sqrt{6}}{6I_1} & \frac{\sqrt{6}}{3I_1} & -\frac{\sqrt{6}}{6I_1}\\ 0 & \frac{mgr}{I_1} & 0 & \frac{I_{zz}}{I_1}\dot{\psi} & 0 & 0 & \frac{\sqrt{2}}{2I_1} & 0 & -\frac{\sqrt{2}}{2I_1}\\ 0 & 0 & 0 & 0 & 0 & 0 & -\frac{\sqrt{3}}{3I_{zz}} & -\frac{\sqrt{3}}{3I_{zz}} & -\frac{\sqrt{3}}{3I_{zz}}\\ 0 & 0 & 0 & 0 & 0 & 0 & -\frac{1}{t_{TC}} & 0 & 0\\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & -\frac{1}{t_{TC}} & 0\\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & -\frac{1}{t_{TC}}\\ \end{bmatrix} \begin{bmatrix} \theta \\ \phi \\ \psi \\ \dot{\theta} \\ \dot{\phi} \\ \dot{\psi} \\ \tau_1 \\ \tau_2 \\ \tau_3 \end{bmatrix} + \begin{bmatrix} 0 & 0 & 0\\ 0 & 0 & 0\\ 0 & 0 & 0\\ 0 & 0 & 0\\ 0 & 0 & 0\\ 0 & 0 & 0\\ \frac{1}{t_{TC}} & 0 & 0\\ 0 & \frac{1}{t_{TC}} & 0\\ 0 & 0 & \frac{1}{t_{TC}}\\ \end{bmatrix} \begin{bmatrix} \tau_{1c} \\ \tau_{2c} \\ \tau_{3c} \end{bmatrix}\]

Measurement Model $C, D$

Unlike dynamics modeling, measurement modeling is strictly a kinematics problem. Again, there is no shortage of mathematical gymnastics that you need to find in my pdf, but most of the work here is simply knowing how to apply the golden rule of rotational kinematics

\[a_r = a_s - \alpha \times r - 2 (\omega \times v_r) - \omega \times (\omega \times r)\]

The final measurement model written out explicitly is

\[\]

$I_w$ Calcs

Analysis is done (for now) and I need to get to know Cubli nice and personal. Low hanging fruit is the rotational inertia of the flywheel. I need this because eventually I am going to allocate $\alpha_i$ from my motor encoders to $\tau_i \in u$ and to do this I need $I_{w}$ to apply $\tau_i = I_{w} \alpha_i$.

Honestly this step is so trivial I am just going to spam you with my hand-calcs and some pictures I took for documentation purposes. $\boxed{I_{w} \sim 2.3036*10^{-4} ~\text{kg m}^2}$.

Hand Calcs
Onshape Mass Properties analysis with overridden mass
dimension measurements

Finding the $I_c$ tensor and $r_{CM}$

The problem of finding the inertia tensor of cubli has allowed me to exercise some creativity in experimental design. Since all of my kinematics/dynamics are calculated wrt $O’$, that is also how I define $I_c$. Now ideally I would have a perfect CAD model with all the right mass properties set or overridden such that a simple computer evaluation would give me $I_c$, but I do NOT have this luxury (this is something I may do in the future ONLY if necessary aka I need a more precise inertia tensor).

The overall problem is that I need to find \(I_c = \begin{bmatrix} I_{xx} & I_{xy} & I_{xz}\\ I_{yx} & I_{yy} & I_{yz}\\ I_{zx} & I_{zy} & I_{zz} \end{bmatrix}\)

Where $I$ is symmetric by nature so that $I_{ij} = I_{ji}$, leaving only 6 unique terms to find. BUT, I make the relatively safe assumption that Cubli is rotationally trisymmetric about the z-axis of $O’$, implying $I_{xx} = I_{yy}$ and also that the z-axis of $O’$ is a principal axis of rotation! This simplifies $I$ to be

\[I_c = \begin{bmatrix} I_{xx} & 0 & 0\\ 0 & I_{xx} & 0\\ 0 & 0 & I_{zz} \end{bmatrix}\]

Voila! I need only to identify 2 inertia terms of my Cubli! Observing $A$ we also see that I will need $r_{CM}$ the location of the center of mass of cubli, which per the rotationally symmetric assumption, should also lie on the z-axis of $O’$. The value hunt begins!

Diagram of the values I am trying to find and their references. Geometry shown on the right illustrates how cubli can be approximated as some equivalent cylinder due to symmetry.

Finding $I_{zz}$

$I_{zz}$ is the relatively easiest value to find. Since I know $I_w$ and all 3 motors are encoded,

Finding $I_{xx}$ and $r_{CoM}$

havent gotten around to documenting yet, but here’s a sneak peak

A series of controlled swings gives me all the information I need

Inner Loop Motor Torque Control