<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Home - Nonlinear Dynamic</title>
    <link rel="self" type="application/atom+xml" href="https://shaostassen.github.io/ShaoFastRobots/tags/nonlinear-dynamic/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://shaostassen.github.io/ShaoFastRobots"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-05-13T00:00:00+00:00</updated>
    <id>https://shaostassen.github.io/ShaoFastRobots/tags/nonlinear-dynamic/atom.xml</id>
    <entry xml:lang="en">
        <title>Lab 12: Inverted Pendulum</title>
        <published>2026-05-13T00:00:00+00:00</published>
        <updated>2026-05-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Shao Stassen
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://shaostassen.github.io/ShaoFastRobots/Fast Robots Stuff/lab-12/"/>
        <id>https://shaostassen.github.io/ShaoFastRobots/Fast Robots Stuff/lab-12/</id>
        
        <content type="html" xml:base="https://shaostassen.github.io/ShaoFastRobots/Fast Robots Stuff/lab-12/">&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;I drew inspiration from two prior writeups —
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;anunth-r.github.io&#x2F;Fast-Robots&#x2F;lab_9000.html&quot;&gt;Aravind Ramaswami&#x27;s wheelie report&lt;&#x2F;a&gt;
(Kalman + pole-placement) and
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;fast.synthghost.com&#x2F;lab-12-inverted-pendulum-control&#x2F;&quot;&gt;Stephan Wagner&#x27;s Lab 12 report&lt;&#x2F;a&gt;
(hand-tuned PD, no observer). With Dyllan Hofflich and Tina Cheng, our final
solution lands closer to Stephan&#x27;s.&lt;&#x2F;p&gt;
&lt;p&gt;The inverted pendulum is a very famous unstable nonlinear system — a small
disturbance from vertical grows exponentially unless the controller can
inject a corrective force faster than the car falls. On our hardware the
fall-time constant is about 70 ms, which sets a hard lower bound on the
control loop rate. The lab broke into three pieces: model the car as a
pendulum on a cart, build a real-time balance controller at $\theta = 0$,
and add a launch state machine to flip the car up from horizontal.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;system-modeling&quot;&gt;System Modeling&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;pendulum-on-cart-geometry&quot;&gt;Pendulum-on-cart geometry&lt;&#x2F;h3&gt;
&lt;p&gt;A wheel of mass $M$ translates along $x$, with a rigid rod of mass $m$ and
moment of inertia $I$ attached to its axle. The rod&#x27;s COM is a distance
$\ell$ above the wheel center. State vector:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\mathbf{x} = \begin{bmatrix} x \ \dot x \ \theta \ \dot\theta \end{bmatrix}.
$$&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;shaostassen.github.io&#x2F;ShaoFastRobots&#x2F;Fast%20Robots%20Stuff&#x2F;lab-12&#x2F;pendulum_diagram.jpg&quot; alt=&quot;Pendulum-on-cart model&quot; &#x2F;&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;shaostassen.github.io&#x2F;ShaoFastRobots&#x2F;Fast%20Robots%20Stuff&#x2F;lab-12&#x2F;car.jpg&quot; alt=&quot;Car-Pendulum&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;lagrangian-and-equations-of-motion&quot;&gt;Lagrangian and equations of motion&lt;&#x2F;h3&gt;
&lt;p&gt;With $T_w = \tfrac{1}{2}M\dot x^2$,
$T_p = \tfrac{1}{2}m,[(\dot x + \ell\dot\theta\cos\theta)^2 + (\ell\dot\theta\sin\theta)^2] + \tfrac{1}{2}I\dot\theta^2$,
and $V = mg\ell\cos\theta$, the Lagrangian
$\mathcal{L} = T_w + T_p - V$ gives:&lt;&#x2F;p&gt;
&lt;p&gt;$$
(M + m),\ddot x + m\ell,\ddot\theta\cos\theta - m\ell,\dot\theta^2\sin\theta = u
$$
$$
(I + m\ell^2),\ddot\theta + m\ell,\ddot x\cos\theta - mg\ell\sin\theta = 0
$$&lt;&#x2F;p&gt;
&lt;h3 id=&quot;linearization-about-vertical&quot;&gt;Linearization about vertical&lt;&#x2F;h3&gt;
&lt;p&gt;Substituting $\sin\theta \approx \theta$, $\cos\theta \approx 1$,
$\dot\theta^2 \approx 0$ linearizes the coupled equations around the
upright equilibrium. Since the car has no wheel encoder and I can&#x27;t ready the cart&#x27;s absolute position, I drop the
$x$ row entirely — penalizing $x$ in $Q$ wouldn&#x27;t actually make the
controller know where the cart is. The reduced 2-state model in
$[\theta,\ \dot\theta]$ becomes:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\dot{\mathbf{x}} = A\mathbf{x} + B u, \quad
A = \begin{bmatrix} 0 &amp;amp; 1 \ \alpha_1 &amp;amp; 0 \end{bmatrix}, \quad
B = \begin{bmatrix} 0 \ \alpha_2 \end{bmatrix}
$$&lt;&#x2F;p&gt;
&lt;p&gt;with $\alpha_1, \alpha_2$ aggregating $g$, $\ell$, $m$, $M$, $I$. The
positive entry $\alpha_1$ on the off-diagonal is what makes the upright
equilibrium unstable — its square root is the divergent eigenvalue of
$A$, the reciprocal of the fall-time constant. I initialize
$\alpha_1 \approx 6.21$, $\alpha_2 \approx 60$ from Aravind&#x27;s report as a starting point.&lt;br &#x2F;&gt;
I will retune the constants by hands later.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;controller-design&quot;&gt;Controller Design&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;lqr-for-principled-gain-selection&quot;&gt;LQR for principled gain selection&lt;&#x2F;h3&gt;
&lt;p&gt;LQR produces gains that optimally
trade off state error against control effort. The cost
$J = \int_0^\infty \mathbf{x}^\top Q,\mathbf{x} + R u^2, dt$
gives optimal feedback $K = R^{-1} B^\top P$, where $P$ solves the
continuous-time algebraic Riccati equation:&lt;&#x2F;p&gt;
&lt;p&gt;$$
A^\top P + P A - P B R^{-1} B^\top P + Q = 0.
$$&lt;&#x2F;p&gt;
&lt;p&gt;I weighted $Q$ heavily on $\theta$ (100) and lightly on $\dot\theta$ (1),
with $R = 1$ — penalize pose error, tolerate motion, don&#x27;t over-penalize
the motor:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; numpy&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; scipy.linalg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; solve_continuous_are&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;A&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np.array([[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;], [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;6.21&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;]])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;B&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np.array([[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;], [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;60.0&lt;&#x2F;span&gt;&lt;span&gt;]])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Q&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np.diag([&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;100.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span&gt;])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;R&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np.array([[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;1.0&lt;&#x2F;span&gt;&lt;span&gt;]])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;P&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; solve_continuous_are(A, B, Q, R)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;K&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np.linalg.inv(R)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; @&lt;&#x2F;span&gt;&lt;span&gt; B.T&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; @&lt;&#x2F;span&gt;&lt;span&gt; P&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;LQR gains [Kp, Kd]:&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, K.ravel())&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Result: $K_p \approx 0.04$, $K_d \approx 0.005$&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-pd-with-hand-tuned-gains-in-the-end&quot;&gt;Why PD with hand-tuned gains in the end&lt;&#x2F;h3&gt;
&lt;p&gt;The LQR output assumes the control input is in Newtons, but my firmware
issues signed PWM (−255 to +255). Converting between them requires a
force-to-PWM constant $k_f$ that I never measured, which left my LQR
gains dimensionally inconsistent with the motor command. I converged on
PD gains by hand instead, using the LQR ratio of $K_p:K_d$ as a starting
point:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Parameter&lt;&#x2F;th&gt;&lt;th&gt;Value&lt;&#x2F;th&gt;&lt;th&gt;Why&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;$K_p$&lt;&#x2F;td&gt;&lt;td&gt;15.0&lt;&#x2F;td&gt;&lt;td&gt;5° error → 75 PWM, above motor deadband&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;$K_d$&lt;&#x2F;td&gt;&lt;td&gt;1.0&lt;&#x2F;td&gt;&lt;td&gt;Rate damping to avoid oscillation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;max pwm&lt;&#x2F;td&gt;&lt;td&gt;255&lt;&#x2F;td&gt;&lt;td&gt;Full authority for large disturbances&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;target pitch&lt;&#x2F;td&gt;&lt;td&gt;+90°&lt;&#x2F;td&gt;&lt;td&gt;Wheelie orientation in my IMU mounting&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;The reference&#x27;s $K_p = 4$, $K_d = 0.2$ were sub-deadband: 5° → 20 PWM
didn&#x27;t move the wheels at all, then larger errors slammed past — a
limit-cycle recipe. Tripling $K_p$ moved every meaningful correction
above the motor&#x27;s static-friction threshold.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;control-law-in-firmware&quot;&gt;Control law in firmware&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; (stunt_state &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;==&lt;&#x2F;span&gt;&lt;span&gt; IPEND_HOLD) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    float&lt;&#x2F;span&gt;&lt;span&gt; pitch_rate_dps &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;= +&lt;&#x2F;span&gt;&lt;span&gt;latest_gx;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    float&lt;&#x2F;span&gt;&lt;span&gt; deviation      &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; latest_pitch_comp &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; ipend_cfg.target_pitch_deg;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    float&lt;&#x2F;span&gt;&lt;span&gt; u              &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; ipend_cfg.Kp&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; deviation&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;                         +&lt;&#x2F;span&gt;&lt;span&gt; ipend_cfg.Kd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; pitch_rate_dps;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    int&lt;&#x2F;span&gt;&lt;span&gt;   pwm            &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;int&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;constrain&lt;&#x2F;span&gt;&lt;span&gt;(u,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt;ipend_cfg.max_pwm,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;                                              +&lt;&#x2F;span&gt;&lt;span&gt;ipend_cfg.max_pwm);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;    setLeftMotor&lt;&#x2F;span&gt;&lt;span&gt;(pwm);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;    setRightMotor&lt;&#x2F;span&gt;&lt;span&gt;(pwm);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Both wheels get the same signed PWM — no differential needed.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;imu-state-estimation&quot;&gt;IMU State Estimation&lt;&#x2F;h2&gt;
&lt;p&gt;I tried Aravind&#x27;s Kalman observer first, but the estimate drifted
unpredictably during fast maneuvers — translational acceleration from
the cart kept contaminating the accelerometer pitch, the filter couldn&#x27;t
distinguish it from a true tilt, and the controller would saturate on
phantom angles. After a couple of sessions tuning $\Sigma_u$ and
$\Sigma_z$ to no avail, I went back to the complementary filter I
already used in earlier labs:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;pitch_lpf_state &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; ALPHA_LPF &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; pitch_acc &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; ALPHA_LPF)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; pitch_lpf_state;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;pitch_comp_state &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; ALPHA_COMP)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; (pitch_comp_state &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; pitch_rate &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; dt)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;                 +&lt;&#x2F;span&gt;&lt;span&gt; ALPHA_COMP &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; pitch_lpf_state;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The intuition is that the gyro is accurate on short timescales but drifts
when integrated; the accelerometer is noisy on short timescales but
absolute on long ones. With &lt;code&gt;ALPHA_LPF = 0.10&lt;&#x2F;code&gt; and &lt;code&gt;ALPHA_COMP = 0.05&lt;&#x2F;code&gt;,
the filter trusts the gyro between frames and gently pulls toward the
accel pitch over hundreds of milliseconds. At 50 Hz this is essentially
as good as Kalman, with zero hyperparameters to retune per stunt.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;axis-swap-calibration&quot;&gt;Axis-swap calibration&lt;&#x2F;h3&gt;
&lt;p&gt;My IMU was mounted rotated 90° about the car z-axis, so car pitch
showed up on IMU-y instead of IMU-x. I verified this by walking the car
through five orientations and noting which accel channel saturated to ±1 g in each. Fixed by
swapping which axes feed pitch and roll:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;latest_pitch_acc &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; atan2&lt;&#x2F;span&gt;&lt;span&gt;(latest_ay, latest_az)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 180.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;f &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; M_PI;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;latest_roll_acc  &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; atan2&lt;&#x2F;span&gt;&lt;span&gt;(latest_ax, latest_az)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 180.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;f &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; M_PI;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;float&lt;&#x2F;span&gt;&lt;span&gt; pitch_rate &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;= +&lt;&#x2F;span&gt;&lt;span&gt;latest_gx;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;switching-cars-tuning&quot;&gt;Switching Cars + Tuning&lt;&#x2F;h2&gt;
&lt;p&gt;After days of fighting motor deadband and surface friction on my own car,
I switch to path planning on the last day to have a complete
deliverable. Coming back to the pendulum with Dyllan and Tina, we ran my PD
code on Dyllan&#x27;s car and it balanced for a couple of seconds, my controller was actually decent for Ananya&#x27;s car.
We then committed to Dyllan&#x27;s full LQR formulation and tuned it together; the results below come from his
car. Ultimately, it came down to whose code seemed to have a better balance results because what it is left was just tuning, and it seems like his code just provided a better starting point&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wheelie-launch-state-machine-attempt&quot;&gt;Wheelie Launch State Machine - Attempt&lt;&#x2F;h2&gt;
&lt;p&gt;The launch sequence: drive forward to build
linear momentum, brake hard to plant the front wheels, then reverse the
motors. The wheels yank the contact point backward while the car&#x27;s
inertia keeps it moving forward, and the mismatch generates a
forward-pitching torque about the front axle that lifts the rear into
a wheelie. Once $|\theta| &amp;gt; 30°$, the FSM hands off to the balance
controller. The FSM has one interesting state (&lt;code&gt;BALANCE&lt;&#x2F;code&gt;); everything
else is event-driven timing, with timeouts dropping back to &lt;code&gt;IDLE&lt;&#x2F;code&gt; if
the launch fails.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;struct&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; WheelieConfig&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    int&lt;&#x2F;span&gt;&lt;span&gt;   forward_pwm        &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 250&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    int&lt;&#x2F;span&gt;&lt;span&gt;   forward_ms         &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 250&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    int&lt;&#x2F;span&gt;&lt;span&gt;   brake_ms           &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 100&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    int&lt;&#x2F;span&gt;&lt;span&gt;   reverse_pwm        &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 250&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    int&lt;&#x2F;span&gt;&lt;span&gt;   reverse_ms         &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 270&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    int&lt;&#x2F;span&gt;&lt;span&gt;   wait_max_ms        &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 600&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    float&lt;&#x2F;span&gt;&lt;span&gt; balance_trigger_deg &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 30.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    float&lt;&#x2F;span&gt;&lt;span&gt; target_pitch_deg    &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;= +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;90.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    float&lt;&#x2F;span&gt;&lt;span&gt; Kp &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 15.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    float&lt;&#x2F;span&gt;&lt;span&gt; Kd &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    int&lt;&#x2F;span&gt;&lt;span&gt;   max_balance_pwm &lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 255&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;};&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;results&quot;&gt;Results&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;my-best-controller&quot;&gt;My best controller&lt;&#x2F;h3&gt;
&lt;p&gt;Here&#x27;s what my best inverted pendulum on Ananya&#x27;s car. This is purely the code I developed on my own.&lt;&#x2F;p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;youtube.com&#x2F;embed&#x2F;o3lsaoRkGiI&quot;
  title=&quot;Baseline hardware — pre-controller&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;h3 id=&quot;first-attempt&quot;&gt;First attempt&lt;&#x2F;h3&gt;
&lt;p&gt;We found that the performance of the controller with the initial gain matrix was pretty bad, with the robot oscillating a lot and not being able to balance for more than a second if at all.&lt;&#x2F;p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;youtube.com&#x2F;embed&#x2F;jXx1CmFetPo&quot;
  title=&quot;First attempt — gains too low&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;h3 id=&quot;after-tuning&quot;&gt;After tuning&lt;&#x2F;h3&gt;
&lt;p&gt;Instead of trying to tune the Q and R matrices to get a better gain matrix, we decided to just manually tune the gain matrix by hand.&lt;&#x2F;p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;youtube.com&#x2F;embed&#x2F;lLXaZBUH5WM&quot;
  title=&quot;Tuned gains&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;h3 id=&quot;pid-solution&quot;&gt;PID solution&lt;&#x2F;h3&gt;
&lt;p&gt;After struggling to get the LQR controller to work well, we attempted to implement a PID controller on a linearized system. It is a simple PID controller using the angle error from the DMP readings, we thought we had more experience tuning it from previous labs, but it just performed worse.&lt;&#x2F;p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;youtube.com&#x2F;embed&#x2F;Z2JP7KJSA-Y&quot;
  title=&quot;PD solution running on Dyllan&#x27;s car&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;h3 id=&quot;launch-attempts&quot;&gt;Launch attempts&lt;&#x2F;h3&gt;
&lt;p&gt;Two attempts at the full launch-to-balance logic. The launch FSM
reaches the trigger threshold, but the dynamic was super noisy, and our balance controller cannot handle it very well.&lt;&#x2F;p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;PDPNfGAy_Yk&quot;
  title=&quot;Launch attempt 1&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;zfL0Ur6ZpwA&quot;
  title=&quot;Launch attempt 2&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;h3 id=&quot;surface-comparison-wood-vs-carpet&quot;&gt;Surface comparison: wood vs. carpet&lt;&#x2F;h3&gt;
&lt;p&gt;Surface friction matters more than I expected. On wood the wheels
slip during accelerations, so the controller cannot change fast enough, and the car tends to be less stable. On carpet the wheels bite and the controller&#x27;s
output actually moves the cart, which is what makes balance sustainable.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Best result on wood:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;V-yTDILahfI&quot;
  title=&quot;Best result on wood&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;p&gt;&lt;strong&gt;Best results on carpet:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;yngxeoroYk4&quot;
  title=&quot;Best result on carpet 1&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;gSWJiIi1oww&quot;
  title=&quot;Best result on carpet 2&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;h3 id=&quot;final-results-with-weight-on-top&quot;&gt;Final results — with weight on top&lt;&#x2F;h3&gt;
&lt;p&gt;The breakthrough was taping weights to the top of the chassis(with some further tuning of course). The
higher $I_{b,\text{com}}$ lengthens the fall-time constant, which
gives the controller more headroom to react to each disturbance.
The pole has a much better stability. See the final
clips at the bottom of
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;spike-h.github.io&#x2F;fastRobots&#x2F;lab12.html&quot;&gt;Dyllan&#x27;s Lab 12 report&lt;&#x2F;a&gt;
for the best runs of the tuned LQR with weight added.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bonus-videos-bloopers&quot;&gt;Bonus Videos &amp;amp; Bloopers&lt;&#x2F;h2&gt;
&lt;p&gt;Some of the more failures and side experiments from the
tuning sessions.&lt;&#x2F;p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;NFR8EhSzrFA&quot;
  title=&quot;Bonus 1&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;Ea4_m86Mnus&quot;
  title=&quot;Bonus 2&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;pruH4gw0J3s&quot;
  title=&quot;Bonus 3&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;QDWJhuRDNK0&quot;
  title=&quot;Bonus 4&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;CdZ6i-P8unU&quot;
  title=&quot;Bonus 5&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;gGT1McqVEnw&quot;
  title=&quot;Bonus 6&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;497FraAeoGw&quot;
  title=&quot;Bonus 7&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;h2 id=&quot;tangent-path-planning&quot;&gt;Tangent: Path Planning&lt;&#x2F;h2&gt;
&lt;p&gt;Overview of path planning. I worked on this for about six hours before joining Dyllan and Tina on
the Inverted Pendulum. The task is to
navigate 9 waypoints across the mapped arena, from $(-4, -3)$ to $(0, 0)$
in grid cells. I went with turn-go-turn between adjacent waypoints
(no global planning — the waypoints are hand-designed and have no walls
between consecutive points) and a Bayes-filter update at every
waypoint, reusing the Lab 11 pattern: uniform-prior reset before each
360° scan, no prediction step (the provided $O(N^6)$ implementation is
too slow for online use). Onboard PID handles both the in-place turn and
the ToF-based straight leg; offboard Python plans the segments and
&lt;code&gt;await&lt;&#x2F;code&gt;s &lt;code&gt;DONE&lt;&#x2F;code&gt; notifications from the firmware so the Bayes-filter
compute doesn&#x27;t compete with the 50 Hz control loop. A single waypoint
takes 8–12 seconds end-to-end, dominated by the 18
turn-and-settle cycles of the localization scan. My best run hits the
first 4–5 waypoints before cumulative heading drift (±5° per turn) points
the car at a wall.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;async def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; step_once&lt;&#x2F;span&gt;&lt;span&gt;(self):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # 1. Current pose = argmax of latest belief&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    cell&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np.unravel_index(np.argmax(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.loc.bel),&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; self&lt;&#x2F;span&gt;&lt;span&gt;.loc.bel.shape)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    current_pose&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; self&lt;&#x2F;span&gt;&lt;span&gt;.loc.mapper.from_map(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;cell)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # 2. Turn-go-turn to next waypoint&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    rot1, trans&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; self&lt;&#x2F;span&gt;&lt;span&gt;._plan_segment(current_pose,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; self&lt;&#x2F;span&gt;&lt;span&gt;.target_waypoint)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; self&lt;&#x2F;span&gt;&lt;span&gt;.robot.turn_to_heading(current_pose[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; rot1)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; self&lt;&#x2F;span&gt;&lt;span&gt;.robot.drive_distance(trans)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # 3. Re-localize: 360° scan + Bayes update&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; self&lt;&#x2F;span&gt;&lt;span&gt;.loc.get_observation_data() &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;    self&lt;&#x2F;span&gt;&lt;span&gt;.loc.update_step()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;pZiou_tTjP8&quot;
  title=&quot;Path execution — best run&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;h2 id=&quot;collaboration&quot;&gt;Collaboration&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Thanks to Dyllan Hofflich for letting me run my PD code on Ananya Jajodia&#x27;s car and working together with Dyllan Hofflich and Tina Cheng on the LQR derivation and tuning sessions. Thanks to Claude for helping debugging my inverted pendulum code and tuning process. Thanks to Professor Helbling and the entire teaching team of Fast Robots for a great semester. This is one of the best classes I have taken in college.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
