<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Jake Robert Read</title>
    <description>ask me about yak shaving ! 
</description>
    <link>http://ekswhyzee.com/</link>
    <atom:link href="http://ekswhyzee.com/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Thu, 04 Jun 2026 15:10:37 -0400</pubDate>
    <lastBuildDate>Thu, 04 Jun 2026 15:10:37 -0400</lastBuildDate>
    <generator>Jekyll v4.3.3</generator>
    
      <item>
        <title>Visting Shenzhen</title>
        <description>
&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-03_pic-0002.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-11_pic-0042.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-07_pic-0033.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-10_pic-0022.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;style&gt;
  .lightbox-overlay {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.6);
    z-index: 9999;
    justify-content: center;
    align-items: center;
  }

  .lightbox-close {
    position: absolute;
    top: 1rem;
    right: 1rem;
    background: transparent;
    border: none;
    color: white;
    font-size: 2.5rem;
    cursor: pointer;
    line-height: 1;
    padding: 0.5rem;
    z-index: 10000;
    opacity: 0.8;
    transition: opacity 0.05s;
  }

  .lightbox-close:hover {
    opacity: 1;
  }

  .lightbox-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    background: rgba(255, 255, 255, 0.1);
    border: none;
    color: white;
    font-size: 2.5rem;
    cursor: pointer;
    padding: 1rem 1.25rem;
    z-index: 10000;
    opacity: 0.8;
    transition: opacity 0.05s, background 0.05s;
    line-height: 1;
  }

  .lightbox-nav:hover {
    opacity: 1;
    background: rgba(255, 255, 255, 0.2);
  }

  .lightbox-nav:disabled {
    opacity: 0.2;
    cursor: default;
    background: transparent;
  }

  .lightbox-prev {
    left: 1rem;
    border-radius: 4px;
  }

  .lightbox-next {
    right: 1rem;
    border-radius: 4px;
  }

  .lightbox-media {
    max-width: 90vw;
    max-height: 80vh;
    object-fit: contain;
    cursor: default;
    transition: opacity 0.05s;
  }

  .lightbox-counter {
    position: absolute;
    bottom: 1rem;
    left: 50%;
    transform: translateX(-50%);
    color: rgba(255, 255, 255, 0.7);
    font-size: 0.9rem;
    font-family: system-ui, -apple-system, sans-serif;
  }

  .lightbox-caption {
    position: absolute;
    bottom: 3rem;
    left: 50%;
    transform: translateX(-50%);
    color: rgba(255, 255, 255, 0.9);
    font-size: 1rem;
    font-style: italic;
    font-family: system-ui, -apple-system, sans-serif;
    text-align: center;
    max-width: 80%;
    padding: 0.75rem 1.5rem;
    background: rgba(0, 0, 0, 0.5);
    border-radius: 4px;
    line-height: 1.4;
  }

  .lightbox-caption:empty {
    display: none;
  }
&lt;/style&gt;

&lt;div id=&quot;lightbox-overlay&quot; class=&quot;lightbox-overlay&quot; onclick=&quot;closeLightbox()&quot;&gt;
  &lt;button class=&quot;lightbox-close&quot; onclick=&quot;closeLightbox()&quot; aria-label=&quot;Close lightbox&quot;&gt;&amp;times;&lt;/button&gt;
  
  &lt;button id=&quot;lightbox-prev&quot; class=&quot;lightbox-nav lightbox-prev&quot; onclick=&quot;event.stopPropagation(); navigateLightbox(-1)&quot; aria-label=&quot;Previous image&quot;&gt;&amp;#8249;&lt;/button&gt;
  
  &lt;div id=&quot;lightbox-media-container&quot; onclick=&quot;event.stopPropagation()&quot;&gt;
    &lt;!-- Media element (img or video) will be inserted here by JavaScript --&gt;
  &lt;/div&gt;
  
  &lt;button id=&quot;lightbox-next&quot; class=&quot;lightbox-nav lightbox-next&quot; onclick=&quot;event.stopPropagation(); navigateLightbox(1)&quot; aria-label=&quot;Next image&quot;&gt;&amp;#8250;&lt;/button&gt;
  
  &lt;div id=&quot;lightbox-caption&quot; class=&quot;lightbox-caption&quot;&gt;&lt;/div&gt;
  &lt;div id=&quot;lightbox-counter&quot; class=&quot;lightbox-counter&quot;&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;script&gt;
  (function() {
    let currentIndex = 0;
    let allMedia = [];

    function getAllLightboxMedia() {
      return Array.from(document.querySelectorAll(&apos;[data-lightbox=&quot;true&quot;]&apos;));
    }

    function openLightbox(element) {
      allMedia = getAllLightboxMedia();
      currentIndex = allMedia.indexOf(element);
      
      updateLightboxMedia();
      
      const overlay = document.getElementById(&apos;lightbox-overlay&apos;);
      overlay.style.display = &apos;flex&apos;;
      document.body.style.overflow = &apos;hidden&apos;;
    }

    window.closeLightbox = function() {
      const overlay = document.getElementById(&apos;lightbox-overlay&apos;);
      overlay.style.display = &apos;none&apos;;
      document.body.style.overflow = &apos;&apos;;
      
      // Pause any playing video when closing
      const container = document.getElementById(&apos;lightbox-media-container&apos;);
      const video = container.querySelector(&apos;video&apos;);
      if (video) {
        video.pause();
      }
    };

    window.navigateLightbox = function(direction) {
      // Pause current video before navigating
      const container = document.getElementById(&apos;lightbox-media-container&apos;);
      const video = container.querySelector(&apos;video&apos;);
      if (video) {
        video.pause();
      }
      
      currentIndex = currentIndex + direction;
      
      if (currentIndex &lt; 0) currentIndex = 0;
      if (currentIndex &gt;= allMedia.length) currentIndex = allMedia.length - 1;
      
      updateLightboxMedia();
    };

    function updateLightboxMedia() {
      const container = document.getElementById(&apos;lightbox-media-container&apos;);
      const prevBtn = document.getElementById(&apos;lightbox-prev&apos;);
      const nextBtn = document.getElementById(&apos;lightbox-next&apos;);
      const counter = document.getElementById(&apos;lightbox-counter&apos;);
      const caption = document.getElementById(&apos;lightbox-caption&apos;);
      
      const currentElement = allMedia[currentIndex];
      const mediaType = currentElement.getAttribute(&apos;data-media-type&apos;);
      const captionText = currentElement.getAttribute(&apos;data-caption&apos;) || &apos;&apos;;
      
      // Clear the container
      container.innerHTML = &apos;&apos;;
      
      if (mediaType === &apos;video&apos;) {
        // Create video element
        const video = document.createElement(&apos;video&apos;);
        video.className = &apos;lightbox-media&apos;;
        video.controls = true;
        video.autoplay = true;
        
        const source = document.createElement(&apos;source&apos;);
        // Get the source from the original video element
        const originalSource = currentElement.querySelector(&apos;source&apos;);
        if (originalSource) {
          source.src = originalSource.src;
          source.type = originalSource.type;
        } else {
          source.src = currentElement.src;
          source.type = currentElement.getAttribute(&apos;data-video-type&apos;) || &apos;video/mp4&apos;;
        }
        
        video.appendChild(source);
        container.appendChild(video);
      } else {
        // Create image element
        const img = document.createElement(&apos;img&apos;);
        img.className = &apos;lightbox-media&apos;;
        img.style.opacity = &apos;0&apos;;
        img.src = currentElement.src;
        img.alt = &apos;&apos;;
        img.onload = function() {
          img.style.opacity = &apos;1&apos;;
        };
        container.appendChild(img);
      }
      
      // Update caption
      caption.textContent = captionText;
      
      prevBtn.disabled = (currentIndex === 0);
      nextBtn.disabled = (currentIndex === allMedia.length - 1);
      
      if (allMedia.length &gt; 1) {
        counter.textContent = (currentIndex + 1) + &apos; / &apos; + allMedia.length;
        counter.style.display = &apos;block&apos;;
        prevBtn.style.display = &apos;block&apos;;
        nextBtn.style.display = &apos;block&apos;;
      } else {
        counter.style.display = &apos;none&apos;;
        prevBtn.style.display = &apos;none&apos;;
        nextBtn.style.display = &apos;none&apos;;
      }
    }

    document.addEventListener(&apos;keydown&apos;, function(e) {
      const overlay = document.getElementById(&apos;lightbox-overlay&apos;);
      if (overlay.style.display !== &apos;flex&apos;) return;
      
      if (e.key === &apos;Escape&apos;) {
        closeLightbox();
      } else if (e.key === &apos;ArrowLeft&apos;) {
        navigateLightbox(-1);
      } else if (e.key === &apos;ArrowRight&apos;) {
        navigateLightbox(1);
      }
    });

    document.addEventListener(&apos;click&apos;, function(e) {
      const target = e.target.closest(&apos;[data-lightbox=&quot;true&quot;]&apos;);
      if (target) {
        openLightbox(target);
      }
    });
  })();
&lt;/script&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;“Everyone has already heard about the young person who comes to Shenzhen to make something and is astounded by the city, this is an old story - let’s talk about something else”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An MIT Alum said this to me in our cab ride back from our visit to a market leading Robotics firm after about a week of my own time as a young person having my mind expanded in Shenzhen (thanks to &lt;a href=&quot;https://inm.mit.edu/&quot;&gt;MIT’s INM&lt;/a&gt; for bringing me along on that daytrip as a guest). The funny thing is that I didn’t &lt;em&gt;feel&lt;/em&gt; as though I had heard that story too many times, or I had but had kind of saved it to chew on for later. I have been nervous to see in the flesh what I have long felt is true - that while I have been whittling away in the basement of the &lt;a href=&quot;https://cba.mit.edu/&quot;&gt;CBA&lt;/a&gt; (I am &lt;em&gt;so&lt;/em&gt; close to graduating) trying to make myself excellent in the fields of manufacturing and robotics, this whole country on the other side of the globe has been doing the same - while my country(s) have been abandoning it. That cab ride was about a week into the trip. At the moment (one more week later) I’m sat on Cathay Pacific flight CX812 from Hong Kong direct to Boston, it’s 11:47pm according to my body clock but 10:47am in my new local time and so I am going to sit down and chew on it; if you’re reading this you’re along for that ride.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this note:&lt;/strong&gt; some vibes based economic analysis from the morning of my arrival, some notes on my glancing blow with the markets and manufacturing ecosystem in Shenzhen, and some photos and thoughts about cities and scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Special thanks&lt;/strong&gt; for this experience goes to &lt;a href=&quot;&quot;&gt;Cedric Honnet&lt;/a&gt; who (at the behest of nobody, apparently) goes out of his way every year to organize in cowboy-style the &lt;a href=&quot;(https://www.media.mit.edu/posts/research-at-scale/)&quot;&gt;Research at Scale&lt;/a&gt; &lt;a href=&quot;https://www.seeedstudio.com/blog/2025/02/08/maker-camp-in-shenzhen-2501/&quot;&gt;Residency&lt;/a&gt;, including the &lt;a href=&quot;https://scalablehci.com/2026/&quot;&gt;Scalable HCI&lt;/a&gt; conference. This was all generously enabled by &lt;a href=&quot;https://www.seeedstudio.com/&quot;&gt;Seeed&lt;/a&gt; - some more on them later. Thanks also to the &lt;a href=&quot;https://cba.mit.edu/&quot;&gt;CBA&lt;/a&gt; for funding my own travel so that I could thread the needle between a Shenzhen visit and also saving enough time to get back to Boston to finish writing my dissertation.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;from-2026-01-04&quot;&gt;From 2026 01 04&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;This chunk is first impressions upon arrival, with added notes and photos post-hoc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After arriving late last night to Shenzhen and having a kind of strange jet-lagged sleep, I got up this morning and went for a wander - and to find a coffee. Now I’m here trying to articulate this sensation: I have travelled pretty well (grew up in Canadian Suburbs, studied for eight months in Halifax and four months in Rome (thinking specifically about cities), interned in San Fransisco and at Princeton, lived briefly in Toronto during the Spaghetti Years post-undergrad, have been to Mexico, Switzerland, Vancouver, New York, and taken work trips to Kerala, Santiago, and Tolouse - even as far out as Puerto Williams (the Chilean jump-off to Antarctica)). But this feels totally different.&lt;/p&gt;

&lt;p&gt;The hotel I’m in is a subset of a larger building complex (called Boyu Nanshan Yuncheng Flagship &lt;em&gt;Store&lt;/em&gt; per the maps translation). The complex is a little city all on its own: there are families and youths mixed into what must be thousands of apartments, the lower floors are filled with small businesses, cafes, restaurants, stores etc. The elevator ride is long enough that it’s worth considering when you estimate how long it’s going to take to get anywhere. Probably I am making myself sound like a country bumpkin, but I guess &lt;em&gt;that’s&lt;/em&gt; just the sensation that is new to me. Normally I go places and I feel like I can kind of comprehend it: the scale makes sense and I am not really in &lt;em&gt;awe&lt;/em&gt; of anything. This feels different, things are big &lt;em&gt;and&lt;/em&gt; there is a real sense that the thing is all working together.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-04_pic-0002.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-04_pic-0038.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-04_pic-0011.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-04_pic-0010.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;Walking around on the street level, I was struck also by the calm that it had. The air felt fresh, the cars are quiet (my current estimate is that only about 20% of the vehicles have tailpipes). The streetlights are not running - I don’t know if this is intentional or what - but traffic proceeds without issue, people giving eachother way and making it through relatively busy intersections efficiently. I stood for a while waiting for a &lt;em&gt;walk&lt;/em&gt; sign until I realized that this is what was happening.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-04_pic-0015.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;No streetlights to govern, no worries.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-04_pic-0023.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;No streetlights to govern, no worries.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-05_pic-0017.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;No streetlights to govern, no worries.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;No streetlights to govern, no worries.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;It’s unclear to me if this is planned or if they have simply not finished with the lights, having many other city-block-size projects to get on with. Another thing that I noticed a few days later is that these intersections are watched over with loving grace by &lt;em&gt;many&lt;/em&gt; CCTV cameras. So, security and calm via the panopticon - whereas in the west our surveillance is somewhat more obscure and functions primarily for the purposes of ad sales.&lt;/p&gt;

&lt;p&gt;On the sidewalk you share space with little electric scooters (many running deliveries, many are just personal transport), but there is ample space. Many streets (in my one-day sample) have dedicated lanes for them, something basically anyone who lives in a western city will be envious of.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-05_pic-0012.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Shenzhen scooter design is evolving faster than it is in the west. They co-habitate with pedestrians, trucks, cars, and tuktuks filled with Yams.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-14_pic-0016.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Shenzhen scooter design is evolving faster than it is in the west. They co-habitate with pedestrians, trucks, cars, and tuktuks filled with Yams.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0090.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Shenzhen scooter design is evolving faster than it is in the west. They co-habitate with pedestrians, trucks, cars, and tuktuks filled with Yams.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Shenzhen scooter design is evolving faster than it is in the west. They co-habitate with pedestrians, trucks, cars, and tuktuks filled with Yams.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;The scooters are everywhere - I wondered for a few days why traffic didn’t seem as bad as it aught to in a city of this scale, and then I saw enough of these giant fucking piles of scooters to realize that they’re doing a classic case of &lt;em&gt;lots of little vehicles&lt;/em&gt; vs. &lt;em&gt;lots of big ones,&lt;/em&gt; seems pretty effective and they look like a blast to rip around in. Two weeks of this later and I can say that there is sometimes a cacophany of scooter-honking but I witnessed zero collisions and only one near miss.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-05_pic-0023.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Scooters at SusTech, Yihua Market, somewhere between Yihua and Nanshan, and in Nantou. I would regularely see these on highways and major roads, resplete with helmetless riders.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0015.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Scooters at SusTech, Yihua Market, somewhere between Yihua and Nanshan, and in Nantou. I would regularely see these on highways and major roads, resplete with helmetless riders.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0108.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Scooters at SusTech, Yihua Market, somewhere between Yihua and Nanshan, and in Nantou. I would regularely see these on highways and major roads, resplete with helmetless riders.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-11_pic-0038.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Scooters at SusTech, Yihua Market, somewhere between Yihua and Nanshan, and in Nantou. I would regularely see these on highways and major roads, resplete with helmetless riders.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Scooters at SusTech, Yihua Market, somewhere between Yihua and Nanshan, and in Nantou. I would regularely see these on highways and major roads, resplete with helmetless riders.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-04_pic-0028.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-04_pic-0024.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-04_pic-0030.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-04_pic-0039.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-04_pic-0043.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-04_pic-0045.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;blockquote&gt;
  &lt;p&gt;Photos above: descending into our base camp at Chaihuo in a so-called &lt;em&gt;design commune;&lt;/em&gt; a kind of brutalist sunken park / mixed use space. There is an amount of three-dimensionality to these cities that made my inner architect excited. Also a sense of planning: &lt;em&gt;Vanke&lt;/em&gt; is a monster developer in China, they made this with the explicit purpose of building lots of small (and hip) little studios / offices for creative and “innovative” firms to start up in. There are cafes and bars and design firms and lunch spots and design stores all setup in here. Somehow it is a park as well, on the top/sunny side are children playing and weird little designer dogs out with their people, and a set of basketball courts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-04_pic-0047.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0014.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-12_pic-0009.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-14_pic-0031.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-14_pic-0033.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-14_pic-0034.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/nanshan_design_commune.png&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-14_pic-0037.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;blockquote&gt;
  &lt;p&gt;Above: more from the &lt;em&gt;design commune&lt;/em&gt; park: subterranean bbq’s, topside cafes, whatever this little courtyard nook is, and a little sports complex. All of this takes up about three city blocks in Nanshan with monumental-sized towers on the north and south sides, including the DJI Headquarters (above, right).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On that &lt;em&gt;first&lt;/em&gt; morning it was the peace of it all, paired with regular city bustle, that really struck me. I picked up a coffee and wandered down towards the makerspace that served as our basecamp for the residence, Seeed’s &lt;a href=&quot;https://www.seeedstudio.com/chaihuo-makerspace&quot;&gt;Chaihuo&lt;/a&gt; makerspace (Chaihuo translates roughly to &lt;em&gt;fire starter&lt;/em&gt; or &lt;em&gt;fire kindling&lt;/em&gt;), our main sponsors / hosts. It lives in a &lt;em&gt;design commune&lt;/em&gt; built by Vanke, the same developers of the hotel complex. It is sunken into a tiny stretch of park, and on my way in I was met with the street cleaning crew who were working on the sidewalks. I may have been projecting but I had a sense of &lt;em&gt;their&lt;/em&gt; contentment: I though of the quip that my old boss and buddy Saul used to make, that when he retires he hopes to sweep the floors in a shop that &lt;em&gt;he&lt;/em&gt; built while the next generation builds whatever’s next. This is the vibe I got from the cleaning crew: older men in dignified jobs, doing a service for a city that they feel is theirs.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;video controls=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot; muted=&quot;true&quot;&gt;
  &lt;source src=&quot;images/2026-01-04_vid-0003.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Up at 6am, I was out with the morning cleanup crew: mostly older dudes sweeping with this style broom (yet to modernize: if it ain’t broke…).&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I often feel that this simple sensation… the feeling that our work contributes to something that is &lt;em&gt;real&lt;/em&gt; and worthwhile - is largely amiss in the western world. The vibes are off and it seems like everyone can feel that, but we can’t quite put our finger on what’s missing. I have always held that the economy has basically two faces: the &lt;em&gt;real shit&lt;/em&gt; (building, inventing, maintaining, transporting, healing, etc), and the &lt;em&gt;extractive:&lt;/em&gt; financial speculation (though I will allow that capital markets play an important role in allocating economic energy, they seem clearly (1) overcomplicated and (2) highly ‘skimmed’ by those who direct them) and all of the various forms of rent-taking, i.e. literally landlordship but also any economic activity that is predecated on the sole ownership of some exclusive property (especially those that require no maintenance, i.e. IP (also netflix and the like)), and the extraction of rents (subscriptions, licenses, and “locked-in-on-proprietary-tech” business models) on those properties. This second half of the economy is fundamentally &lt;em&gt;not productive&lt;/em&gt; - it serves only to accumulate capital into bigger and bigger piles.&lt;/p&gt;

&lt;p&gt;Economies where real growth is low overall (not a lot of &lt;em&gt;building shit&lt;/em&gt;) naturally tend towards rent-taking: when there is less &lt;em&gt;new&lt;/em&gt; output, it is much easier to make money by owning more of the pie than by participating in the making of more pie. Because rents can only be taken on things that are &lt;em&gt;owned,&lt;/em&gt; the result is that &lt;em&gt;prior ownership of assets becomes more important than “real” activities&lt;/em&gt; i.e. labour (of which we all own an equal amount of).&lt;/p&gt;

&lt;p&gt;This is the broad-strokes driver of inequality &lt;em&gt;and it’s vibes-based counterpart:&lt;/em&gt; the feeling that we don’t have meaningful stake in our world, in our city. For a properly enlightened read on the same, see Piketty’s &lt;a href=&quot;https://en.wikipedia.org/wiki/Capital_in_the_Twenty-First_Century&quot;&gt;&lt;em&gt;Capital in the 21st Century,&lt;/em&gt;&lt;/a&gt; probably one of the most important economics works of the first quarter of this century and an exceptional piece of level-headed academia (it is also actually enjoyable to read).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/piketty_fig10_10.png&quot; alt=&quot;img&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;images/piketty_top-1-percent-income.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;The main dynamic that Piketty discusses in his &lt;em&gt;Capital;&lt;/em&gt; where the growth rate (more real stuff) is larger than the return-on-capital investment, we get equality. Or the opposite. The second chart here is to 2010, and you will probably believe that it goes up and to the right from then until 2025. Notably missing is Chinese data: FWIW its &lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_countries_by_income_inequality&quot;&gt;Gini Coefficient&lt;/a&gt; sits somewhere between the USA and Canada.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a simple example of the results of this dynamic, readers of my generation will have probably noticed that access to &lt;em&gt;real wealth&lt;/em&gt; basically requires our (1) being born or married into it or (2) making some new and valuable firm (a startup) after having convinced other capital owners (VCs) to lend you a few tens of millions dollars - and subsequently convincing other capital laden folks to purchase it for the purposes of their future returns. Consider also the phrase &lt;em&gt;you will own nothing and you will be happy&lt;/em&gt; and think about how many things in your life have tended in this direction (and whether or not that brings you joy). There was a moment there where we had enough real growth in the western economy (following the destruction of WWII, it’s accelerated industrialization of the west, the joining of women into the workforce and ~ the yolking of North America’s previously peacefully-at-rest vast natural resources) where many (not &lt;em&gt;all&lt;/em&gt; by any means) folks could work an honest job and eventually enter the owning class (via home ownership). Today, this kind of salary requires that we get into the top few percentile of day jobs.&lt;/p&gt;

&lt;p&gt;So that’s where I was at: thinking about the scale of the city (big!) but the simultaneous sense that everyone here &lt;em&gt;is in it&lt;/em&gt; - is a meaningful participant. I won’t say that I am any kind of authority on the topic but I am here trying to tell you about how/why it made me feel this way, not really to convince you of anything. I had a seat in the little pit of the design commune and honest to god I shed a few tears about it. I will hedge again that I had just done about 24 hours of travel / wakefulness and only a few hours of sleep, &lt;em&gt;and&lt;/em&gt; that I am personally really &lt;em&gt;going through it&lt;/em&gt; these days, but these were not a woe-is-me set of tears, this was kind of… awe of the world, awe of this other culture’s accomplishment, sadness that my own culture has not been working on a similar project of massive growth via mass economic participation, and excitement that it is happening somewhere.&lt;/p&gt;

&lt;p&gt;There is much other hedging to do on all accounts; relative measures of actual justice and equality in any of these large and messy systems that we build for ourselves to live inside of. China is only now having its reward on the backs of a few generations that absolutely struggled through it, and I won’t pretend that I completely understand their story. This was just my feeling and thinking after walking around about two or three blocks in Nanshan which is just a very small (and shiny) slice of a very, very large place. I absolve myself of any responsibility for (1) not having seen enough of any of this to properly comprehend it at this point and (2) bringing all of my prior bias and readings and thinkings to the topic. As noted &lt;em&gt;we’re doing travelogue in this one, y’all.&lt;/em&gt; so it is vibes based economics for today.&lt;/p&gt;

&lt;p&gt;To close this one out I want to try to articulate that scale again. Below are three images: the view from this hotel / apartment in the morning (facing south-west and then south: an infinity of towers) and then the slice of the city that I am estimating I am looking at. By the way that haze is water vapor not smog, the air quality index was excellent and my lungs felt great the whole trip.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-05_pic-0003.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0088.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-05_pic-0018_crop.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;from-2026-01-20-on-the-flight-to-boston&quot;&gt;From 2026 01 20 (on the flight to Boston)&lt;/h2&gt;

&lt;p&gt;16 days later and I still feel like those reflections above are a reasonable take. I want now to look a little more at what I saw of the city itself, and then aim at what has always been the beacon for me: economic empowerment through increased participation in manufacturing. For this I will share what I saw from the markets (inputs and outputs to / from the Shenzhen manufacturing ecosystem), and then some factory visits (transformers of the same) as well as some robots that represent top tier output. And we will get back to those street sweepers.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;canadian-architecture-student-visits-the-big-city-and-gapes-at-buildings&quot;&gt;Canadian Architecture Student visits the Big City and Gapes at Buildings&lt;/h3&gt;

&lt;p&gt;I love a busy city for the same reason that I love to think about economics: these are real manifestations of collected human effort, cooperation and struggle. If the “economy” is the holy ghost (ephemeral, impossible to completely measure), the city is the body of christ (messy and human), and the father is nowhere to be seen (or maybe it’s the govt’, but I don’t want to give them that). They contain an infinity of little stories that are all linked together, they are &lt;em&gt;systems of systems&lt;/em&gt; that all somehow work (mostly). They are all (mostly) guided by individuals who want to better their own world and that of their neighbours.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-10_pic-0022.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-07_pic-0014.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-05_vid-0006.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;Shenzhen is big; it should be possible to look at a map and understand this but of course it doesn’t really work. You may recall the feeling of hiking towards a mountain: it is large in the distance and then gets larger over the course of minutes, but slowly. Suddenly you are under / within it but you never really &lt;em&gt;got&lt;/em&gt; there, it just eventually became your surroundings. There are buildings like this in Shenzhen.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-14_pic-0035.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0120.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0124.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0125.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;blockquote&gt;
  &lt;p&gt;The DJI HQ from ~ 500m away, 50m away, and right below.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To a foreigner it is also fantastically difficult to navigate. It feels that there are two extra layers of scale: I am used to finding a building on a street in a neighbourhood of the city. Here there is an extra terminal guidance step once you get &lt;em&gt;to&lt;/em&gt; the complex of buildings where your goal is, and an extra layer somewhere between neighbourhood and city. Take for example maps provided to us to navigate to our hotel (itself just a particular set of floors in one tower of a four tower complex), and to navigate into the Chaihuo makerspace (nested in the belly of a three-block long “design commune” development (subterranean)).&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/boyu_map.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Terminal Navigation to Boyu (our hotel) and to [Chaihuo](https://seeed-studio.github.io/MakerCamp/Chaihuo/)&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/chaihuo_nav_tall.png&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Terminal Navigation to Boyu (our hotel) and to [Chaihuo](https://seeed-studio.github.io/MakerCamp/Chaihuo/)&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Terminal Navigation to Boyu (our hotel) and to &lt;a href=&quot;https://seeed-studio.github.io/MakerCamp/Chaihuo/&quot;&gt;Chaihuo&lt;/a&gt;&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;There is a wonderful heterogeneity here as well. 20 minutes from the towering, bladerunner DJI headquarters is the Nantou “Ancient Village” (a tourist trap that I shamelessly enjoyed). Here is adhoc and small scale construction that architecture students dream of, buildings stuck on to other buildings and the dissolving of street / building boundaries. People seem to just be sticking structures in whichever places they can get away with to make more space.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-11_pic-0016.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-11_pic-0006.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-11_pic-0005.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-11_pic-0026.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-11_pic-0027.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-11_pic-0036.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-11_pic-0041.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-11_pic-0042.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-11_pic-0043.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;I saw a lot of this in Hong Kong as well: shops, bars and restaurants where the interior of the space borrows from the street. Architects loves this blurry stuff. The climate here allows it and the density requires it.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-18_pic-0140.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-19_pic-0012.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-19_pic-0059.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;Parts of Shenzhen are new and shiny but many of the towers are both monstrous and also well lived in, drying laundry heaps out of the balconies.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-04_pic-0004.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-12_pic-0013.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;Many of the shiny new towers had a feeling that the wrapping hadn’t entirely been taken off, or maybe their first major maintenance cycles had yet to finish. I saw a lot of dirty windows in otherwise brand new looking buildings… the city being only forty years old means that it has been somewhat unburdened by maintenance so far - or uninterested in it since the new things are shiny enough by default. This is maybe part of a larger arc on &lt;em&gt;technological leapfrogging&lt;/em&gt; - Shenzhen being “all new” enables them to make a better city than we can in the west because there isn’t so much cruft to deal with / old shit to maintain while the new activity goes on. For example it is famously difficult to build new transit in Rome because everywhere they dig is an archeological site. In modern western cities there are existing land owners to deal with, nimbyism and more, and planning authorities that lack the backing of a totalizing government body. These “encumberances” can be good; in Shenzhen, neighbourhoods are scheduled to be &lt;em&gt;entirely demolished&lt;/em&gt; by the state and existing residents are simply made to move out. This is called an &lt;em&gt;evacuation&lt;/em&gt; - as if it is the result of some natural force. The same kind of dynamic shows up in i.e. American power grids: the east coast’s is shoddier than the west’s because it was originally built many decades prior. Mistakes made the first go-round weren’t repeated, but the lessons and technologies were transferred. The same has happened wholesale in China, largely in part via grey-zone technology transfer from western companies (who put their factories in China while chasing a profit) that has many folks in the west upset. The mistake that we make is to ho-hum about it rather than get after our own improvements. There is no really good long term response to this other than to &lt;em&gt;get good&lt;/em&gt; and dig back in ourselves.&lt;/p&gt;

&lt;p&gt;The construction of the city continues; every morning I woke up around six am and by seven as I sat down to work on the 42nd floor I was met with the sounds of pile driving in the giant site to my south…&lt;/p&gt;

&lt;!-- 
| | | | |
| --- | --- | --- | --- |
| ![img](images/2026-01-04_pic-0036.jpg) | ![img](images/2026-01-05_pic-0016.jpg) | ![img](images/2026-01-06_pic-0005.jpg) | ![img](images/2026-01-07_pic-0004.jpg) |
| ![img](images/2026-01-08_pic-0003.jpg) | ![img](images/2026-01-10_pic-0001.jpg) | ![img](images/2026-01-11_pic-0007.jpg) | ![img](images/2026-01-12_pic-0004.jpg) | 
| ![img](images/2026-01-14_pic-0001.jpg) | ![img](images/2026-01-15_pic-0002.jpg) | ![img](images/2026-01-16_pic-0001.jpg) | ![img](images/2026-01-17_pic-0002.jpg) | 
| ![img](images/2026-01-18_pic-0029.jpg) | 
--&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-04_pic-0036.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-05_pic-0016.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-11_pic-0007.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-12_pic-0004.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-14_pic-0001.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;I also managed to get out for one run along the riverbank, which is done up like we would expect in a modern city and it was sublime to maplessly make my way down and back through it.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-07_pic-0033.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-07_pic-0036.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-07_pic-0039.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-07_vid-0021.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-07_pic-0043.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-07_pic-0047.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-07_pic-0049.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;trucks-of-shenzhen&quot;&gt;Trucks of Shenzhen&lt;/h3&gt;

&lt;p&gt;All of this requires that a lot of mass move around: dirt, microcontrollers, fruits and fish. One of the moments that gave me chills was when I noticed one of the dump trucks from my window’s construction site moving around the city without emitting the whistle of a turbodiesel; instead it was the coil wine and gear-meshing sound of an EV. Rather than a tailpipe I saw an absolute monster of an electric motor bolted onto the rear axle of the truck. In the end I think about 40% of these particular model haulers I saw had been converted to EV’s.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-14_pic-0029.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Believe it or not, this is an EV.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Believe it or not, this is an EV.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;I had been prepared to see lots of EV sedans and scooters, but I think I was still of the mind that workhorses were going to be trusty old diesels. This was not the case and it was joyous to see boxy, boring and dirty old vans and haulers rolling around via the magic of motor control rather than interal combustion.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-16_pic-0006.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-16_pic-0010.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0010.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;the-markets-hqb-and-yihua-and-lcsc-and-jlc&quot;&gt;The Markets: HQB and Yihua (and LCSC and JLC)&lt;/h3&gt;

&lt;p&gt;So far we have buildings (made of stuff) and trucks (moving stuff) but what about the buying and selling of stuff? For this we have Huaqiang Bei (HQB) and “Yihua Electron Plaza” (per the maps translation) and pitched to me as “the factory market.”&lt;/p&gt;

&lt;p&gt;HQB is a famous landmark that most electrical engineers will have heard about at some point - a place where you can actually go to the Murata stall and buy a reel of 10000 resistors, capacitors etc. There are already really good write-ups and &lt;a href=&quot;https://hackaday.com/2024/03/13/review-the-new-essential-guide-to-electronics-in-shenzhen/&quot;&gt;resources&lt;/a&gt; for the market and its navigation but I will relay my own observations.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0058.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0049.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0047.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0011.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0028.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0054.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;I don’t think I’ve ever had my visual cortex as stimulated as it was walking through HQB. It is chaotic, disorientating and on first glance lacks any real organizing principle. It is a bazaar and I am still trying to piece together how this could be an efficient way to do business in an era where I can go on &lt;a href=&quot;https://digikey.com&quot;&gt;DigiKey&lt;/a&gt; and find exactly the component I need against some millions of SKUs. I would have to suppose that it is the speed: DigiKey still takes a few days to arrive - but surely if I need thousands I am planning at least those few days in advance?&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-10_pic-0020.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;In the market for Mil-Spec Connectors, LEDs, or Robot Actuators ?&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0010.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;In the market for Mil-Spec Connectors, LEDs, or Robot Actuators ?&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-10_pic-0023.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;In the market for Mil-Spec Connectors, LEDs, or Robot Actuators ?&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;In the market for Mil-Spec Connectors, LEDs, or Robot Actuators ?&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;From what I gather, HQB has evolved from a proper electronics-components market (which would have made more sense in the proto-internet era of its origin) to what it feels like now, which is half components market and half shopping mall for knockoffs of Shenzhen-manufactured goods. I guess I was hoping to find the inside of McMaster Carr, Digikey and AutomationDirect, but it was more like DigiKey + BestBuy + … You know what, it’s not like anything else, it is HQB.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0015.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;From Wartime Goods to Knockoff Airpods and Dyson to Flying Cars...&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0025.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;From Wartime Goods to Knockoff Airpods and Dyson to Flying Cars...&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0006.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;From Wartime Goods to Knockoff Airpods and Dyson to Flying Cars...&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;From Wartime Goods to Knockoff Airpods and Dyson to Flying Cars…&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;DigiKey but... not online. Shoutout to MeanWell who make a solid PSU. Should&amp;#39;ve taken one of me at the Murata booth, but missed the opportunity.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-09_vid-0014.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0053.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;DigiKey but... not online. Shoutout to MeanWell who make a solid PSU. Should&amp;#39;ve taken one of me at the Murata booth, but missed the opportunity.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-10_pic-0019.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;DigiKey but... not online. Shoutout to MeanWell who make a solid PSU. Should&amp;#39;ve taken one of me at the Murata booth, but missed the opportunity.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;DigiKey but… not online. Shoutout to MeanWell who make a solid PSU. Should’ve taken one of me at the Murata booth, but missed the opportunity.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;The part that I did enjoy were the tools: here you can get the worlds best tweezers for PCB assembly, as well as some very cute &lt;a href=&quot;https://www.youtube.com/watch?v=5iwtDwJlbWk&quot;&gt;but dubiously spec’d&lt;/a&gt; handheld oscilloscopes, thermal cameras, etc. Also: spectacular microscopes. Third dimension? Never heard of it.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-10_vid-0004.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-10_vid-0007.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;I saw microscopes and BGA rework stations and countless fixtures and jigs. There is a block where you can get &lt;a href=&quot;https://www.youtube.com/watch?v=leFuF-zoVzA&quot;&gt;every part to make an iPhone&lt;/a&gt; and then also the tools to put it together with.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0041.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0044.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0040.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0043.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0051.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Steve !&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Steve !&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;I am a machines fan and I was hoping to find &lt;em&gt;those&lt;/em&gt; components: linear slides, motors, etc. HQB was not the spot for this, but &lt;em&gt;Yihua&lt;/em&gt; (about an hour’s drive west of HQB) was closer. This is the place to shop if you are building or running a factory: we have industrial motors of all sizes, knockoff (or maybe genuine?) festo cylinders / slides / valves, belts, bearings, metrology equipment, cutters, drill bits, &lt;em&gt;literal heaps&lt;/em&gt; of nuts and bolts and screws, etc.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0014.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;On the road to, and arriving at &amp;#39;Yihua Electron Plaza&amp;#39; - an industrial market.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0015.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;On the road to, and arriving at &amp;#39;Yihua Electron Plaza&amp;#39; - an industrial market.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;On the road to, and arriving at ‘Yihua Electron Plaza’ - an industrial market.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0088.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Yihua&amp;#39;s main atrium.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Yihua’s main atrium.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0020.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Belts, PLCs, and Cutters...&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0022.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Belts, PLCs, and Cutters...&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0065.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Belts, PLCs, and Cutters...&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Belts, PLCs, and Cutters…&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0028.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Compressors, metrology, motors...&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0029.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Compressors, metrology, motors...&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0081.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Compressors, metrology, motors...&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Compressors, metrology, motors…&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0037.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Pneumatic actuation, grippers, and soft grippers.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0039.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Pneumatic actuation, grippers, and soft grippers.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0041.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Pneumatic actuation, grippers, and soft grippers.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Pneumatic actuation, grippers, and soft grippers.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0068.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Shoutout to the CNC Booth&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Shoutout to the CNC Booth&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0048.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Couplers, slides, and motors.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0049.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Couplers, slides, and motors.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0055.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Couplers, slides, and motors.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Couplers, slides, and motors.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;Maybe most interesting were the &lt;em&gt;factory accoutrements&lt;/em&gt; - safety stickers, QC stickers, PPE, etc. The junk drawer of the factory / all of the things that you don’t remember you will need until you need them.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0078.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;PPE (those are fingertip condoms), stickers, and ESD Flip-Flops.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0079.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;PPE (those are fingertip condoms), stickers, and ESD Flip-Flops.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0073.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;PPE (those are fingertip condoms), stickers, and ESD Flip-Flops.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;PPE (those are fingertip condoms), stickers, and ESD Flip-Flops.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;Oh, and the nuts-and-bolts of it all:&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0103.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Hardware, babey. Bags of it.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0044.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Hardware, babey. Bags of it.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0045.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Hardware, babey. Bags of it.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Hardware, babey. Bags of it.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h4 id=&quot;working-in-the-markets&quot;&gt;Working in the Markets&lt;/h4&gt;

&lt;p&gt;The thing I saw in both markets that helped me to understand them better was that &lt;em&gt;people are working there&lt;/em&gt; - they are packaging and preparing their wares for shipping, modifying them for semi-custom orders, and &lt;em&gt;haggling&lt;/em&gt; - age old market stuff.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-10_pic-0021.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0034.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-10_pic-0014.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0084.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0057.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-15_pic-0090.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-09_vid-0004.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-09_vid-0005.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-15_vid-0010.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;They were there with their children on the weekend and after school, and they were kind of &lt;em&gt;living&lt;/em&gt; there… being middlemen and old school retailers. It all has an informality that has been washed out of the western world: people negotiating with one another to try to find the right price and arrangement that could benefit both. I think the thing that really struck me was that informality and that &lt;em&gt;flatness&lt;/em&gt; - you can probably get anything there if you can &lt;em&gt;find a guy,&lt;/em&gt; in fact two folks from our residency have people they call &lt;em&gt;finders&lt;/em&gt; who are professional market navigators. In the west we try to stand up searchable systems to do this, i.e. the digikey hierarchy that I pointed at earlier. Those are great but lack the flexibility of negotiating directly with another human and when we need to put a new thing on that market it takes time to get into &lt;em&gt;the system.&lt;/em&gt; As a result we have some less variety and some lag between offerings and availability.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-09_pic-0039.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Special shoutout to Terry for showing us around HQB!&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Special shoutout to Terry for showing us around HQB!&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h4 id=&quot;e-commerce-and-maas&quot;&gt;E-Commerce and MaaS&lt;/h4&gt;

&lt;p&gt;Besides some wonderful hand tools, I did not find the ballscrews, loadcells or spindle motor that I am “in the market for” at either of these places. For those I will have to use online vendors as is my SOP.&lt;/p&gt;

&lt;p&gt;Going to HQB and Yihua helps me to understand things like aliexpress: it is a bazaar and it is &lt;em&gt;flat&lt;/em&gt; in the same way that the physical counterpart is. Searchable but not organized, and never static. One of the things that I leave Shenzhen thinking about is how much those markets will do what I think of as &lt;em&gt;modernizing&lt;/em&gt; - moving towards more stable, organized systems that are perhaps more consolidated and (would seem to be) more efficient. I have a sense that what I see as organization may feel like an &lt;em&gt;encumberance&lt;/em&gt; to the natives of HQB, but I am not sure.&lt;/p&gt;

&lt;p&gt;On the horizon in this regard are vendors like &lt;a href=&quot;https://lcsc.com&quot;&gt;LCSC&lt;/a&gt; (Shenzhen’s DigiKey) and &lt;a href=&quot;&quot;&gt;JLCMC&lt;/a&gt; (McMaster Carr). These are more organized and maintain the low costs of the physical markets. &lt;a href=&quot;&quot;&gt;TaoBao&lt;/a&gt; (Amazon but faster) would also be worth understanding: it is even flatter than the markets, is online, and can deliver most wares same-day.&lt;/p&gt;

&lt;p&gt;Last thought on the markets (real and virtual) is that they are overall &lt;em&gt;soupier&lt;/em&gt; than what we have in the west; my current model for them is that they are the chaotic broth of a manufacturing ecosystem that is itself diffuse, heterogeneous and messy in the same way that some of the buildings are: there is so much &lt;em&gt;new&lt;/em&gt; happening that there is not any spare energy to clean up what is already there.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;engineers-and-artists-from-a-world-of-manufacturing&quot;&gt;Engineers (and Artists) from a World of Manufacturing&lt;/h3&gt;

&lt;p&gt;OK: trucks and markets are a side effect of the main activity. Historically that main activity in Shenzhen has been the manufacture of goods designed in the west, but this part is also changing.&lt;/p&gt;

&lt;p&gt;In my short jaunt I got to visit a few sites where &lt;em&gt;design&lt;/em&gt; is happening in Shenzhen and I want to show you some of those. The pretext for this section is that one of the core assumptions that western businesses make when they offshore their manufacturing is that they can do away with the dirty, “boring” and low-margin parts of their business while maintaining their innovative edge: their talented engineers, their IP, and their new product pipelines. This has always seemed like shoddy boardroom logic to me, so I was curious to meet some Shenzhen engineers and their work.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-07_pic-0015.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-07_vid-0006.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-07_vid-0005.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;Firstly we have these exhibits at the UABB where the artists are toying with robotics. These two were beautiful and were expertly crafted, which I think is points-for the notion that the practice of robot building is sufficiently diffuse in the population. Being at the Media Lab at MIT I have seen my share of &lt;em&gt;“at the intersection of art and technology”&lt;/em&gt; type sh-, what I saw here was ~ up to or above the level of most of what I’ve seen from the lab.&lt;/p&gt;

&lt;p&gt;For machines-hacking in particular, take for example this knitting piece (below): they’ve added computer controlled linear slides to one of the OG hand-knitting machines, and a Jacquard system (Jacquard was an expert systems integrator himself, by the way), and then fed one into the other. Art! I think, but also a solid little piece of engineering, done casually for the sake of it.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-07_vid-0008.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;We visited also AIRS, the &lt;em&gt;Shenzhen Institute of Artificial Intelligence and Robotics for Society.&lt;/em&gt; Here were numerous robots designed to serve infrastructure projects: basically a suite of inspection and maintenance robots. Two notes on this: (1) it is very forwards-looking and practical of this institute to work direclty on such obviously forthcoming issues and (2) that the funding model is not confounded by the requirement for academic novelty, they are simply making functional things that directly serve a purpose. The innovation &lt;em&gt;happens as a byproduct of doing real things&lt;/em&gt; and (from the few controls posters I saw) is driven by the desire to i.e. build better tools and more generalizeable systems for the next practical thing. This in counterpoint to what feels like a common practice where “innovation” is the primary goal and then “applications” are invented to suit demonstration of the innovation. I think that a lot of research groups get lost in trap where novel systems, architectures, etc are pursued for the purposes of publishing. Grounding them to real applications &lt;em&gt;is furiously difficult&lt;/em&gt; because we have to also make whatever new invention &lt;em&gt;work&lt;/em&gt; and if it’s robotics etc, &lt;em&gt;work well and reliably.&lt;/em&gt; When the grad students who are working in your lab don’t have any grounded experience making i.e. robotic systems, it is even &lt;em&gt;more&lt;/em&gt; difficult to ground whatever novel invention in real applications.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-08_pic-0012.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Academic output from AIRS&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-08_pic-0013.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Academic output from AIRS&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-08_pic-0014.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Academic output from AIRS&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-08_pic-0015.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Academic output from AIRS&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Academic output from AIRS&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-08_pic-0017.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Pipe Inspection, Underpass Inspection, etc... there&amp;#39;s a robot for that.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-08_pic-0022.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Pipe Inspection, Underpass Inspection, etc... there&amp;#39;s a robot for that.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-08_pic-0029.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Pipe Inspection, Underpass Inspection, etc... there&amp;#39;s a robot for that.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Pipe Inspection, Underpass Inspection, etc... there&amp;#39;s a robot for that.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-08_vid-0003.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Pipe Inspection, Underpass Inspection, etc… there’s a robot for that.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;I want to stick a note in here about &lt;em&gt;composability&lt;/em&gt; of robotic systems: at AIRS were lots of projects that were in the vein of “this thing strapped to this thing, plus some new stuff.” It’s the kind of thing that happens when we have a genuine sense of urgency about systems development (and a small amount of personal ego involved) I.E. it’s exceptionally common in wartime, observers of the current trends will know. I’m not going to do a whole tirade on that, but I think it’s a sign of real systems familiarity, and is a practice I’m interested in enabling more broadly. The guts of my own thesis is basically “tools for strapping different embedded systems together.”&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-08_pic-0019.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;It&amp;#39;s a billet world.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-08_pic-0021.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;It&amp;#39;s a billet world.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;It’s a billet world.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;blockquote&gt;
  &lt;p&gt;One of my running fantasies is to live in the &lt;em&gt;billet world&lt;/em&gt; - where machining is so easy and cheap that, for almost any part, we might as well just carve it out of a block (a “billet”) of metal. The design is simpler and faster that way, but the trade-off is normally cost and time to machine. The component in the robot above (zoom at right) aught to be an extrusion of some kind, or a square tube. It would be cheaper, it would be simpler. However, these engineers seem to be living in the billet world: where CNC machining is so abundant that we can just throw it at everything.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The output we saw at AIRS was impressive because it was immanently real, and clearly made by people who were &lt;em&gt;versed in the arts&lt;/em&gt; - who had &lt;em&gt;studied the blade&lt;/em&gt; of mechatronics. Same with the art pieces we saw. This is a difficult thing to measure or account for, but you know it when you see it. Over at MIT we have for example this decades-long push to bring &lt;em&gt;projects based education&lt;/em&gt; into the mainstream - that basically aims at the same outcome: that we could learn our heady maths etc while also reckoning with real systems and messy debugging. The alternative might just be to soak the world in a milieu of making things, then surely at least some handfuls of pupils will have been raised in that broth and the schools can be centers for excellence where practical skills learned in the world (where they naturally belong) are honed and refined with more abstract studies.&lt;/p&gt;

&lt;h4 id=&quot;seeed-hq&quot;&gt;Seeed HQ&lt;/h4&gt;

&lt;p&gt;On the Monday following I visited &lt;a href=&quot;https://www.seeedstudio.com&quot;&gt;Seeed’s&lt;/a&gt; office / HQ to discuss the possibility of my partnering with them to produce some integrated &lt;a href=&quot;https://ekswhyzee.com/2025/03/24/foc-stepper.html&quot;&gt;motor controllers&lt;/a&gt; via &lt;a href=&quot;https://github.com/jakeread/nema-motor-controller-iface-proposal&quot;&gt;this interface&lt;/a&gt;. I met there with Seeed’s CEO Eric Pan and three of his engineers who are working towards expanding their ecosystem of robotic components. I hope to spend more time explaining my hopes and dreams for that project (a family of motors, motion controllers, and other modules…) but this particular rambler / travelogue / brain dump is not the time for it.&lt;/p&gt;

&lt;p&gt;I do want to recount two things about the visit to Seeed. The first is that their offices are &lt;em&gt;large&lt;/em&gt; and filled with interesting work - one full floor of a full size office building. I arrived around 10am and I would guess that I saw perhaps 120 people at work there. It is in a modern building and had a cozy, lived-in energy. There is a big fish tank with plants growing out of the top, people were brewing tea at their desks as they chatted about their projects. Their desks were messy in the way that a productive desk is messy: they had their products strapped down beside their keyboards, a number of those were machines or robot parts that were moving around as they worked on their control systems, interfaces and mechanical designs. Some of them were busy taking apart their competitors products, some had circuit samples and board design tools open, some were reworking and probing circuits, some were locked in writing firmware, and some were writing documentation. There was a lot of &lt;em&gt;real shit&lt;/em&gt; going on, and it was a joy to see.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-14_pic-0002.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;The Seeed Offices: Bustlin&amp;#39; !&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;The Seeed Offices: Bustlin&amp;#39; !&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-14_vid-0002.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;The Seeed Offices: Bustlin’ !&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;I will hedge that this trip / residency is largely funded by Seeed and that my lab has an existing relationship with them - they worked with my thesis reader Nadya way back in 2011 and with my old colleague and pal Zach Fredin to make some kits for &lt;a href=&quot;https://gitlab.cba.mit.edu/zfredin/homelab&quot;&gt;How to Make Almost Anything&lt;/a&gt; in 2020. We consume probably a few hundred of their &lt;a href=&quot;https://www.seeedstudio.com/xiao-series-page&quot;&gt;Xiao&lt;/a&gt; dev boards every year, etc (they are friends of the lab). However they are friends of the lab for a reason and that is this, the second thing about Seeed, which is that they have an intentional relationship with open source. If you are not on this blog for the first time you will know that &lt;a href=&quot;https://ekswhyzee.com/2023/10/16/searching-for-oshw.html&quot;&gt;it is a dear topic for me&lt;/a&gt; and so I am definitely doing the heart-eyes when I talk about this but! Enough hedging.&lt;/p&gt;

&lt;h4 id=&quot;an-aside-on-open-hardware&quot;&gt;An Aside on Open Hardware&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;1000 words in this aside, skip to the next section it if you want to see more Shenzhen and less &lt;em&gt;discussion&lt;/em&gt; …&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;From what I can tell Seeed’s attitude towards open source is that, once something is sufficiently figured out &lt;em&gt;in the manufacturing ecosystem,&lt;/em&gt; there aught to be available a good open implementation of it that will allow novices to get their hands on it, use it, and understand it. They are also no-nonsense about it: they expect to turn a profit and they &lt;em&gt;can do this&lt;/em&gt; because they are in Shenzhen where developing and releasing products is cheap.&lt;/p&gt;

&lt;p&gt;Seeed runs a &lt;a href=&quot;https://www.seeedstudio.com/co-create.html&quot;&gt;co-create&lt;/a&gt; program where they partner with open source designers to license and produce open designs and split revenue.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/co-create-explainer.png&quot; alt=&quot;co-create&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This program is actually a nice nexus for the whole of Open Harware’s quandry; &lt;em&gt;Open Software&lt;/em&gt; can work because the cost of one additional software unit is zero (besides maintenance), hardware has fixed &lt;em&gt;and&lt;/em&gt; recurring costs. We can make money by selling the things we have designed (not many people actually want to go to all the effort to DIY a circuit even if the design is free, also our costs at volume (plus margin) are about on par with the cost for a one off), but if our thing is successful enough surely others will start selling them too. This leaves us with two questions: (1) why doesn’t Seeed just start making and selling Open Hardware projects that they think would do well in the market (why involve and then pay &lt;strong&gt;50%&lt;/strong&gt; to the creators, other than for good will?) and (2) why get into the business of making products where, if the product does well, you have already made it easy for your competitors to try to make the same part and undercut you?&lt;/p&gt;

&lt;p&gt;This is still an emerging model and it sits in the dangerous gulf between prototype-project and commercial project. Typically open hardware doesn’t make it to the commercial step and in fact lots of major open hardware projects that have gone there eventually either go closed source (i.e. &lt;a href=&quot;https://odriverobotics.com/&quot;&gt;odrive&lt;/a&gt;) or die (i.e. &lt;a href=&quot;https://tropical-labs.com/mechaduino/&quot;&gt;mechaduino&lt;/a&gt;). I think figuring this out is well worth everyone’s time, and there’s some evidence that Seeed is on the right track: the &lt;a href=&quot;https://foldscope.com/&quot;&gt;foldascope&lt;/a&gt; first launched with Seeed, and in robotics they are now collaborating with &lt;a href=&quot;https://www.pollen-robotics.com/&quot;&gt;reachy&lt;/a&gt; / &lt;a href=&quot;https://www.seeedstudio.com/Amazing-Hand-Right-Hand-The-Open-Source-Robotic-Hand-Developer-Kit.html&quot;&gt;pollen robotics&lt;/a&gt;. Robotics requires a lot of systems integration, something that open source &lt;em&gt;can&lt;/em&gt; be really good at, so it will be fun to see how this all shakes out.&lt;/p&gt;

&lt;p&gt;In any case, back to our questions. Why does anyone do this?&lt;/p&gt;

&lt;p&gt;The first hidden dynamic has to do with the project creators. If you read &lt;a href=&quot;https://dash.harvard.edu/entities/publication/73120378-c0ba-6bd4-e053-0100007fdf3b&quot;&gt;Coase’s Penguin&lt;/a&gt; or &lt;a href=&quot;https://press.stripe.com/working-in-public&quot;&gt;Working in Public&lt;/a&gt; (both excellent), you will see that open source is a top tier labour market. That is, the people who select to work on some project and give it away for free are &lt;em&gt;probably really good at it,&lt;/em&gt; or at least uniquely passionate about it. This makes them the ideal “hire” to work on that project, and means that a company like Seeed (who might need a project manager anyways) might as well work with the original creator. This person is going to be best suited to help take the rough edges off of the project and figure out how to fit it into a broader market. They are also very likely already the leader in the community of people who would use the thing: this is a perfect sales channel. They are &lt;em&gt;also&lt;/em&gt; going to be good at supporting customers who buy the thing, i.e. through those user communities and with documentation authorship etc.&lt;/p&gt;

&lt;p&gt;The second is just plain old market competition: by building relationships within the open hardware community, Seeed is hooked up to a pipeline of potentially innovative products that their competitors might not see coming. This is why they fund the residency and were so (surprisingly to some of us) excited to help us out. If they were to turn around and run away with something without involving the project creator, the (small) community of open hardware folks would share that and they would loose this competitive advantage. On the side of the project creators, there is the same market of contract manufacturers and it would not be exceptionally difficult to ask one of them to do what Seeed can do and simply do it first without them.&lt;/p&gt;

&lt;p&gt;Finally there is a unique attitude to copying and IP in Shenzhen, which is that it is inevitable (there is a stall in HQB where you can have someone reverse engineer a PCB in its entirety for you: every trace, every part, and as much of the firmware as they can extract). IP law is ineffective and slow, so the more surviveable option is actually to (again) &lt;em&gt;get good&lt;/em&gt; - be the first mover, and be working on the improved versions or newer products while your competitor is spinning up a line to make the thing that you already have in the warehouse. If your competitor is following you they are going to have thinner margins (they have to undercut you and their input feedstocks come from the same ecosystem as yours) - &lt;em&gt;and&lt;/em&gt; your relationship to the creator means that all of the offical documentation etc (which is always growing) points to your storefront and not theirs. For a few dollars of price difference, any busy engineer is going to prefer the trusted vendor over the knockoff.&lt;/p&gt;

&lt;p&gt;Unfortunately this model doesn’t exactly scale: if the product becomes so well established that it becomes a fundamental piece of kit, it essentially becomes a commodity and other manufacturers will be eating lots of the pie (which is still growing, by the way). This happened to i.e. the &lt;a href=&quot;https://www.pololu.com/product/1182&quot;&gt;pololu&lt;/a&gt; &lt;a href=&quot;https://www.amazon.com/s?k=a4988+stepstick&quot;&gt;stepstick&lt;/a&gt; and to the &lt;a href=&quot;https://store-usa.arduino.cc/products/arduino-nano&quot;&gt;arduino&lt;/a&gt; &lt;a href=&quot;https://www.amazon.com/s?k=nano+board&quot;&gt;nano&lt;/a&gt; etc. But for creators &lt;em&gt;this is actually kind of cool&lt;/em&gt; - the thing you made is an important part of the ecosystem now, and you probably made enough money to keep doing &lt;em&gt;your&lt;/em&gt; thing, which is to design new things (not run a race-to-the-bottom business). &lt;a href=&quot;https://build-its-inprogress.blogspot.com/2019/03/hello-there-mini-cheetah.html&quot;&gt;Ben Katz’ Robot Actuator&lt;/a&gt; is a famous example of this &lt;a href=&quot;https://hackaday.com/2022/12/19/mini-cheetah-clone-teardown-by-none-other-than-original-designer/&quot;&gt;phenomenon:&lt;/a&gt; that basic design pattern (hand-wavey) led to the emergence of i.e. &lt;a href=&quot;https://shop.unitree.com/products/unitree-go2&quot;&gt;Unitree Go2&lt;/a&gt; (who perfected it) by way of a proliferation of aliexpress clones of his original design. When I got to ask him about his feelings on this (it was the aliexpress proliferation era, not yet the Unitree era), he kind of shrugged and said “I think it’s kind of flattering.” I think he currently lives in rural-ish Western Mass, probably pulls a mondo salary (due to his actuator fame), and builds cool shit in his shed. Not bad.&lt;/p&gt;

&lt;p&gt;The subtlety I think is that open source works well for what I would call “core competency components” - actuators, sensors… &lt;em&gt;modules&lt;/em&gt; that are re-used in lots of applications. People who develop these tend to do it for the love of it, not for perpetual profit. I could go on but I won’t, if any of this is interesting you should read &lt;a href=&quot;https://ekswhyzee.com/2023/10/16/searching-for-oshw.html&quot;&gt;this&lt;/a&gt; and stick around this blog for a longer update on the topic.&lt;/p&gt;

&lt;h4 id=&quot;western-firms-in-shenzhen&quot;&gt;Western Firms in Shenzhen&lt;/h4&gt;

&lt;p&gt;I was also lucky enough to visit a Cambridge-Native engineering firm’s Shenzhen office. Their founder is an MIT alum and I’ve met many of their engineers during my time in Cambridge and it is an institution of its own right: time at this firm is the practical equivalent to an MIT masters degree.&lt;/p&gt;

&lt;p&gt;This firm has long worked with contract manufacturers in the Shenzhen area and obviously they are impressed enough with the engineering talent there to have launched this office. It is in Nanshan and takes up one full floor in a relatively new office park. I probably saw about 60 folks at work there (a Tuesday around noon). It feels like a failing on my own bias that I would have to say this but it was totally… the same as what you would expect in an engineering office in any western city. Maybe skewing younger, but it was just a bunch of college grads (they even had lattes) doing bigbrain work in front of fancy monitors in an open plan office. It was &lt;em&gt;nice&lt;/em&gt; - it had regular cash-flowey office niceties, trees, a solid cafe downstairs and tasty lunch spots nearby. Same as the Seeed HQ: despite being halfway around the world in a country that can seem kind of opaque from a distance, the vibes are astoundingly familiar.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Regular day at the office...&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-09_vid-0010.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Regular day at the office…&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;For another seat-of-the-pants jaunt into economics town (shorter this time), the salary arbitrage value I have heard thrown around is about 30%. For a talented mechanical engineer an American startup might pay $220k USD, for the same in Shenzhen about $70k USD (source: am in this market). Since these businesses are all international anyways (and are probably already shipping at least &lt;em&gt;part&lt;/em&gt; of their product out of the region), this amounts to being able to hire three Chinese engineers for every American - or one in China before you could hire any in America. Add to that the fact that finding a junior engineer in America who has actually laid their hands on or repaired a piece of industrial equipment is (anecdotally, source not disclosed) difficult. Add again that these businesses spend good money and time flying their American engineers into Shenzhen to liase with their Contract Manufacturers (including i.e. interpreters and &lt;em&gt;finders&lt;/em&gt; types of middlemen).&lt;/p&gt;

&lt;p&gt;So, a picture starts to emerge… the results of funding the living daylights out of your educational institutions (and doing a little bit of currency devaluation as a treat). Also the value of having raised a generation of engineers whose parents and neighbours were either building, maintaining, or working in factories (and the related mechanical arts) - that’s the broth.&lt;/p&gt;

&lt;h3 id=&quot;factories-automation-tools&quot;&gt;Factories, Automation, Tools&lt;/h3&gt;

&lt;p&gt;OK. We have the markets (goods exchange) and the engineers (goods design), what about the business end, the muscle of it all - &lt;em&gt;value adders ?&lt;/em&gt; If you are like me you probably came here to see some gd &lt;em&gt;machines&lt;/em&gt; doing stuff. Finally we have arrived.&lt;/p&gt;

&lt;h4 id=&quot;seeeds-flex-factory&quot;&gt;Seeed’s Flex Factory&lt;/h4&gt;

&lt;p&gt;The first visit was to Seeed’s factory, which is a small but flexible operation relative to what is typical in Shenzhen. They have one board assembly line: paste squeegee -&amp;gt; inspect -&amp;gt; smt (small parts) -&amp;gt; smt (large parts) -&amp;gt; inspect -&amp;gt; selective reflow solder (through holes) -&amp;gt; final inspection.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-06_vid-0026.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;PNP, Inspection, and Selective Wave Solder&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-06_vid-0025.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;PNP, Inspection, and Selective Wave Solder&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-06_vid-0029.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;PNP, Inspection, and Selective Wave Solder&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-06_vid-0031.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;PNP, Inspection, and Selective Wave Solder&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0057.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Seeed&amp;#39;s SMT Line&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0058.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Seeed&amp;#39;s SMT Line&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0063.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Seeed&amp;#39;s SMT Line&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0061.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Seeed&amp;#39;s SMT Line&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Seeed’s SMT Line&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;For the one line there is a &lt;em&gt;lot&lt;/em&gt; of supporting storage. For scale reference the SMT line fits into about 20x60 feet, maybe 10% of one floor in their (two floors) of factory. Much of the rest of the space is inventory: parts (for PCBs and other products that are assembled here, so i.e. injection moulded parts, antennas, hardware, other odds and ends).&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0045.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Seeed&amp;#39;s stockroom&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0046.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Seeed&amp;#39;s stockroom&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0047.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Seeed&amp;#39;s stockroom&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0048.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Seeed&amp;#39;s stockroom&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Seeed’s stockroom&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;Also stored are PCB solder stencils (for that paste machine), and test jigs. Lots of test jigs. These are used to i.e. load bootloaders into microcontroller dev boards, and also run full tests on electronics modules.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0053.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Stencil Storage and Test Jigs&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0050.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Stencil Storage and Test Jigs&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Stencil Storage and Test Jigs&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-13_vid-0002.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Stencil Storage and Test Jigs&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;Ahead of the SMT line is a parts sorting room / kind of clearing house which is properly chaotic at first glance but for which they have a fairly sophisticated stockroom tracking system. These are the boring parts.&lt;/p&gt;

&lt;p&gt;One surprise for me was that for PCBA runs &lt;em&gt;less than 500 or 1000 units&lt;/em&gt; (depending on the board complexity), assembly is done &lt;em&gt;by hand&lt;/em&gt; - as in, someone gathers the parts, applies solder paste with the stencil (by hand) and then tweezers the parts onto the board.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0049.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Tweezertown.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0055.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Tweezertown.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Tweezertown.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;This is what we do in the basement of the CBA for board fab at low quantities, and I really thought that when I was sending boards out to be assembled at i.e. JLCPCB, PCBWay or Seeed, they were running them through pick and place lines. I appear to have been wrong.&lt;/p&gt;

&lt;p&gt;As part of the residency I sent &lt;a href=&quot;https://github.com/jakeread/knuckles-imu-lsm6-lis2-rp2354a&quot;&gt;a board&lt;/a&gt; through Seeed’s PCBA service. They invited me for a second visit to see this happen and indeed it was a team of folks with tweezers assembling my boards.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0033.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Parts, Docs, and Kitting.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0041.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Parts, Docs, and Kitting.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0042.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Parts, Docs, and Kitting.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0043.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Parts, Docs, and Kitting.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Parts, Docs, and Kitting.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;It was a little bit strange watching these folks do this for me, but also cool to see how the pros do it… it is mostly the same as what I do but they are faster and more organized than I am. They also have the good tools: these tweezers are the same that I eventually picked up to take home from the Yihua market. They have hardened steel tips that are &lt;em&gt;quite&lt;/em&gt; sharp and are a different material than the body of the tweezer, a little bit like a proper chef’s knife with a layer of hardened tool steel in the blade that is forged together with mild steel jackets for impact resistance.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Stencil to apply solderpaste, then tweezering components. And those mf tweezers.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-13_vid-0003.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0046.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Stencil to apply solderpaste, then tweezering components. And those mf tweezers.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0004.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Stencil to apply solderpaste, then tweezering components. And those mf tweezers.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Stencil to apply solderpaste, then tweezering components. And those mf tweezers.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-13_vid-0010.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Stencil to apply solderpaste, then tweezering components. And those mf tweezers.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;Other &lt;em&gt;tools,&lt;/em&gt; like the jig they used to solder stencil the &lt;em&gt;second&lt;/em&gt; side of the board (after the first was soldered) were ad-hoc, i.e. a stack of paper.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0052.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Reflow, Human Inspection, and Machine Inspection.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0056.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Reflow, Human Inspection, and Machine Inspection.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0057.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Reflow, Human Inspection, and Machine Inspection.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Reflow, Human Inspection, and Machine Inspection.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;They also sent the boards through their inspection tool, which uses a &lt;a href=&quot;https://en.wikipedia.org/wiki/Telecentric_lens&quot;&gt;telecentric&lt;/a&gt; lens to image the board as a flat scan.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Machine Inspection and Flat-Scan Images.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-13_vid-0016-timecrop.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0066.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Machine Inspection and Flat-Scan Images.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0067.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Machine Inspection and Flat-Scan Images.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0065.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Machine Inspection and Flat-Scan Images.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Machine Inspection and Flat-Scan Images.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;This board is one of my &lt;em&gt;knuckles&lt;/em&gt; set that is long overdue to get out into the world. Have a thesis to finish first, but the long and short of it is that it’s an IMU module based on a chip that Quentin found and that runs up past 7kHz sample rate. For machine things.&lt;/p&gt;

&lt;p&gt;The PCBA in this case was done in Seeed’s flex assembly shop, a medium sized room on the first floor where they do the least automate-able step for short run products, i.e. final assembly. They are also working on this cute little robot with &lt;a href=&quot;https://www.pollen-robotics.com/&quot;&gt;pollen robotics&lt;/a&gt; and huggingface.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0040.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Seeed&amp;#39;s Flex Assembly Room.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Seeed’s Flex Assembly Room.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0034.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Reachy Mini and Assembly.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0038.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Reachy Mini and Assembly.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0037.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Reachy Mini and Assembly.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0035.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Reachy Mini and Assembly.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Reachy Mini and Assembly.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-06_vid-0020.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Reachy Mini and Assembly.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;This factory is in a little industrial park that’s on the near-periphery of the city. The park had a good energy to it, and when we left after the second visit (around 630pm) it seemed like most folks in the area were getting ready to wrap it up for the day.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0058.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Outside the Seeed factory.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-13_pic-0060.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Outside the Seeed factory.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Outside the Seeed factory.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;h4 id=&quot;a-flex-pcba-line&quot;&gt;A Flex PCBA Line&lt;/h4&gt;

&lt;p&gt;Cedric hooked the whole group up to see a Flex PCB Assembly line as well. This was further out of the city and a fairly small operation as well. Similar to Seeed’s line, but a little less sophisticated.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Flex PCB Assembly line: automation + manual rework.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-06_vid-0018.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Flex PCB Assembly line: automation + manual rework.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-06_vid-0019.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Flex PCB Assembly line: automation + manual rework.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;Was not surprised to see lots of manual intervention. I think that there is a lot of myth making about automation around &lt;em&gt;lights out factories&lt;/em&gt; - which are in fact quite rare. Rather, most lines and especially flexible ones are still kept alive in large part by people doing spot fixes or a few of the tricky steps (or are busy reconfiguring and fixing the automation equipment).&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0022.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Real !&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0023.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Real !&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0026.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Real !&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Real !&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0029.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Factory accoutrements, lunch, and the crossing into Shenzhen&amp;#39;s Special Economic Zone.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0068.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Factory accoutrements, lunch, and the crossing into Shenzhen&amp;#39;s Special Economic Zone.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0069.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Factory accoutrements, lunch, and the crossing into Shenzhen&amp;#39;s Special Economic Zone.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Factory accoutrements, lunch, and the crossing into Shenzhen’s Special Economic Zone.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;h4 id=&quot;injection-mould-making&quot;&gt;Injection Mould Making&lt;/h4&gt;

&lt;p&gt;We saw an injection moulding shop (I missed the moulding step because I was oogling the Sink EDM machines). This is &lt;em&gt;Electric Discharge Machining&lt;/em&gt; - we take a copper tool and drive an arc between it and the workpiece (probably hardened steel) to ablate it. Great for making tiny, sharp pockets in moulds - and produces a solid surface finish.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Sink EDM: drop, discharge, wash and repeat...&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-06_vid-0003.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Sink EDM: drop, discharge, wash and repeat...&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-06_vid-0004.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Sink EDM: drop, discharge, wash and repeat…&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0014.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Sink EDM: CNC to make tooling, and an old school edge finder.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0017.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Sink EDM: CNC to make tooling, and an old school edge finder.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0018.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Sink EDM: CNC to make tooling, and an old school edge finder.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Sink EDM: CNC to make tooling, and an old school edge finder.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0010.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Sink EDM: tools, output, and fixtures.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0011.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Sink EDM: tools, output, and fixtures.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0012.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Sink EDM: tools, output, and fixtures.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Sink EDM: tools, output, and fixtures.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0009.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Punching gates in a mould, and an in-progress *unit.*&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-06_pic-0021.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Punching gates in a mould, and an in-progress *unit.*&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Punching gates in a mould, and an in-progress &lt;em&gt;unit.&lt;/em&gt;&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;h4 id=&quot;the-motor-factory&quot;&gt;The Motor Factory&lt;/h4&gt;

&lt;p&gt;As part of this potential motor project, I whipped up an &lt;a href=&quot;https://github.com/jakeread/nema-motor-controller-iface-proposal&quot;&gt;interface proposal&lt;/a&gt; for mounting closed-loop controllers to stepper motors. Seeed hooked me up with a visit to this factory for a tour and to potentially work together on the project.&lt;/p&gt;

&lt;p&gt;Short notes on that: I learned that you need to show up to this meeting with (as close as possible) a 1:1 of the thing you want to make. With the language barrier, drawings are good but mostly useless and it’s better to be able to take the actual thing apart and spec every relevant detail. This is obvious in hindsight and for me this was more of a see-if-it-is-possible activity.&lt;/p&gt;

&lt;p&gt;In any case, it seems like this factory can make just about any electric motor &lt;em&gt;smaller&lt;/em&gt; than a 355ml soda can. I actually didn’t see any steppers in their factory (this seems to be a shrinking market), but did see many BLDCs and DC motors being assembled.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-16_pic-0017.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Motors in the showroom. Last is the Dyson 100k RPM Fan that powers their modern lineup, this part and its knockoffs have become a commoddity.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-16_pic-0023.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Motors in the showroom. Last is the Dyson 100k RPM Fan that powers their modern lineup, this part and its knockoffs have become a commoddity.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-16_pic-0024.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Motors in the showroom. Last is the Dyson 100k RPM Fan that powers their modern lineup, this part and its knockoffs have become a commoddity.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Motors in the showroom. Last is the Dyson 100k RPM Fan that powers their modern lineup, this part and its knockoffs have become a commoddity.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;Lots of CNC here to make motor housings, back irons etc. Lots of young CNC operators, this is a problem in the west where most operators are kind of old hat and filling junior roles can be difficult.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-16_pic-0028.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Rows of CNC, and young operators making motor bell housings.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-16_pic-0029.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Rows of CNC, and young operators making motor bell housings.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-16_pic-0033.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Rows of CNC, and young operators making motor bell housings.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Rows of CNC, and young operators making motor bell housings.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-16_vid-0003.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Rows of CNC, and young operators making motor bell housings.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-16_pic-0044.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Laminations and Windings, and soldering by-hand windings to inerposer / connector PCBS.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-16_pic-0047.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Laminations and Windings, and soldering by-hand windings to inerposer / connector PCBS.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Laminations and Windings, and soldering by-hand windings to inerposer / connector PCBS.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-16_vid-0008.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Laminations and Windings, and soldering by-hand windings to inerposer / connector PCBS.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;This factory was at a different scale than the others. They reported 40% growth year on year for the last two and were actively expanding, I saw a new test facility going up and some factory floors being epoxied in prep for new lines. I didn’t get an answer on their total sales volume but I would estimate the whole factory was producing about one complete motor at least every few seconds. Doesn’t take much imagination to understand why demand for these types of motor has exploded in the last few years.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-16_pic-0054.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Rotors, their winding machine, and stacks of the same.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Rotors, their winding machine, and stacks of the same.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-16_vid-0005.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-16_pic-0055.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Rotors, their winding machine, and stacks of the same.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Rotors, their winding machine, and stacks of the same.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;Hadn’t seen a coil winder in person before so that was excellent. Again there are many many humans here: moving things between machines, operating them directly, testing motors one at a time, and doing fine-dexterity work like terminating windings onto housing connnectors.&lt;/p&gt;

&lt;p&gt;I’m always curious about who builds these pieces of equipment. Coil winders and other generalizeable tools are almost certainly produced by coil winder machine manufacturers, but a lot of the lines seemed to have been assembled for the specific lines, a-la these bits of kit.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-16_vid-0015.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Brushes being mounted onto rotors, motor lead preparation, and stators being mounted to rotors.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-16_vid-0020.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Brushes being mounted onto rotors, motor lead preparation, and stators being mounted to rotors.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-16_vid-0018.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Brushes being mounted onto rotors, motor lead preparation, and stators being mounted to rotors.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-16_vid-0017.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Brushes being mounted onto rotors, motor lead preparation, and stators being mounted to rotors.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;This would mean that this factory has a (proably small) team of machine builders and maintainers. They, not the particular technologies, are where automation comes from and I think we forget about them and their relative abundance / sparsity when we think about how much automation &lt;em&gt;could&lt;/em&gt; be going on but then notice that &lt;em&gt;actually&lt;/em&gt; &lt;a href=&quot;https://mit-serc.pubpub.org/pub/puzzle-of-missing-robots/release/1&quot;&gt;&lt;em&gt;not very much is&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-16_pic-0047.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;People and their HMI.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-16_pic-0056.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;People and their HMI.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-16_pic-0063.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;People and their HMI.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;People and their HMI.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;I doubt that these engineers / mechanics are not mechatronics grads or “tech” workers like we would imagine them to be in the west. They are hands on, greasy, inventive, and probably worked the lines that they later automated. I would be surprised to find a complete CAD model of any of these lines, but probably some bits and pieces.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-16_pic-0053.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Messy !&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Messy !&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;And some advanced testing equipment:&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Science !&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-16_vid-0026.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Science !&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;There were also a good number of general-purpose three-axis gantries doing i.e. glue-ups and other odd jobs.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-16_pic-0057.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;XYZ Gantries for Goo Dispensing.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;XYZ Gantries for Goo Dispensing.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-16_vid-0025.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;XYZ Gantries for Goo Dispensing.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;The orchestration of an operation like this is I think an immense task that takes many years to mature and maybe requires slow, smooth growth to come into being. This factory has been around winding copper onto laminations for twenty years: it’s hard to stand things like this up overnight. It is not sexy, but its existence as part of the ecosystem is giving direct rise to the explosion of humanoid and legged (dog) robot companies that are popping up in Shenzhen (each of which contains probably twenty-to-thirty of these types of motor, plus gearboxes). Not to mention those drones, and did you see the DJI headquarters at the start of this document? In a decade there will be a few more towers of that magnitude built by the winners of the humanoid robot race.&lt;/p&gt;

&lt;h3 id=&quot;about-those-street-sweepers&quot;&gt;About those Street Sweepers&lt;/h3&gt;

&lt;p&gt;This has been fun to write down and I hope you can tell by this point that there &lt;em&gt;is&lt;/em&gt; a point I am trying to snake out of my vacation-photos slideshow.&lt;/p&gt;

&lt;p&gt;The scale in Shenzhen is huge and is very obviously &lt;em&gt;real&lt;/em&gt; in a way that is undeniable. Though I only had glancing contact with it, it seems to be working well: people are ambitious and driven but are basically kind and fair with one another. A vast number of people make a living in the city and many of them are by now either as skilled as their western counterparts or they are tooled up to work jobs that simply don’t exist in the west. They are probably still working harder, too. The whole pie is getting bigger and everyone is scrambling to make their mark in the expanding territory rather than fighting tooth and nail over the scraps. There is an attitude there that I heard echoed a number of times by business leaders that competition simply involves &lt;em&gt;doing better&lt;/em&gt; than your adversaries (rather than trying to build technological “moats” or manipulate other structural systems), and that is paying dividends. A huge amount of this was basically directed by the government so yes we can continue to say that some of it is “fake,” but it has been realized and fine tuned in a straightforward, rules based market economy.&lt;/p&gt;

&lt;p&gt;There is obviously &lt;em&gt;much&lt;/em&gt; missing from this ad-hoc attempt at explaining any of what I saw vs. what I have seen during my time in America. I will note that I have had the priviledge of seeing a number of factories there as well (special thanks to Manufacturing at MIT and the IPC for that). My Dad also spent his career in manufacturing and for the last twenty years I have watched as he has grown a small industrial bakery’s output from about 50 to 500 thousand pounds of bread per week. I have studied the nuts and bolts of automated mechatronic systems pretty intensely over the last eight years as a grad student. I am however not a real economist, I just read about it. I have not done substantial numeric analysis on the total size of these economies, markets, etc etc. None of this is either endorsed or sponsored by anyone, I am self motivated by the desire to understand where in the world my own craft will fit in - and whether it will be useful at all.&lt;/p&gt;

&lt;p&gt;Back to the point: I think &lt;em&gt;the boring parts&lt;/em&gt; of any project - economic or factory scale - are its bedrock. We &lt;a href=&quot;https://www.bostonreview.net/forum/suzanne-berger-finance-gutted-manufacturing/&quot;&gt;traded those away&lt;/a&gt; for short term profits starting in the 1980’s and we also forgot about the most fundamental bedrock of all, which is in a well educated population. We won’t bootstrap our way to effective automation if the people on the factory floor can’t imagine how they might invent themselves out of their own jobs (and give themselves a better one in the process).&lt;/p&gt;

&lt;p&gt;If we want to bring manufacturing back to the west, we shouldn’t be making moonshots for magical lights out factories: they don’t actually exist except for to make the most advanced and high volume products, and if you want to meet the people who put them together and brought them to life you probably shouldn’t go to the university and you definitely shouldn’t ask a software engineer (sorry to my software friends I still think you’re brilliant).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But what &lt;em&gt;about&lt;/em&gt; those street sweepers ?&lt;/strong&gt;&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;New Age Sweeping.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-08_vid-0009.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;New Age Sweeping.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-20_vid-0004.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;New Age Sweeping.&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-07_vid-0002.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;New Age Sweeping.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;NVIDIA stock holders reading this piece are thinking I might be a luddite that has never seen a large language model or reinforcement learning, or that I am doing the same trick where we mix up our linear projections with those nasty exponentials. I thought about this whenever I saw these urban roombas around in the city.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 0.2rem; margin-bottom: 0.8rem;&quot;&gt;
  
    
    
    
    
    
      
    
      
    
      
    
      
    
      
    
      
    
    
    
      
        
        
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        
        &lt;video data-lightbox=&quot;true&quot; data-media-type=&quot;video&quot; data-video-type=&quot;video/mp4&quot; data-caption=&quot;Demographics Solutions ?&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; controls=&quot;&quot;&gt;
          &lt;source src=&quot;images/2026-01-05_vid-0004.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Your browser does not support the video tag.
        &lt;/video&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-08_pic-0032.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Demographics Solutions ?&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
    
    
    
    
    
      
        
        
    
    
      
    
      
    
      
    
      
    
      
    
    
    &lt;div style=&quot;flex: 1 1 0; min-width: 0; display: flex; align-items: center;&quot;&gt;
      
        &lt;img src=&quot;images/2026-01-10_pic-0027.jpg&quot; data-lightbox=&quot;true&quot; data-media-type=&quot;image&quot; data-caption=&quot;Demographics Solutions ?&quot; style=&quot;width: 100%; height: 100%; object-fit: cover; cursor: pointer;&quot; alt=&quot;&quot; /&gt;
      
    &lt;/div&gt;
  
&lt;/div&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem;&quot;&gt;
    &lt;p&gt;Demographics Solutions ?&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- Lightbox overlay (only included once per page) --&gt;

&lt;p&gt;Just about every country in the developed world has some nasty demographic issues coming up in the near future. Not to mention debt issues. By the time those dynamics come home, Shenzhen may have built more roads than it has dudes with brooms. The same is already true for American infrastructure. An LLM can’t sweep the floor or grease a wheel but a robot can, and thanks to the last thirty years of hard work they will be flush with robots in China. If we don’t get our act together we will be buying them at a mark up or we’ll be getting the robot squeeze in the same manner as we’re currently being rare-earths squeezed: we’ll realize we need them but we won’t be able to make them. Not because we don’t have enough GPUs or AI researchers or datasets, but because we won’t have enough motor factories, magnet wire plants, neodymium, aluminum foundries and extruders, screw machines, etc.&lt;/p&gt;

&lt;p&gt;We could maybe get the robots to build all of that, but whether or not a robot with an LLM for brains can debug or build a factory is a question that I hope this tour of the messy reality of manufacturing might help you to answer. I don’t even want to hedge against the possibility of AGI because planning on that rather than doing our homework is tantamount to economic suicide. It additionally proposes that the AGIs won’t need any machinery, warehouses, or feedstocks to work with.&lt;/p&gt;

&lt;p&gt;It would also be worth asking about who will own those robots and tools, but if we want to participate in that discussion we aught to start by being able to build them.&lt;/p&gt;
</description>
        <pubDate>Tue, 20 Jan 2026 00:00:00 -0500</pubDate>
        <link>http://ekswhyzee.com/2026/01/20/shenzhen.html</link>
        <guid isPermaLink="true">http://ekswhyzee.com/2026/01/20/shenzhen.html</guid>
        
        <category>travelogue,</category>
        
        <category>economics,</category>
        
        <category>manufacturing</category>
        
        
      </item>
    
      <item>
        <title>The Blair Winch Project</title>
        <description>&lt;p&gt;Deployable robots in the woods!&lt;/p&gt;

&lt;video controls=&quot;&quot; loop=&quot;&quot; autoplay=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot; muted=&quot;true&quot;&gt;
  &lt;source src=&quot;videos/2025-06-07_appearing.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    Drawing in space!
  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;video controls=&quot;&quot; loop=&quot;&quot; autoplay=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot; muted=&quot;true&quot;&gt;
  &lt;source src=&quot;videos/2025-06-07_cube-01.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    Long exposure linework. 
  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I got lucky again this year and was able to join in &lt;a href=&quot;https://www.haystack-mtn.org/haystack-labs&quot;&gt;Haystack Labs&lt;/a&gt;, which is a craft school / summer camp that Neil (my advisor) and James Rutter (Haystack Fab Lab Director) temporarily repurpose (for six days) as a kind of craft x digital-fab experiment. The gist of the experiment is to ask: why is it that digital fab and craft seem mostly like oil and water, and what can we do to emulsify them?&lt;/p&gt;

&lt;p&gt;I think that mixing craft and digital fab is a kind of generational task… many of my favorite collaborators and advisors (&lt;a href=&quot;https://web.mit.edu/imoyer/www/portfolio/&quot;&gt;Ilan Moyer&lt;/a&gt;, &lt;a href=&quot;https://leomcelroy.com/&quot;&gt;Leo McElroy&lt;/a&gt;, &lt;a href=&quot;https://depts.washington.edu/machines/&quot;&gt;Nadya Peek&lt;/a&gt;, and &lt;a href=&quot;https://fabacademy.org/2020/labs/ulb/students/quentin-bolsee/about/&quot;&gt;Quentin Bolsee&lt;/a&gt;) are making big contributions here. I have always hoped that my own work will contribute along this front, making it easier to make interactive tools with human-and-process feedback across multiple levels, but for all of my efforts, the tools I have developed have yet to hit the level of reliability and usefulness that they would be appropriate to deploy in these messy environments. I have resolved that I will have to leave grad school and gather a team in order to fulfill that dream.&lt;/p&gt;

&lt;p&gt;It was my fourth time at Haystack - in the past I have made &lt;a href=&quot;https://ekswhyzee.com/2018/07/08/haystack.html&quot;&gt;a clay 3d printer&lt;/a&gt; &lt;a href=&quot;https://ekswhyzee.com/2021/07/08/haystack.html&quot;&gt;and another one&lt;/a&gt;… and this &lt;a href=&quot;https://ekswhyzee.com/2023/07/21/clangers-and-bangers.html&quot;&gt;weird pots-and-pans machine&lt;/a&gt;. This time around, I decided to try to build something &lt;em&gt;wondrous&lt;/em&gt; - to do &lt;em&gt;machine building as a craft,&lt;/em&gt; in a shamelessly self indulgent way. Art, babey… and the simple joy of making things that move.&lt;/p&gt;

&lt;h2 id=&quot;what--where&quot;&gt;What / Where&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;images/2025-06-03_haystack-beauty-shot.jpg&quot; alt=&quot;haystack&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Haystack is a gorgeous place: the coast of Maine, an island reachable via an island, and &lt;em&gt;woods, moss&lt;/em&gt; crawling over rocks. I was programming in the woods and it was joyous.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/2025-06-05_prog-in-the-woods.jpg&quot; alt=&quot;woods-prog&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I wanted to make a machine that was lightweight, that we could install in an hour or less, and take away with no footprint. So: the winches.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-06-05_winch-on-tree.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;One such winch, lashed to an unsuspecting tree.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I made four of these: torque-controlled winches that use &lt;a href=&quot;https://ekswhyzee.com/2025/03/24/foc-stepper.html&quot;&gt;my latest driver&lt;/a&gt;, and one of my &lt;a href=&quot;https://ekswhyzee.com/2019/04/09/gt2-belt-rotary-cad.html&quot;&gt;favourite design patterns&lt;/a&gt; for a belt reduction. The spool &lt;em&gt;does not&lt;/em&gt; have any ‘tending’ hardware, i.e. the rope simply winds on, so we can’t be completely sure what the exact diameter is at any given moment (not good) but the thing is pretty coarse anyways, so I eschewed the complexity that would have entailed.&lt;/p&gt;

&lt;p&gt;Rope enters the spool through a small teflon o-ring, so that the angle of entry doesn’t change the friction &lt;em&gt;too much,&lt;/em&gt; and this entrance (… known colloquially as &lt;em&gt;the butthole&lt;/em&gt;) is positioned about 150mm from the spool, in an effort to have the rope coil up nicely on the spool.&lt;/p&gt;

&lt;p&gt;I also designed &lt;em&gt;the orb&lt;/em&gt; - a sphere with four entry points, and interior mounts for some RGBW LEDs. We stuck an ESP32 in here and a 21700 LiPo cell, so that we can change the light’s color and intensity remotely over WiFi.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/2025-05-27_le-orb.jpg&quot; alt=&quot;orb&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There’s a flap on each winch with a limit switch, which we use to home the machine… this little bonk might be my favourite part of the whole project.&lt;/p&gt;

&lt;video controls=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot; muted=&quot;true&quot;&gt;
  &lt;source src=&quot;videos/2025-06-06_homing.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    The homing / geometry discovery sequence: last three motors are shown. 
  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;video controls=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot; muted=&quot;true&quot;&gt;
  &lt;source src=&quot;videos/2025-06-04_spooky.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    The orb has a ~ spooky ~ vibe, hence the Blair Winch...
  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;homing-and-learning-where-the-winches-are&quot;&gt;Homing, and Learning Where the Winches Are&lt;/h3&gt;

&lt;p&gt;Credit goes to &lt;a href=&quot;https://fabacademy.org/2020/labs/ulb/students/quentin-bolsee/about/&quot;&gt;Quentin&lt;/a&gt; for figuring out how this should work, and implementing it in a routine that I could plug into the controller.&lt;/p&gt;

&lt;p&gt;Homing the winches is simple-ish: we startup each motor, and then run them sequentially with three (idling motors) under some minimum torque (to keep lines straight), and the fourth (reeling motor) at a larger torque. Then we pull motor encoder measurements (which we can correlate to rope lengths), as the chosen motor reels the orb in towards the limit. Once that limit is hit, we swap motors so that the next is reeling and the rest are idling. We do this four times, so that each motor is the reel-er once.&lt;/p&gt;

&lt;p&gt;After the homing routine is done, we have a big system of equations to solve. The main issue it that we don’t know where the winches’ rope-entry points are, we’ll call those \((w0 \ ... \ w3)\). At each time step in the homing routine, we have rope lengths (measured) for each winch \((r0_t \ ... \ r3_t)\). We also add a long list of unknown positions of the orb for each time step, \((orb_t)\)&lt;/p&gt;

&lt;p&gt;We assume that at each time step, the distance of the orb to each winch is matched to the measured rope length… i.e.&lt;/p&gt;

\[\| \overrightarrow{wN - orb_t} \| \approx rN_t\]

&lt;p&gt;This gives us a pretty simple loss function: for each time step, some \(orb_t\) should exist that is \(ropeN_t\) distance from each \(wN\) position. If we apply that loss to each time step, we find \(wN\) positions that are consistent through time (… the winches don’t move). So, sort of oddly, to solve for the winch positions we also have to figure out what the whole homing trajectory looked like.&lt;/p&gt;

&lt;p&gt;This is a simple loss, but there’s lots of data - normally 1000 or so points along the homing trajectory. So we throw that over to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scipy.optimize.minimize&lt;/code&gt; to chew through it.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loss_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;n_motor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# winch positions
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;xyz&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reshape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_motor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_motor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# orb positions: one for each time step 
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reshape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_motor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# guess is good if distance from winches to the orb 
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# matches the measurement at that time step 
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_motor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;dist_sq_i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xyz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:])&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;axis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;err_i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;square&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dist_sq_i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]))&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;tot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err_i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_motor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tot&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the end, this solved pretty well each time we ran it. I stuck it in a subprocess so that we could run it (takes a few seconds) after the homing routine, with the rest of the controller still alive and talking to hardware. The solver doesn’t solve in any fixed orientation (although we tried that), instead it just orients the solution randomly. Once the hard part is done, Quentin simply rotates the solution around such that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w1&lt;/code&gt; is at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0, 0, 0&lt;/code&gt; and the vector from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w1 -&amp;gt; w3&lt;/code&gt; is vertical along z and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w2&lt;/code&gt; is somewhere in the XZ plane. Good enough for a first pass, probably we could also look at torques over time to ascertain the z / gravity vector, but not today.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-06-04_solver-blender.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;To get a qualitative check for the solve, Quentin popped output points into blender, which let us check if the tetrahedron was ~ about the right shape.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The solver also returns the tetrahedron’s &lt;a href=&quot;https://en.wikipedia.org/wiki/Tetrahedron#Measurement&quot;&gt;insphere&lt;/a&gt; radius and location, which is a useful place to move target trajectories into: points in the insphere are guaranteed to have relatively nice reachability, whereas i.e. points outside the tetrahedron are unreachable (obviously) and points close to the corners require using lots of torque on one of the motors…&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-06-03_tetra-measurement-validation.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;With some help from Sebastian of &lt;a href=&quot;https://morakana.com/&quot;&gt;morakana&lt;/a&gt;, we validated our tetrahedron solves by measuring vertex-to-vertex lengths.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;an-aside-solving-w-unknown-offsets&quot;&gt;An Aside: Solving w/ Unknown Offsets&lt;/h4&gt;

&lt;p&gt;As the orb approaches each of the stops, the geometry changes such that the opposing three motors exert more force on the orb: i.e. we are pulling “more away” from motors 0-2 when we are close to motor 3. This is obvious when you look at the geometry, and raises an issue with the somewhat underpowered winches: in the first setup we did, the tetrahedron made up by the winches was pretty far from an “ideal” tetrahedron… this made for a situation where the homing motor wasn’t able to reel the orb in completely. We tried initially so solve this problem away, by having our solver learn &lt;em&gt;these&lt;/em&gt; offsets as well as the rest of the geometry, i.e adding an unknown offset \(roN\) …&lt;/p&gt;

\[\| \overrightarrow{wN - orb_t} \| \approx rN_t + roN\]

&lt;p&gt;Neither Quentin or I could get it to solve consistently. Probably it is possible, but it was beyond us at the time (although Quentin certainly got closer).&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;video controls=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot;&gt;
  &lt;source src=&quot;videos/2025-06-05_homing-wo-offset.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;This shows the homing routine w/o offsets, where we flip motor directions once the orb has stopped for ~ 200ms. It should be possible to solve even with this data, solving also for the final offset in each direction, but that turned out to be a second-spiral task. This video also shows an attempt at control using only rope-length-setting output, which results in odd shaped circles due to the ~ nonlinear force that each corner of the tetrahedron exerts (and non-infinite torques).&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To solve this problem ad-hoc styles, we added additional stops on the rope to make sure the limits were hit, and measured that offset manually. The second tetrahedron we made was also much more &lt;em&gt;regular,&lt;/em&gt; so this problem went away (i.e. the orb actually hit the stop in each corner).&lt;/p&gt;

&lt;h3 id=&quot;controlling-the-orb&quot;&gt;Controlling the Orb&lt;/h3&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-06-06_linear-alg-oye-vey.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;If only I had internalized more of Gil Strang’s class…&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Controlling the orb once we had the winch positions was more involved than I had thought. My first naive attempt was to simply take the target position, measure what the rope lengths should be at this target, and then run PID on the motor torques to hit that. This worked &lt;em&gt;fine&lt;/em&gt; but did not produce very smooth motion, and led to some instability when the winch positions were not very well worked out.&lt;/p&gt;

&lt;p&gt;I eventually developed a &lt;em&gt;net force&lt;/em&gt; calculation, to push the orb along a particular force vector at each time step. This involves a few steps.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;video controls=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot;&gt;
  &lt;source src=&quot;videos/2025-06-06_circle-drawing.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;I did manage to get the orb more-or-less under control, here it is drawing a circle in space - slowly, surely…&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;1-where-is-the-orb-&quot;&gt;1: Where is the Orb ?&lt;/h4&gt;

&lt;p&gt;We have a target position at each time step, and we have rope length measurements. To figure out where the orb is, we need to do forwards kinematics (actuators to cartesian). This can be solved using &lt;a href=&quot;https://gamedev.stackexchange.com/questions/75756/sphere-sphere-intersection-and-circle-sphere-intersection&quot;&gt;sphere intersections:&lt;/a&gt; each winch is the center of a sphere, and each sphere radius is the rope length. With four lengths, this is overconstrained, but if we just take a subset of the motors it is perfectly defined always (so long as all spheres intersect).&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sphere_sphere_intersection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;atol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1e-9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;sh&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;
    returns (center, normal, radius)  |  None
    &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;d_vec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c1&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;linalg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d_vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Degenerate centre-coincident case
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;atol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ValueError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bad circles&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# No intersection if too far apart or one inside the other
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;atol&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;atol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Unit vector along the centre-to-centre line
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d_vec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Distance from c1 to the circle’s plane (along e)
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Circle radius^2
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;r_c_sq&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r_c_sq&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;atol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;      &lt;span class=&quot;c1&quot;&gt;# Numerical guard
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;radius&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r_c_sq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;center&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;normal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;              &lt;span class=&quot;c1&quot;&gt;# already unit length
&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;radius&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fwds_kinematics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tetra_vertices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rope_lengths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# intersect 0, 1 to get a circle betwixt, 
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;circ_0_1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sphere_sphere_intersection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tetra_vertices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rope_lengths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tetra_vertices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rope_lengths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;circ_0_1&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# bonk, return center ... 
&lt;/span&gt;        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;sphere-sphere intersect fails&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rad&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;insphere&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tetra_vertices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;center&lt;/span&gt; 

    &lt;span class=&quot;c1&quot;&gt;# intersect circle and third to get two points... 
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;pts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;circle_sphere_intersection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;circ_0_1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tetra_vertices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rope_lengths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pts&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;sphere-circle intersection fails&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rad&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;insphere&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tetra_vertices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;center&lt;/span&gt; 
    
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# pick tallest... 
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;argmax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I learned this trick while helping the &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.24/Architecture/machine.html&quot;&gt;HTMAA Architecture Group&lt;/a&gt; finish their machine. Works great.&lt;/p&gt;

&lt;h4 id=&quot;2-move-the-orb&quot;&gt;2: Move the Orb&lt;/h4&gt;

&lt;p&gt;Once we know where the orb is, we can apply some force to take it where we want it to go. In this case, I simply chose a ‘requested’ force vector that was scaled with the orb’s current distance to the target: when further away, we ask for more juice, and when closer we dial that back. P&lt;del&gt;ID&lt;/del&gt; control, the quickest of hacks.&lt;/p&gt;

&lt;p&gt;Then there is the matter of picking which motors to exert which torques. We know the orb position and the winch positions, we have four rope vectors that can exert some force (but only in tension), so we have to find a set of tensions that sums to the desired direction (and magnitude).&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-06-10_control-diagram.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;To control using ‘net force’, I calculate a set of tensions that best combine to produce some desired force vector on the orb. This would be simple if we could produce unlimited torques, but we sometimes don’t have enough juice to reproduce the desired vector exacly.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Were our motors infinitely torquey, this would always be possible - but they are not. To add complexity, we always have to exert some minimum tension to keep the lines taught (otherwise our rope length measurements get wacky).&lt;/p&gt;

&lt;p&gt;I tried and tried to work this out using i.e. vector projections, but couldn’t quite nail it. I tried to summon my inner &lt;a href=&quot;https://math.mit.edu/~gs/&quot;&gt;Gil&lt;/a&gt; but failed. Instead I used a least-squares fit that minimizes error to the target direction, ye olde have-someone-else-who-wrote-the-python-routine solve the problem for me: praise to the tools-builders, for they are truly His disciples! To check how much trouble this was causing, I calculate the resulting force vector’s alignment with the target at each step, and report that if it’s eggregious.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# net force vector style control 
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;do_control_net_force_dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tetra_vertices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rope_lengths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targ_dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tq_min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tq_max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pos_now&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fwds_kinematics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tetra_vertices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rope_lengths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dir_vec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targ_dir&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dir_vec_mag&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;linalg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dir_vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 

    &lt;span class=&quot;n&quot;&gt;f_des&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dir_vec&lt;/span&gt;       &lt;span class=&quot;c1&quot;&gt;# desired Cartesian force
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;f_des_unit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f_des&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;linalg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f_des&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 

    &lt;span class=&quot;n&quot;&gt;rope_vecs&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tetra_vertices&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pos_now&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rope_units&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rope_vecs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;linalg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rope_vecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;axis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[:,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# (4,3)
&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;B&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rope_units&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;            &lt;span class=&quot;c1&quot;&gt;# (3,4)
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;t0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;full&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tq_min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;     &lt;span class=&quot;c1&quot;&gt;# initial torques (all @ min) 
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;f0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;B&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t0&lt;/span&gt;                 &lt;span class=&quot;c1&quot;&gt;# initial net force 
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;f_res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f_des&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f0&lt;/span&gt;          &lt;span class=&quot;c1&quot;&gt;# extra force we still need
&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;hi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;full&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tq_max&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tq_min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sol&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lsq_linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f_res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bounds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;trf&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;delta_t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tensions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delta_t&lt;/span&gt; 
    &lt;span class=&quot;c1&quot;&gt;# tensions = np.clip(tensions, tq_min, tq_max)
&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# to check 
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# project torques back along ropes 
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;f_final&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;B&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tensions&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;f_final_unit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f_final&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;linalg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f_final&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 

    &lt;span class=&quot;c1&quot;&gt;# angle betwixt ? 
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;cos_ang&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f_des_unit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f_final_unit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ang_err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;degrees&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;arccos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cos_ang&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Magnitude error
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;mag_err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;linalg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f_final&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f_des&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rel_err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mag_err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;linalg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f_des&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# track max-latest 
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;control_iters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ang_err_max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ang_err_max_at&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;control_iters&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; 
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ang_err_max_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;control_iters&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ang_err_max&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ang_err&lt;/span&gt; 

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ang_err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ang_err_max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ang_err_max&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ang_err&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;ang_err_max_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;control_iters&lt;/span&gt; 

    &lt;span class=&quot;c1&quot;&gt;# compare torque result w/ desired
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;do_control_net_force ...&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;f_des (unit)     &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f_des_unit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;f_final (unit)   &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f_final_unit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;f_ang_err        &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ang_err&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\t&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; (max &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ang_err_max&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;f_rel_err        &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rel_err&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tensions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dir_vec_mag&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I found that this caused &lt;em&gt;lots&lt;/em&gt; of problems with tightly bounded motor torques (i.e. if the minimum torque is big, lots of trouble), but when I deployed more motor juice and controlled with smaller force requests, it was mostly OK. Next go-around I will &lt;em&gt;either&lt;/em&gt; build stronger winches &lt;em&gt;or&lt;/em&gt; write a smarter controller.&lt;/p&gt;

&lt;p&gt;There is clearly lots of improvement here: we can always exert &lt;em&gt;some&lt;/em&gt; amount of force in the target direction, and probably &lt;em&gt;direction alignment&lt;/em&gt; should be prioritized over &lt;em&gt;magnitude matching&lt;/em&gt; - i.e. the higher level controller (a p-only controller, hah), should avoid requesting forces that would require the direction vector to skew.&lt;/p&gt;

&lt;p&gt;This controller doesn’t do &lt;em&gt;any lookahead&lt;/em&gt; either, a clear flaw. I am working on MPC style controllers for other projects, and their utility here is clear, especially since the kinematics effectively changes at each time step. Modeling the orb as a mass in space (and gravity) would improve things, etc etc. I think it is a good test case, and I hope I will get to wake it back up once more to improve. For now, the output is satisfying:&lt;/p&gt;

&lt;video controls=&quot;&quot; loop=&quot;&quot; autoplay=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot; muted=&quot;true&quot;&gt;
  &lt;source src=&quot;videos/2025-06-07_cube-02.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;p&gt;&lt;img src=&quot;images/2025-06-04_sit-circle.jpg&quot; alt=&quot;sit&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/2025-06-04_orbular.jpg&quot; alt=&quot;zoomies&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/2025-06-04_half-circle.jpg&quot; alt=&quot;half&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/2025-06-06_spiral-composite.jpg&quot; alt=&quot;spirale&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/2025-06-06_spiral-part.jpg&quot; alt=&quot;spiral-part&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;midi---orb&quot;&gt;MIDI -&amp;gt; Orb&lt;/h3&gt;

&lt;p&gt;Besides light painting, we also ran a little demo / concert with the gang at Haystack. &lt;a href=&quot;https://charstiles.com/about&quot;&gt;Char&lt;/a&gt; plugged an accelerometer into &lt;a href=&quot;https://strudel.cc/&quot;&gt;strudel&lt;/a&gt; (a live-coding synth environment), and piped accelerometer values to the orb controller via MIDI. The values were also used to change some beat arrangements and samples… I transformed this data into orb motion, and people gathered-round for a try piloting the orb / music.&lt;/p&gt;

&lt;video controls=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot; muted=&quot;true&quot;&gt;
  &lt;source src=&quot;videos/2025-06-06_le-dance.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;p&gt;&lt;img src=&quot;images/2025-06-06_midi-scene-00.jpg&quot; alt=&quot;midi-00&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/2025-06-06_midi-scene-01.jpg&quot; alt=&quot;midi-00&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/2025-06-06_midi-scene-02.jpg&quot; alt=&quot;midi-00&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/2025-06-06_midi-scene-03.jpg&quot; alt=&quot;midi-00&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;what-we-learned-what-we-missed&quot;&gt;What We Learned, What We Missed&lt;/h3&gt;

&lt;h4 id=&quot;more-power--better&quot;&gt;More Power == Better&lt;/h4&gt;

&lt;p&gt;Despite what I thought was a pretty solid belt reduction and reasonable sized NEMA17 motor, we were still torque limited in many cases. I think there’s lots of room to improve the controller to overcome this, but it would still of course be preferrable to be able to absolutely throw the thing around.&lt;/p&gt;

&lt;p&gt;The upper limit wasn’t actually the motor (we only ran it near ~ 60% available output) - it was the belt, a GT2, which would occasionally slip on the drive wheel (which is printed…). Moving to a GT3, and improving the printed geometry would help in this regard.&lt;/p&gt;

&lt;h4 id=&quot;control-with-smarter-limits&quot;&gt;Control with Smarter Limits&lt;/h4&gt;

&lt;p&gt;When the controller was in a position when it couldn’t exert a net force in the right direction, it would apply the best effort to hit the target &lt;em&gt;magnitude / direction&lt;/em&gt; combo, meaning the thing would wiggle off trajectory. A controller that scales &lt;em&gt;up to&lt;/em&gt; to maximum magnitude, without violating the target direction, might perform better - but then we would be running PID with a nonlinear real output. A smarter version of this would involve some more of that direction-vector modeling in the main control loop… MPC for the orb would be very cool.&lt;/p&gt;

&lt;h4 id=&quot;learning-with-torques-to-boot&quot;&gt;Learning with Torques to boot&lt;/h4&gt;

&lt;p&gt;These motor controllers &lt;a href=&quot;https://ekswhyzee.com/2025/03/24/foc-stepper.html&quot;&gt;can learn parts of the motor model&lt;/a&gt;, and this project showed me pretty well where the next step would be: with a good motor model, we could also model i.e. the friction at the rope input (as a function of entry angle), and the winch’s stiction properties.&lt;/p&gt;

&lt;p&gt;We could also add data to our solver by looking at &lt;em&gt;net torques&lt;/em&gt; - i.e. if we keep rope lengths matched, then dial one motor up (more tension), and run a controller on the other motors to hold length, we get some information that describes the motors’ positions w/r/t one another, since we can assume the force vectors along the ropes in these cases are summing to zero. We would need to account for stiction there, too, but it’s all more data to better constrain the solver.&lt;/p&gt;

&lt;h4 id=&quot;learning-all-the-time&quot;&gt;Learning All The Time&lt;/h4&gt;

&lt;p&gt;Something I’ve been really interested in is developing a controller that is continuously learning… each new time step generates a new set of rope lengths, motor output torques, and positions… we should be able to continuously add those to our data pile and occasionally re-run / improve estimates of the systems’ properties.&lt;/p&gt;

&lt;p&gt;Very fancy versions of that system would learn also &lt;em&gt;where they are underconstrained&lt;/em&gt; and then try to operate in regions where data is sparse, to better characterize the whole system… and with enough data, also measuring i.e. motor and driver temperatures, we might learn about how i.e. heat saturation affects performance, etc etc.&lt;/p&gt;

&lt;h4 id=&quot;whereis-python-good-for-control&quot;&gt;Whereis Python Good for Control&lt;/h4&gt;

&lt;p&gt;In a lot of the work I do elsewhere on MPC control for fast machines, using python to build controllers can be a performance bottleneck. On the flip-side, those controllers are easier to develop. This system was controlled pretty slow (around 100Hz main loop), where python perf was absolutely good enough. The upshot is big in these cases: we can lean on our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scipy.optimize&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lsq_linear&lt;/code&gt; code blocks to quickly assemble relatively sophisticated controllers.&lt;/p&gt;

&lt;p&gt;The real win would be if we could build portable blocks for the same: having developed a controller in i.e. python, quickly deploy it in an embedded build to bring the bandwidth up. This is one of the fantasies for the graph-style control architectures I have been perpetually trying to bring to life.&lt;/p&gt;

&lt;h4 id=&quot;linear-algebra-is-neat-and-sometimes-non-obvious&quot;&gt;Linear Algebra is Neat and Sometimes Non-Obvious&lt;/h4&gt;

&lt;p&gt;I was surprised to find that even i.e. intersecting two lines in space can amount to ~ some 20 lines of numpy code - or i.e. intersecting two spheres, etc. Most of the linear algebra sub-problems in this project involved Quentin and I both reading lots of wikipedia pages, and then puzzling out how to implement those in numpy. Leo also pointed out the overlap between these types of solvers / routines, and his more generalized work on constraint solving.&lt;/p&gt;

&lt;p&gt;Again towards &lt;em&gt;more generalized&lt;/em&gt; controllers, I saw here the need to build good libraries for these commonplace tasks, and ~ geometry representations to feed them… yada yada&lt;/p&gt;

&lt;h3 id=&quot;in-summary&quot;&gt;In Summary&lt;/h3&gt;

&lt;p&gt;An absolute blast of a project, and one that was on my bucketlist for grad school. A pleasure to do it with such an awesome gang of people around, who were interested in helping with each of the system components, and knowledgeable.&lt;/p&gt;

&lt;p&gt;Major thanks to &lt;a href=&quot;https://www.haystack-mtn.org/&quot;&gt;Haystack&lt;/a&gt; and to Neil for bringing me along. Go there if you can, it’s a wondrous spot filled with really good people.&lt;/p&gt;
</description>
        <pubDate>Mon, 09 Jun 2025 00:00:00 -0400</pubDate>
        <link>http://ekswhyzee.com/2025/06/09/blair-winch-project.html</link>
        <guid isPermaLink="true">http://ekswhyzee.com/2025/06/09/blair-winch-project.html</guid>
        
        <category>systems,</category>
        
        <category>control,</category>
        
        <category>motors</category>
        
        
      </item>
    
      <item>
        <title>Field Oriented Control with (almost any?) Stepper</title>
        <description>&lt;p&gt;What feels like ages ago when we were &lt;em&gt;both&lt;/em&gt; at the &lt;a href=&quot;https://cba.mit.edu/&quot;&gt;CBA,&lt;/a&gt; &lt;a href=&quot;http://samcalisch.com/&quot;&gt;Sam Calisch&lt;/a&gt; pointed out to me that it’s possible to control a stepper motor as if it were a brushless motor, using &lt;em&gt;field oriented control,&lt;/em&gt; aka &lt;strong&gt;FOC&lt;/strong&gt; - everyone’s favourite motor control algorithm. This was an exciting idea and I quickly built a circuit to carry it out and I made a decent pass on the project &lt;a href=&quot;https://gitlab.cba.mit.edu/jakeread/ucbus-stepper/-/blob/master/log/cl-step-control-log.md&quot;&gt;back in 2021&lt;/a&gt; although I never managed to completely integrate it into a motion controller.&lt;/p&gt;

&lt;p&gt;Since then I have some new hardware with better current measurement, and a better idea about how to integrate the thing with the rest of the motion control problem, so I am back to learn again.&lt;/p&gt;

&lt;p&gt;In particular, I want to learn how to do this across generalizeable hardware - i.e. slap a controller onto whatever motor is lying around and transform it into a high performance servo. To do this, I need to understand and implement FOC, and then use the controller hardware to learn enough about whichever particular motor it finds itself on in order to control it successfully. In this post, I mostly accomplish the first task, and learn lots about the second.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;video controls=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot; muted=&quot;true&quot;&gt;
  &lt;source src=&quot;videos/2025-03-30_torque-pid.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The result of this effort: a NEMA14 stepper motor in closed-loop position mode, reporting torque in real-world units, using a motor model that was (partially) learned using the motor itself.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/stepper-cl.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The closed-loop stepper board, and one of the same mounted on a NEMA17 motor.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Whether or not this is a good idea has been the subject of many a heated debate in the &lt;del&gt;dungeon&lt;/del&gt; basement of the CBA. Proponents argue that steppers might have more low end torque than ‘normal’ brushless (BLDC) motors - probably due to their high pole count. This seems likely to me, but only in an intuitive way. It seems that they are almost definitely less power dense than BLDCs, but again all I can stand on there is intuition… steppers are built with a lot more magnet steel than seems necessary. Hopefully I will find time to look into (and write up) some more fundamental motor physics that would explain these trade-offs, but in practice I think they are often more subject to the availability of components for our project than first principles physics, and stepper motors are downright ubiquitous - and downright cheap. Hence the almost-any-motor framing for the project.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/is-this-a-dyno.jpg&quot; alt=&quot;a-dyno&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I have to make a shout-out to the &lt;a href=&quot;https://hackaday.io/project/11224-mechaduino&quot;&gt;mechaduino&lt;/a&gt; project, which is the first implementation I saw - I think there are many such projects out there, and I know this is not terribly novel of me. FWIW, I suspect that the real lack of closed-loop in open-source has to do more with the systems we use to &lt;em&gt;integrate&lt;/em&gt; motors with motion controllers (and tune servo loops) than the availability of motor controllers themselves. I have done lots of work building higher-level motion controllers / planners (synchronization, look-ahead, and even model-predictive motion lately), but I wanted to come back to the low level and see how that might all connect to i.e. position PIDs.&lt;/p&gt;

&lt;p&gt;So, &lt;em&gt;in this post:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;a brief explainer on FOC and its relevance / application to steppers&lt;/li&gt;
  &lt;li&gt;a tour through the hardware and some of the low-level controller components
    &lt;ul&gt;
      &lt;li&gt;ADC aligned sampling&lt;/li&gt;
      &lt;li&gt;current control in rotating reference frames&lt;/li&gt;
      &lt;li&gt;encoder calibrations&lt;/li&gt;
      &lt;li&gt;a simple position / velocity / acceleration observer&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;some motor data collection and modeling to find &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(kt, ke, kd)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;learning how to use the motor as a sensor even when we are running open loop&lt;/li&gt;
  &lt;li&gt;some notes on integration with motion systems&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;stepper-foc-vs-brushless-direct-current-bldc-motors&quot;&gt;Stepper FOC vs. Brushless Direct Current (BLDC) Motors&lt;/h2&gt;

&lt;p&gt;Electric motor architectures are proliferating, especially due to interest in EVs - but all share the same principle: the &lt;em&gt;rotor&lt;/em&gt; has a magnetic field - normally provided by permanent magnets made of those geopolitically sensitive &lt;em&gt;rare earth&lt;/em&gt; metals - and the &lt;em&gt;stator&lt;/em&gt; has electromagnet windings. We then use a computer to switch current through the stator windings, so that we can “point” the stator’s magnetic field in whichever direction we would like.&lt;/p&gt;

&lt;p&gt;In all cases, what we want to do is drive the stator’s magnetic field so that it is 90’ out of phase (left or right) from the rotor’s magnetic field. Since the rotor’s field wants to &lt;em&gt;align&lt;/em&gt; with the stator, the 90’ phase advance effectively &lt;em&gt;pulls&lt;/em&gt; the rotor in that direction. Moving the stator’s field so that it advances ahead of the rotor’s field is known as &lt;em&gt;commutation,&lt;/em&gt; and we call the reference frame that rotates with the rotor the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dq&lt;/code&gt; frame, with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;direct&lt;/code&gt; axis that is aligned with the rotor’s magnetic field, and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;quadrature&lt;/code&gt; axis that is 90’ offset from that axis.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/bldc.gif&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;In a motor with three phases, like BLDC motors or most induction motors, we have three magnet windings in the stator that make up a kind of set of basis vectors. By varying the current on these three windings, we can effectively &lt;em&gt;point&lt;/em&gt; a current vector within in the stator.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;iframe width=&quot;700&quot; height=&quot;394&quot; src=&quot;https://www.youtube.com/embed/VMwv4XFZ2L0?si=MeUTGTTsX1u1npMC&amp;amp;start=161&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;blockquote&gt;
  &lt;p&gt;The best explainer on stepper motor operation that I could find.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even though they are fundamentally the same as any other motor, steppers can be confusing since they are ‘hybrid’ architectures, combining normal permanent magnet motor design patterns with ‘reluctance.’ This basically means the field generated by the permanent is carefully guided through patterns in the rotor’s laminations. If you’re unfamiliar, the video above is the best way to learn.&lt;/p&gt;

&lt;p&gt;When we’re doing control, it’s almost better to not think about those finer details. What we really want to know is that &lt;em&gt;the stepper has 50 electrical rotations for every mechanical rotation&lt;/em&gt; - that is, every time we swing the shaft around once &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0-2PI] radians&lt;/code&gt; we rotate the magnetic field around inside the stator &lt;em&gt;fifty times.&lt;/em&gt; There are four ‘full steps’ in each full rotation of the electrical phase, hence &lt;em&gt;200 ‘steps’&lt;/em&gt; in a motor.&lt;/p&gt;

&lt;p&gt;The image below &lt;a href=&quot;https://www.allaboutcircuits.com/textbook/alternating-current/chpt-13/stepper-motors/&quot;&gt;via allaboutcircuits&lt;/a&gt; shows an effective way to think about a stepper motor, for control purposes. We have two &lt;em&gt;windings&lt;/em&gt; - one ‘up and down’ (the ‘B’ winding) and one ‘side to side’ (the ‘A’ winding) (sometimes X and Y, depending on the parlance).&lt;/p&gt;

&lt;p&gt;If we only drive current from left to right on the horizontal winding, we are putting all of our current at zero degrees. If we only drive current ‘up’ on the vertical winding, we are putting current at ninety degrees… and so on: right to left = 180 degs, down = 270 degs. We combine components of these to drive current at whichever angle we would like.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/pm-wave-drive-sequence.webp&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;We can effectively think of stepper motor stators as an electromagnet with two windings: one pair side-to-side (A) and one up-down (B). These are basically basis vectors A, B (or X, Y depending on who you are asking) that we can use to control the direction and amplitude of the magnetic field within our stator.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, besides the particular layout of the hardware, FOC applies just about identically to the stepper. In fact the algorithm is smaller, since our component vectors are a simple orthagonal set (rather than the 120’ separated phases in a BLDC). That said, FOC in a stepper probably needs to commutate faster than in a BLDC (since the phases are so tightly packed), and steppers tend to have higher inductance, meaning we might want more voltage to swing the things around.&lt;/p&gt;

&lt;h2 id=&quot;the-hardware&quot;&gt;The Hardware&lt;/h2&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-17_stepper-debuggen.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;My FOC-stepper board, with current sensing and PWM pins bodged out for debugging. Microcontroller (SAMD51) and an interface for comms is on the other side. It’s a busy fit on a NEMA11, but well sized for NEMA14s, and can probably drive up to NEMA23 windings.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Some specs for you:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;MCU: SAMD51J19A, 120 ~ 200MHz, ARM-M4F&lt;br /&gt;
Encoder: AS5047P, 14 bit, on-axis magnetic&lt;br /&gt;
Drive: 2x DRV8251A, 4.5-50v, 4.1A peak, 450mOhm, current sense out &lt;br /&gt;
I2C Port&lt;br /&gt;
Fan Port&lt;br /&gt;
Motor Thermistor Plug&lt;br /&gt;
Limit Switch Plug&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The board is part of a family that I am calling &lt;em&gt;knuckles&lt;/em&gt; - that share physical link hardware with a swappable PHY spec, TBD on releasing that but it basically means that we can put whatever connectivity we want onto this thing: USB + USB PD for development is pretty handy, I also have an RS-485 adapter, and should develop CAN and BLE versions, etc.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;div style=&quot;display: flex; margin-bottom: 0.8rem;&quot;&gt;
  &lt;div style=&quot;padding-right: 3px&quot;&gt;
    &lt;img src=&quot;images/preview_front.png&quot; /&gt;
  &lt;/div&gt;
  &lt;div style=&quot;padding-left: 3px;&quot;&gt;
    &lt;img src=&quot;images/preview_back.png&quot; /&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Front and Back, from KiCAD. The group of 12 labelled pads is the interface for a modular PHY layer that I am developing.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/knuckles-kit.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Some of of the’knuckles’ family of modular circuits, showing also the small embedded message passing and power distribution device (top center).&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This fits &lt;em&gt;ish&lt;/em&gt; on a NEMA11, and fits well on NEMA14 up to 23, where I suspect that the drivers would start to be a little bit heat challenged. I &lt;em&gt;did&lt;/em&gt; put a small fan driver stage on the board for active cooling, and with the right enclosure / heatsink I think NEMA23 is totally manageable - at least the shorties.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;TODO: pics of the circuit mounted on motors !&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The board is four-layers, the two internal layers were aspirationally all GND plane but I had to cheat a little and route some lines under the microcontroller. This layout was tricky because I was really trying to pinch the thing onto a NEMA11, but I think I worked it out well. I do love a good routing challenge even though it normally takes me ~ four days instead of the two that I budget.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/layout.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The layout. This one was tight, and it could probably use more bulk capacitance, but in the end it all fits OK. I did have to get a little fancy with the internal layers - only time will tell if I committed any GND plane sins.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/schematic.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The schematic… thanks to the DRV8251A, and to the modular PHY, it’s pretty simple: encoder, 2x hbridge, voltage regulation for the MCU, thermistor, some plugs, and the fan driver… RGB LED as a treat. I’ll publish the harware properly someday, I promise.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I picked the DRV8251A because of it’s specs (50V, 4.1A, nice!) but mostly because of it’s feedback. The bridge contains a &lt;em&gt;current mirror&lt;/em&gt; output on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IPROPI&lt;/code&gt; pin that outputs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1575uA/A&lt;/code&gt; of current flowing through the bridge. By sticking a resistor on that output (to make a voltage that is proportional to the output current), we can measure that on our microcontrollers’ ADC.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/hbridge-block-diagram.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The DRV8251A block diagram. Mostly a standard hbridge, but including this &lt;em&gt;current mirror,&lt;/em&gt; which we can use to measure current in the motor stator (and so, the magnetic field). Note that the one output pin &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IPROPI&lt;/code&gt; combines inputs from two measurement devices &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ISEN1&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ISEN2&lt;/code&gt; … this will cause us to have to do some tricks later on.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;the-foc-algorithm&quot;&gt;The FOC Algorithm&lt;/h2&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03_cl-stepper-control-diagrams-loop.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The controller is really two main loops. Key inputs are encoder position and current readings (left top, bottom). We use encoder readings to correct for position disturbances (or hit target speeds / trajectories etc) (top loop). We also use it to commutate the motor (bottom).&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;FOC’s power is that it works well across a wide range of speeds, and uses the entirety of the motor effectively… it is smooth, torque-ey, and silent when implemented well. However, it requires that we have an accurate reading of the rotor’s position &lt;em&gt;and&lt;/em&gt; readings of the current in the windings - both of which require additional hardware (as opposed to i.e. an open loop driver). FOC also requires that we tune a few different controllers: the PI controllers in the current loop, three gains in the rotor position observer (more on that later), and PIDs for position or speed loops that will get rolled on top of the core FOC driver. These are some of the things that we want to tune either automatically or with some information from a motor model.&lt;/p&gt;

&lt;h3 id=&quot;the-magic-current-control-in-a-stable-reference-frame&quot;&gt;The Magic: Current Control in a Stable Reference Frame&lt;/h3&gt;

&lt;p&gt;As mentioned, FOC works by placing target current on the &lt;em&gt;quadrature&lt;/em&gt; axis of the rotor. We can’t &lt;em&gt;directly&lt;/em&gt; control current in the stator, only voltage. This means that we need to sense current, and write a controller that dials voltage up and down in order to hit the right current. This would be enough to get started, but we would end up with rapidly changing current targets - as the motor spins, the target current vector rotates as well. This is where the magic of FOC is: it lets us do that current control in a reference frame that rotates alongside the rotor.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/foc-dq-frame.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Via &lt;a href=&quot;https://www.researchgate.net/publication/282859207_Review_on_Field_Oriented_Control_of_Induction_Motor&quot;&gt;ResearchGate&lt;/a&gt; ‘Review on FOC of Induction Motor’ - the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dq&lt;/code&gt; frame is aligned with the rotor: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d&lt;/code&gt; axis current doesn’t do any work, but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;q&lt;/code&gt; axis current generates torque. Open loop steppers blindly rotate current in circles and take for granted that the rotor follows pretty closely, but lots of energy is wasted because only the misaligned current does work.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/foc-unwrap.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Via &lt;a href=&quot;https://www.researchgate.net/publication/375679028_Field_Oriented_Control_Implementation_on_BLDC_Motor_Controller_with_PI_and_SVPWM_using_STM32F103C8T6&quot;&gt;‘FOC Implementation using STM32F103…’&lt;/a&gt; &lt;a href=&quot;http://dx.doi.org/10.1088/1742-6596/2622/1/012025&quot;&gt;(doi)&lt;/a&gt;. This diagram depicts FOC for a three-phase BLDC, our two-phase stepper doesn’t require the Clarke transforms, just the Park transform and its inverse. FOC basically lets us control motor currents in a stable reference frame, i.e. the frame that moves alongside the motor’s rotor. Without these transforms, our current controller’s setpoint would be rapidly swinging around, making tracking much more difficult - and probably impossible at high speeds.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While I was working this out, I made a small test code in python to help make sense of the transforms before I wrote them into the firmware.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;linspace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;x_in&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;y_in&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# t_in = (np.arctan2(y_in, x_in) - np.pi * 0.6) % (2 * np.pi) - np.pi 
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t_in&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;arctan2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;xy2dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; 
    &lt;span class=&quot;n&quot;&gt;q&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; 
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;q&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dq2xy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;q&lt;/span&gt; 
    &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;q&lt;/span&gt; 
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; 

&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;q&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;xy2dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;x_out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dq2xy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-20_05.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;My transform test. At the top, two example ‘measurements’ ‘x_in, y_in’ and the reference angle ‘t_in’… these are transformed into the stationary dq frame (middle) and then back (bottom).&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I called these transforms simply &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xy2dq&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dq2xy&lt;/code&gt;, (or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ab2dq&lt;/code&gt; …) but they are properly known as Park and Inverse Park. In FOC for a stepper, we don’t need the Clarke components since we already have two orthogonal basis vectors.&lt;/p&gt;

&lt;h2 id=&quot;current-sensing&quot;&gt;Current Sensing&lt;/h2&gt;

&lt;p&gt;I did the usual hardware setup to read the encoder and write some basic PWMs into the hbridges, and then got into measuring current. My ambition was to simply spin the thing around in open loop &lt;em&gt;voltage mode&lt;/em&gt; and then look at currents coming back, to start to piece together the rest of the controller. In particular I was curious to see what the phase delay between voltage and current was, since I figured there was lots of motor physics to learn with that data.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-02_01.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;First trace I saw on the scope, CH1 (yellow) is an inactive debug pin, CH2 (blue) and CH3 (purple) are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN1&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN2&lt;/code&gt; on one of the hbridges, and CH4 (green) is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IPROPI&lt;/code&gt;&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This was encouraging: with previous hardware, current sense output was really noisy, and this is nice and clean. There’s a brief switching time (2us here) when I first switch the hbridge on (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CH2&lt;/code&gt; goes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HI&lt;/code&gt;), then we see current rising in the winding, before it goes off again. But we see these nasty current spikes at startup and at the end of the pulse.&lt;/p&gt;

&lt;p&gt;I hadn’t looked very carefully at the hbridge’s ‘truth table’ though, and at this point I was actually driving the thing into &lt;em&gt;high-z&lt;/em&gt; state during the PWM off times. This would brake the motor excessively - it’s also known as &lt;em&gt;fast-decay&lt;/em&gt; - it totally shuts the stage off, and causes the windings to generate lots of voltage… basically, we spun them up, and now there is a magnetic field in the windings that can’t just instantly turn off - so voltage rises as the energy tries to fight its way out of the winding. What we really want is to let the little current pulse that we created during the on state to keep looping around in the windings, it’s free real estate. This is kind of similar to using a flyback diode on a low-side switch: we give current in a winding a place to go during PWM off-times.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/hbridge-states.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The hbridge ‘truth table’ - when we switch &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN1&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN2&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LO&lt;/code&gt;, we put the bridge in ‘fast decay’ mode - instead, we want the thing to always circulate current through the two low-side fets, and then turn one-or-the-other high-side fets on momentarily to generate more current.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/hbridge-paths.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The hbridge current paths, corresponding to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN1/IN2&lt;/code&gt; states above.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the real strategy here is to set both pins &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HI&lt;/code&gt; when the ‘voltage’ request is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LO&lt;/code&gt; and then flip one side &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LO&lt;/code&gt; when the voltage request is issued in that side of the bridge’s &lt;em&gt;direction&lt;/em&gt; - this little inversion is one of many small gotchas/headaches in this kind of firmware and basically requires that we write the PWM driver by hand. So it was into the datasheet for me.&lt;/p&gt;

&lt;p&gt;In this firmware I’m using the SAMD51’s TCC module - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Timer/Counter for Control&lt;/code&gt; which is a relative joy to use, but probably only because I am already familiar with the chip. Here I simply flipped the polarity of each output channel on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN1&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN2&lt;/code&gt; and then I operate them based on a &lt;em&gt;signed&lt;/em&gt; pwm request value.&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;apply_pwm_hbridge_a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// no -ves into the TCC &lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;uint16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;abs_duty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;duty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// flip dir. based on sign &lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;uint16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cc_in1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cc_in2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;duty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cc_in1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;abs_duty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cc_in2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cc_in1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cc_in2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;abs_duty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// write to hardware &lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;TCC0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CCBUF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HBRIDGE_CC_CHAN_A_IN1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cc_in1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;TCC0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CCBUF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HBRIDGE_CC_CHAN_A_IN2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cc_in2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-02_05.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CH2&lt;/code&gt; is PWM into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN1&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CH3&lt;/code&gt; is PWM into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN2&lt;/code&gt; - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CH4&lt;/code&gt; is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IPROPI&lt;/code&gt; pin at the ADC. I am driving a triangle wave -ve -&amp;gt; +ve, and we see positive current measurement in either direction. The small aberrations are me rotating the shaft by-hand - this changes inductance in the stator (and generates some BEMF voltage), so we can see the effects on the current trace.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-02_06.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;As above, but zoomed in. We can see the short ‘on’ pulse happens when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN1&lt;/code&gt; goes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LO&lt;/code&gt; - the current rises during this interval. Then, in the slow-decay state, the current gently decreases, before it is lifted again at the next pulse…&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The plot above is what we want to see with a motor controller: the motor is ‘basically a big inductor’ that is filtering the pulsed voltage (blue) into a steady stream of current (green).&lt;/p&gt;

&lt;h3 id=&quot;pwm-aligned-adc-sampling&quot;&gt;PWM Aligned ADC Sampling&lt;/h3&gt;

&lt;p&gt;Looking at the current on the scope is cool and all, but we want to read that into the microcontroller to do control with it. So here comes one of the tricks we can pull with a microcontroller that simply isn’t possible on more general computing devices: we configure the ADC so that it samples &lt;em&gt;exactly when one of the TCC channels is triggered.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This SAMD51’s TCC has 8 channels in total, and I am using four already for the PWMs (they are configured as outputs). I can setup an additional channel to trigger one of the SAMD51’s &lt;em&gt;event channels&lt;/em&gt; - these are like little hardware dataflow lines that we can use to communicate from one peripheral to another. That event channel is then connected to the ADC, where it triggers a sampling event.&lt;/p&gt;

&lt;p&gt;This is a bit of a PITA to setup, but the timing is important. Firstly, it helps us to sample &lt;em&gt;away&lt;/em&gt; from any noisy spikes on the line during the switch, and it lets us sample &lt;em&gt;when we know what states the gates are in&lt;/em&gt; - this last part is more important.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-03_00.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CH1&lt;/code&gt; is a debug pin that I toggle in an interrupt activated by the event channel that triggers the ADC reading… so, it flips when the ADC is triggered. Here, I trigger it just after the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN1&lt;/code&gt; gate goes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LO&lt;/code&gt;.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I spent a while trying to get this right.&lt;/p&gt;

&lt;p&gt;Originally I was triggering the ADC in the middle of either side of the PWM phase, i.e. halfway through the ‘off’ part of the cycle &lt;em&gt;or&lt;/em&gt; halfway through the ‘on’ part of the cycle - I thought that due to the slow decay, I would always be measuring valid current and the precise timing was just useful to avoid noisy transitions.&lt;/p&gt;

&lt;p&gt;The current reading is unsigned, as in we don’t know &lt;em&gt;which way&lt;/em&gt; it is flowing through the hbridge when we sample the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IPROPI&lt;/code&gt; pin &lt;em&gt;if it’s in slow decay&lt;/em&gt; because the measurement is taken from whichever of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ISEN1&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ISEN2&lt;/code&gt; is largest… recall from the earlier block diagram of DRV8251A. I had figured that, looking at previous states from the system, it would be easy to reason about the direction of the current - but I soon realized that I needed a way to &lt;em&gt;deterministically&lt;/em&gt; ascertain the sign of the reading…. the current loop is far too fast to be messing about with observers etc.&lt;/p&gt;

&lt;p&gt;The real solution is somewhat counter-intuitive: during &lt;em&gt;every&lt;/em&gt; pwm cycle, we need to flip the hbridge &lt;em&gt;on&lt;/em&gt; in &lt;em&gt;both directions&lt;/em&gt; for some amount of time. For example, if we are driving the bridge &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FWD&lt;/code&gt; at 20% duty over a 1040us period we drive:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN1=HI, IN2=LO&lt;/code&gt; (forward) for 220us, sampling the ADC (reading is FWD signed)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN1=HI, IN2=HI&lt;/code&gt; (coast) for 800us,&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN1=LO, IN2=HI&lt;/code&gt; (reverse) for 20us, sampling the ADC (reading is REV signed)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically, we insert this &lt;em&gt;minimum on-time&lt;/em&gt; for both directions, and sample the phase at the instant where we &lt;em&gt;know&lt;/em&gt; the sign of the current measurement. This required that I re-write the PWM driver and the ADC sampling events.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-19_01.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Traces from a Saleae logic analyzer, showing PWM to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN1&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN2&lt;/code&gt;. The bridge here is driving in reverse: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN1&lt;/code&gt; spends more time ‘down’ than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN2&lt;/code&gt; does, driving current backwards through the bridge on average. We additionally pulse the bridge FWD for a short duration, during which cycle the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IPROPI&lt;/code&gt; output (red) flips to show us how much current is flowing in that direction. The ADC is sampled during the middle of both pulses (sample timing is illustrated with the ISR line toggles).&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-20_vcc-cur-offsets.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;With two measurements, one in each direction, I could finally ascertain the &lt;em&gt;signed&lt;/em&gt; current measurement from hardware, no fooling required. In these plots, you can see the phase delay between i.e. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pwm_a&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cur_a&lt;/code&gt;…&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To set this up in hardware, I flipped the PWM’s WAVEGEN mode to use &lt;em&gt;dual-slope pwm&lt;/em&gt; - this is a mode where the TCC counts up until TOP, and then back down to ZERO (figure below). I then flip the polarity of the second output channel, so that it goes LO around the TOP of the wave, and the first channel goes LO during the bottom of the wave (eliminating any overlap of the two LO signals, which would put the bridge into fast decay mode).&lt;/p&gt;

&lt;p&gt;Then I have to trigger the ADC at &lt;em&gt;both&lt;/em&gt; pulses. For this, I generate an event out of the TCC’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;overflow (OVF)&lt;/code&gt; event, which (in this pwm mode &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DSBOTH&lt;/code&gt;) is fired at both the top &lt;em&gt;and&lt;/em&gt; the bottom of the wave. That even triggers the ADC sample, and then an ISR picks up the readings and stashes them (and reads the TCC’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COUNT&lt;/code&gt; register to ascertain which side has just been sampled) - they’re consumed in a subsequent ISR that runs the current loop.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-19_dsboth.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The SAMD51’s Dual Slope PWM mode, (datasheet: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;49.6.2.5.6&lt;/code&gt;). I inverted pins such that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN1&lt;/code&gt; to go &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LO&lt;/code&gt; at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TOP&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN2&lt;/code&gt; goes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LO&lt;/code&gt; at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ZERO&lt;/code&gt; - this eliminates a fast-decay state that would occur during the small overlap that would occur if I used ‘normal’ PWM. I also use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TOP&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ZERO&lt;/code&gt; overflow events to trigger ADC readings at those specific moments.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I tried to set this up with the minimal number of ISRs, since switching contexts is costly. Both ADC0 and ADC1 are both setup on the D51, with ADC0 sampling one hbridge and ADC1 sampling the other. Since I am using one TCC to control both sides of the motor (via four output channels to two hbridges… two channels each), the samples (and ISRs) are aligned across hbridges, meaning I can use one ISR to collect both samples.&lt;/p&gt;

&lt;h3 id=&quot;interleaving-other-adc-readings&quot;&gt;Interleaving Other ADC Readings&lt;/h3&gt;

&lt;p&gt;For one last gotcha, I had to get the other ADC readings to happen in the middle of all of these events. Most microcontrollers only have a few real ADCs, that are ‘muxed’ onto many different input channels. For example in an arduino with nine ‘analog in’ pins, the MCU might only have one actual ADC. When we tell arduino to sample on (1) and then on (2), it actually reconfigures the ADC when we call the function, then triggers a sample and waits for it to complete. This all takes up valuable time, since the conversions don’t happen in one clock cycle (it can be 50 or more clock counts).&lt;/p&gt;

&lt;p&gt;To avoid these hangups, I added a modal switch that swaps the ADC channels to my three other analog inputs on the board (on-board thermistor, off-board thermistor and a voltage divider on VCC). Right now this happens only two or three times a second, which is plenty fast for those readings. I should improve this later, as it briefly disables the current readings during those intervals (the controller just uses the previous measurement…). To do better, I can sneak a third sample in without interrupting the main current sampling instants.&lt;/p&gt;

&lt;p&gt;The thermal information is slow, but the VCC might be interesting to watch at higher frequencies - doing so could be a good indicator that more bypass capacitance needs to be added, for example, and precise input voltage is important to measure if we are going to dynamically fit motor models.&lt;/p&gt;

&lt;h2 id=&quot;calibrating-the-encoder&quot;&gt;Calibrating the Encoder&lt;/h2&gt;

&lt;p&gt;OK: we have current readings, so we can roll a current controller to point the magnetic field in whichever direction we choose. However! We need that pesky reference angle \(\theta\).&lt;/p&gt;

&lt;p&gt;This is why we have the encoder: it reads &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0, 16383]&lt;/code&gt; (14 bits) and it’s absolute, so the readings are indexed in the same manner when we restart the controller. However, we need to know how mechanical angles correlate to electrical angles. Recall that we have 50 full electrical rotations for every mechanical rotation, and we have some offset. The full 14 bits are useful here, since it means we have ~ 328 counts per electrical phase. Some other common encoders for BLDC FOC have only 11 or 12 bits of resolution, i.e. 41 or 82 counts per electrical phase.&lt;/p&gt;

&lt;p&gt;To gather data for this, I spin the motor around in open loop &lt;em&gt;voltage&lt;/em&gt; mode - I sweep forwards and reverse at a few different speeds, and ingest time stamped readings of the speed, voltages, current, and encoder readings. Using that raw data, I can turn A, B voltage and current readings into phase angles, and then plot them against the encoder angle, that’s below.&lt;/p&gt;

&lt;p&gt;The electrical phase of the &lt;em&gt;current&lt;/em&gt; is the best estimate of the rotor’s actual position during these samples, since that’s where the magnetic field is pointing, and we are assuming that the rotor is tracking that. However, it’s easy to spin the field too quickly and loose rotor tracking, so the calibration routine needs to discriminate stalled rotors and eliminate those data points. It’s also important that we use an equal number of samples from each speed and direction, since the rotor actually lags the current vector by some amount - more when the thing is moving faster.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-19_02.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Encoder readings potted on x, with voltage and current phases (grey, black) plotted on y. We want to fit a function that returns the rotor’s electrical phase for any given encoder reading.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a sensible way to look at the data, since it’s basically the function we want to fit: for any encoder reading, return the electrical angle. But fitting it in this modulo’d space is insane, although I will admit that I tried to do so for a minute. Quentin ended up helping me out, and showed me &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;np.unwrap(...)&lt;/code&gt; which unrolls both sets of periodic data, where they look like more normal functions. For convenience, we also do the fit in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[-pi,pi]&lt;/code&gt; on both axes (i.e. normalizing the encoder readings). Then we can fit them with a little &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scipy.optimize.minimize&lt;/code&gt; …&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# returns electrical phase, given encoder phase 
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fit_func_linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enc_phase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enc_phase&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loss_linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enc_phase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tru_cur&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fit_func_linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enc_phase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tru_cur&lt;/span&gt; 
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;square&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;x0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scipy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;optimize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;minimize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loss_linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;encs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;curs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1e-12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;xf_lin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-20_02.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Errors after a linear fit - looks a lot like CORDIC error.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The linear fit is pretty good, but has this odd looking error… there’s a sinusoid in here at twice the frequency of the encoder phase, and one at the frequency. As it turns out, this is probably &lt;em&gt;mostly&lt;/em&gt; error from the AS5047’s CORDIC unit, which is an old computational / hardware library that approximates the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;atan2&lt;/code&gt; function - something I learned as we investigated this error.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/cordic-atan.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Via &lt;a href=&quot;https://wirelesspi.com/3-ways-to-approximate-atan2-in-hardware/&quot;&gt;wirelesspi.com&lt;/a&gt; - Coordinate Rotational Digital Computer &lt;a href=&quot;https://wirelesspi.com/coordinate-rotation-digital-computer-cordic/&quot;&gt;(CORDiC)&lt;/a&gt; is a set of lightweight trigonometry approximations for embedded devices and ICs. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;atan2&lt;/code&gt; function is particularely common / useful for translating between cartesian and polar coordinates… but it has this error, which looks about identical to the error term that drops out of a linear fit from our encoder.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/as5047-block-diagram.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Block diagram for the AS5047 encoder, showing the ATAN CORDIC block, which is what the encoder uses to translate A, B magnitudes from &lt;em&gt;its hall sensors&lt;/em&gt; to an angular reading.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Probably the best way to do this would be to draw up the actual CORDIC error pattern as a function and then fit parameters for offset and scale of &lt;em&gt;that&lt;/em&gt; to this data, but instead we just stuck two more sinusoids in to fit: one at 2x the encoder frequency and one at 1x. This worked great, or at least well enough for now.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# returns electrical phase, given encoder phase 
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fit_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enc_phase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;lin_fit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fit_func_linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xf_lin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enc_phase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;major_period&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enc_phase&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; 
    &lt;span class=&quot;n&quot;&gt;minor_period&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;enc_phase&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lin_fit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;major_period&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minor_period&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enc_phase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tru_cur&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fit_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enc_phase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tru_cur&lt;/span&gt; 
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;square&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;x0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scipy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;optimize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;minimize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;encs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;curs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1e-12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;xf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; 
&lt;span class=&quot;n&quot;&gt;final_loss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;encs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;curs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-20_03.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Including two more sinusoids into the fit (one at 2x encoder frequency, and one at 1x), we get rid of &lt;em&gt;most&lt;/em&gt; of the rest of the errors. Those noisy little spikes are a bona-fide source of noise in the system: the SPI transaction with the encoder seems to occasionally suffer from one-flipped-bit… I haven’t tracked that down yet.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that we have our fit, we can stick it back in hardware. For now what I’ve done is just had numpy pretty-print a table into the console that I copy and paste into a lookup table (LUT) style thing. I do the modulo arithmetic in python as well - and re-scale everything to the units that I’ll be using in the embedded system.&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// in lut_encoder.h &lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LUT_ENCODER_2_ROTOR_ELECTRICAL_PHASE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16384&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
  &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-19_02-fit.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Periodic data as-above, now with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;le fit&apos;&lt;/code&gt;&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;lut-trigonometry-for-the-transforms&quot;&gt;LUT Trigonometry for the Transforms&lt;/h2&gt;

&lt;p&gt;Speaking of units and data types, we now have to do a bunch of arithmetic in embedded, and so we are going to add even more lookup tables. If you recall the controller outline above, you’ll notice that i.e. native data types for encoder and motor phase are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0-16383]&lt;/code&gt; …&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03_cl-stepper-control-diagrams-loop.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Note elec. phase LUTs are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0-16383]&lt;/code&gt;.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well! That’s because we are doing trigonometry in here with more LUTS. This is a pretty common trick so I won’t go into too much detail, I did find a neat new LUT generator &lt;a href=&quot;https://github.com/ppelikan/drlut&quot;&gt;here&lt;/a&gt; to use…&lt;/p&gt;

&lt;p&gt;For those unfamiliar, the gist is simple: we take some function \(f(x)\) and then discretize it across some integer range… in this case mapping \(x \in [0, 2\pi] = [0, 16383]\) and then we calculate all of the possible results ahead of time and simply write them into memory, in a lookup table. Using i.e. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;const ...&lt;/code&gt; will tell the compiler to write that into Flash (not RAM), saving some valuable space. Then to evaluate, we simply say \(f(x) =\) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LUT[x]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is particularely useful with periodic functions aka sin/cos, where the input can wrap (i.e. \([0,2\pi] == [2\pi,4\pi]\)) to set the span of the input at some power of two: i.e. in this case the input is 14 bits. This means that we can use a bit mask to replace the modulo operator we might otherwise need…&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/** Generated using Dr LUT - Free Lookup Table Generator
  * https://github.com/ppelikan/drlut
  **/&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Formula: sin(2*pi*t/T) &lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LUT_TRIG_SIN_LUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16384&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;///&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;///&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// ingests theta = [0 -&amp;gt; 16383] == [0 -&amp;gt; 2PI] &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// returns float [-1.0 -&amp;gt; 1.0]&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lut_trig_sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// ingests theta = [0 -&amp;gt; 16383] == [0 -&amp;gt; 2PI] &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// returns float [-1.0 -&amp;gt; 1.0]&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lut_trig_cos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lt_dq_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lt_xy_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 

&lt;span class=&quot;n&quot;&gt;lt_dq_t&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lut_trig_xy_2_dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lt_xy_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;lt_xy_t&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lut_trig_dq_2_xy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lt_dq_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// we can wrap 14 bits using a mask, meanig we do periodic maths &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// implicitly / bitwise - fast ! &lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define LUT_TRIG_THETA_WRAP_MASK 0b0011111111111111
&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lut_trig_sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// wrap for 0-16k &lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LUT_TRIG_THETA_WRAP_MASK&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LUT_TRIG_SIN_LUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lut_trig_cos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// same wrap, shift phase by 90&apos; &lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LUT_TRIG_HALF_PI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LUT_TRIG_THETA_WRAP_MASK&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LUT_TRIG_SIN_LUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then the transforms themselves are pretty simple - using a custom struct for dq, xy frames - and (since the SAMD51 has an FPU) I do most of the rest of the maths using floats - nice and simple.&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;lt_dq_t&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lut_trig_xy_2_dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lt_xy_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;lt_dq_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lut_trig_cos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lut_trig_sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;q&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lut_trig_sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lut_trig_cos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;lt_xy_t&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lut_trig_dq_2_xy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lt_dq_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;lt_xy_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;xy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lut_trig_cos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lut_trig_sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;xy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lut_trig_sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lut_trig_cos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;filtering-encoder-readings&quot;&gt;Filtering Encoder Readings&lt;/h2&gt;

&lt;p&gt;By now all of the nuts and bolts are together, but the larger challenge of controlling the motor &lt;em&gt;well&lt;/em&gt; is still with us. One of the things that has caused trouble for me in the past is de-noising the position reading from the encoder, and also estimating the thing’s velocity (and acceleration, if possible) using that data.&lt;/p&gt;

&lt;p&gt;Position is obviously useful for closing position loops, but velocity and acceleration are great to have. For example in FOC, we can use the velocity to predict the rotor’s position &lt;em&gt;in between encoder samples&lt;/em&gt; - so i.e. if we are sampling our encoder around 10kHz but we want to run our current controller at 30kHz, we need two extra predictions. We might also want to replace the position PID with a proper LQR controller, and velocity measurement there would be critical… The PID controller is also making a continuous estimate of the &lt;em&gt;position error derivative&lt;/em&gt; which is ~ at least highly related to velocity, so we might be able to filter once and use the information twice - etc, you get the picture, it’s useful to know how fast you’re going haha.&lt;/p&gt;

&lt;p&gt;Taking discrete derivatives of position in order to calculate velocity is famously difficult to do without making really noisy signals, since we typically sample at high frequency and &lt;em&gt;most&lt;/em&gt; of the change between measurements sample-to-sample is due to noise, unless we are going very quickly. We basically need to do some kind of averaging or filtering to get a sensible output, even with pretty good encoders.&lt;/p&gt;

&lt;h3 id=&quot;kalman4idiots&quot;&gt;Kalman4Idiots&lt;/h3&gt;

&lt;p&gt;I was originally trying to crack this by writing a Kalman filter and, while I could get ChatGPT to spit out a good implementation of one, I still don’t &lt;em&gt;entirely&lt;/em&gt; understand what it is doing. This is a real kind of moment where I wish I had eaten more of my maths vegetables in the past.&lt;/p&gt;

&lt;p&gt;I then wrote this weird filter that turned out to have really good performance - it keeps a velocity estimate and uses that to improve position estimates (taking i.e. 20% of the estimate and 80% of the reading, depending on a setting), and conversely uses the position estimate as a basis for the &lt;em&gt;velocity&lt;/em&gt; estimate (which is filtered using a normie exponential filter).&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Kalman4Idiots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alpha_pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alpha_vel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alpha_pos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alpha_pos&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alpha_vel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alpha_vel&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt; 

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;measurement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;vel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;measurement&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos_est&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alpha_vel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alpha_vel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
        &lt;span class=&quot;n&quot;&gt;pos_predict&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;measurement&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alpha_pos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pos_predict&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alpha_pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos_est&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_est&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This actually worked really well, in fact I could tune it to about the same performance as the Kalman implementation (with the bonus that I had an intuition for the knobs I was dialling). These plots are coming from tests using data that I generated in numpy with some artificial noise - so I’m not actually sure how well this would work on real data.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-21_01.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Performance of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Kalman4Idiots&lt;/code&gt; filter was surprisingly good.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A neat trick that I will probably stash for later - and that could presumably be extended into acceleration… but it works like shit when we have substantial changes to acceleration - unsurprising since it doesn’t have internal prior that acceleration might happen.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-21_05.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The same filter, but subjected to big changes in acceleration. The position estimate basically lags the real position by some amount that is proportional to the acceleration.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;alpha-beta-gamma-observers&quot;&gt;“Alpha Beta Gamma” Observers&lt;/h3&gt;

&lt;p&gt;I wound up learning (again) about &lt;em&gt;observers&lt;/em&gt; - I originally saw these when I took &lt;a href=&quot;https://www.eecs.mit.edu/people/jacob-white/&quot;&gt;Jacob White’s&lt;/a&gt; beginner controls class (IIRC it was 6.302)… I probably should have finished this project right after that class, but at least the seed was planted!&lt;/p&gt;

&lt;p&gt;The basic idea is that we have a model of our system and we track its internal states. This one in particular is simple: we have pos, vel, accel, and the model just says that acceleration turns into velocity and velocity turns into position. At each time step we make a prediction, and we compare our prediction to the measurement. The difference is called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;innovation&lt;/code&gt; (unpredicted change!), and we can update our estimates using some amount of that innovation - i.e. three filter values (the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alpha, beta&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gamma&lt;/code&gt;) that are ~ related to our trust in the measurement.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AlphaBetaGamma4Idiots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# alpha = position gain, beta = velocity gain, gamma = acceleration gain 
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# we assume position is slow to change (small gain), 
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# velocity is faster (larger), 
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# accel is nearly instant (arbitrarily big) 
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alpha&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;beta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gamma&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vel_max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;acc_max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alpha&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alpha&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;beta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;beta&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gamma&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gamma&lt;/span&gt; 

        &lt;span class=&quot;c1&quot;&gt;# very-useful abs. bounds:
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_max&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vel_max&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;acc_max&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;acc_max&lt;/span&gt; 

        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;acc_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt; 

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;measurement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;pos_pred&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;acc_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;vel_pred&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;acc_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;acc_pred&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;acc_est&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# ... all change in accel is due to new info 
&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# unpredicted change... 
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;innovation&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;measurement&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pos_pred&lt;/span&gt; 

        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pos_pred&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alpha&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;innovation&lt;/span&gt; 

        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vel_pred&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;beta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;innovation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_est&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;acc_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;acc_pred&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gamma&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;innovation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;acc_est&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;acc_max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;acc_est&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;acc_max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos_est&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_est&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;acc_est&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I had a really hard time getting this to be stable &lt;em&gt;until&lt;/em&gt; I added the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vel_max&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;acc_max&lt;/code&gt; values… which are pretty easy to guess using system knowledge (and they don’t have to be too close to reality, just guesses with lots of overhead). Without them, what can happen is this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;we make a measurement that causes us to make a big acceleration and velocity innovation&lt;/li&gt;
  &lt;li&gt;the next cycle, our prediction is &lt;em&gt;well&lt;/em&gt; ahead of the measurement, and now we dial it back in the other direction (overcorrecting)&lt;/li&gt;
  &lt;li&gt;etc, until we hit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;np.inf&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This one I was ablt ot tune up pretty well to my test data, and the addition of an internal acceleration state (while noisy…) is cool.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-21_07.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Output from this alpha-beta-gamma filter, showing good tracking for position and velocity, with noisy (but available!) tracking of an acceleration state.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I went ahead and implemented this in cpp on the device, so now I have states available in hardware, although I haven’t properly tuned it, which is kind of future work at this point. That said, the gains should be about the same from motor-to-motor, since they are only related to the noise that comes from the encoder, and that shouldn’t change drastically. Tuning the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vel_max&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;acc_max&lt;/code&gt; is similarely simple: we should basically cap them such that they don’t allow the system to explode, which can be calculated easily if we know the time step taken between measurement / update steps.&lt;/p&gt;

&lt;h2 id=&quot;current-controller-pi-gains&quot;&gt;Current Controller PI Gains&lt;/h2&gt;

&lt;p&gt;To get to the next steps, we need to tune the current controller gains. I learned that this is possible if we know the motor’s coil resistance \(R\) and inductance \(L\) - both of which are on the datasheet for this motor, and &lt;em&gt;can&lt;/em&gt; be measured using this hardware, although I haven’t implemented that step yet.&lt;/p&gt;

&lt;p&gt;\(k_p\) is the simplest, check it out: \(k_p = R\) - seems too good to be true, but consider the units \(V / A\) - i.e. how many volts should we apply to get one amp of current correction? That’s just ohm’s law.&lt;/p&gt;

&lt;p&gt;\(k_d\) doesn’t exist, basically beacause there’s no / negligible inertia in the system, thank god.&lt;/p&gt;

&lt;p&gt;\(k_i (V/A \cdot s)\) is messier - we want to pick a bandwidth for the controller first. We can use the electrical time constant to understand what a maximum bandwidth might be:&lt;/p&gt;

\[\tau = \frac{L}{R} = \frac{0.01}{6.8} = 0.00147s\]

&lt;p&gt;The time-constant is how long it takes for the system to hit ~ 63% of its final state, after a step input, so \(\frac{1}{\tau} = f_c = 680Hz\) represents the frequency that we can expect to swing the thing around. So, an upper bound on how fast our current controller can expect to operate. What still confuses me here is that this somehow applies &lt;em&gt;in the rotating frame&lt;/em&gt; even though the underlying hardware is rotating much faster. As will appear later, my non-understanding of frequency-dependent impedance of the motor-as-and-inductor is causing some problems.&lt;/p&gt;

&lt;p&gt;In any case we can pick this as a target bandwidth, and use \(k_i = 2\pi f_c \cdot L = 6.8\) (!)&lt;/p&gt;

&lt;p&gt;I will say, I don’t completely understand this step, but it works terribly well so I am leaving the comprehension for next time.&lt;/p&gt;

&lt;h2 id=&quot;a-position-pid-loop-and-loop-timings&quot;&gt;A Position PID Loop, and Loop Timings&lt;/h2&gt;

&lt;p&gt;Once the core FOC layer and observer was working, this step was pretty uncomplicated: position estimate comes from the observer and the PID controller decides how much torque to put on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;q&lt;/code&gt; axis of the FOC controller.&lt;/p&gt;

&lt;p&gt;Tuning the gains is another question: it is easy to make the thing work, but difficult to make it work well. I get the sense that tuning in-general (of the current controller PI gains, of the observer gains, etc) is the main challenge for closed-loop motion systems, and (besides cost, which is actually not that great) is probably why most simple systems use open loop motors, or ‘closed loop’ motors that only use their encoders to recover lost steps.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;video controls=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot; muted=&quot;true&quot;&gt;
  &lt;source src=&quot;videos/2025-03-30_torque-pid.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Here I’m periodically updating a position PID’s setpoint, and plotting torque that the motor is using to hit that target. The gains aren’t execellent, and the inertia is huge relative the actuator, but it at least closes the loop across the whole system.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I also tightened up my loop timings at this point. The current controller bandwidth is way higher than I think is probably necessary, but it is pretty cheap to run. The spline interp / position control is fast enough, but ideally we would actually poll the encoder at extremely high speed, since commutating the many-pole-pairs of the stepper motor requires really high frequency position information… in fact, in a couple of cases I saw current sinusoids on the oscilloscope that were only two or three PWM periods long - meaning the motor rotated through one-half electrical rotations in as much time.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;30kHz PWM&lt;/li&gt;
  &lt;li&gt;30kHz Current Control&lt;/li&gt;
  &lt;li&gt;15kHz Spline Interpolation&lt;/li&gt;
  &lt;li&gt;15kHz Position Control&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-07_01-loops-decay.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Via &lt;a href=&quot;https://oraclerobotics.com/support/app-notes/&quot;&gt;app note 103 / 102&lt;/a&gt; from Oracle Robotics. Shows ‘typical’ loop speeds for closed loop servo control - although this is likely to vary application to application and across motor sizes, with smaller inertias requiring more responsive control. These intervals are substantially larger than those that I implemented, probably typical of larger motors with less pole pairs, but also means that I could probably get away with less Hz if I could tune these loops like the experts.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To give myself some more overhead for all of this, I am running the SAMD51 at 180MHz - a little more than the ‘spec’ 120MHz. Using Platformio and Adafruit’s M4 Feather Core, this was a really easy config change. Internet suggests it can run at 200MHz, but I think I have it in a somewhat noisy environment so I will leave it here.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-31_00-isr-traces.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;I have two ISRs here, and interleave operations. Basically the whole controller runs in these ISRs besides the interface, and it takes about 25% of the processor’s time to do so. Not pictured are the ISRs that handle the SPI transactions for the encoder. Each sequence is triggered by the TCC’s overflow or underflow, which sends an event to the ADCs to start a sample. That event also triggers the first ISR (the tiny slice), which records pin states during the sample time. The second ISR is the ADC’s conversion-complete signal, where we wake up again and pick up the new data. Then we either (1) go back to sleep if we are still waiting for the second ADC sample, (2) run the current controller, the interpolator, and tell the encoder to start acquiring a sample, or (3) run the current controller and position controller, using the new encoder reading. The interpolation step is the longest (up to 10us) since it is evaluating a basis spline and potentially incrementing through a buffer of control points.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;measuring-motor-parameters-and-some-modelling&quot;&gt;Measuring Motor Parameters, and some Modelling&lt;/h2&gt;

&lt;p&gt;OK, with hardware it normally takes a long time to sort out the nuts and bolts, but then we get to do the fun stuff. That is the point we are finally at here; we get to (try to) learn some parameters for the motor-under-test (MUT)’s model, so that we can report &lt;em&gt;torques&lt;/em&gt; while it is in use… so that we can model our machine systems / motion systems while we operate them! Modeling / understanding motor parameters also helps us to tune some of those nasty gains without thinking too hard.&lt;/p&gt;

&lt;p&gt;Robots measuring their own forces is sometimes called &lt;em&gt;proprioception,&lt;/em&gt; a term borrowed from biology for ‘the sense of self-movement, force and position.’ - i.e. the sense you use when you’re reaching inside the cheeze-its box to find that last morsel is proprioception.&lt;/p&gt;

&lt;p&gt;In the case of most electric motors, output torque is more or less directly proportional to the current that is active on the &lt;em&gt;q-axis,&lt;/em&gt; but we need to fit a few parameters in order to know how this current relates to torque.&lt;/p&gt;

&lt;p&gt;I want to add a caveat here that this is something &lt;em&gt;I am still learning about&lt;/em&gt; - so … don’t trust my maths, basically.&lt;/p&gt;

&lt;h3 id=&quot;the-canonical-motor-model&quot;&gt;The Canonical Motor Model&lt;/h3&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/for-five-hours-now.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;It’s beautiful, but lossy.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

\[\ddot{\Theta}J = k_tI - k_d\dot{\Theta}\]

\[I = \frac{V_a - k_e\dot{\Theta}}{R}\]

\[V = RI + L\dot{I}\]

&lt;ul&gt;
  &lt;li&gt;\(V_a\) is the voltage &lt;em&gt;applied&lt;/em&gt; to the motor, i.e. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v_a = vcc_supply * pwm_duty&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;\(\dot{\Theta}, \ddot{\Theta}\) velocity and acceleration &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(rads)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;\(Q = torque = \ddot{\Theta}J\) is the identity for torque &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(Nm)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;\(R\) is coil resistance &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(Ohms)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;\(J\) is inertia of the rotor and whatever else is attached &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(kg \ m^2)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;\(k_d\) is damping due to friction &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(force / speed)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;\(k_e, k_t\) are emf and torque constants&lt;/li&gt;
  &lt;li&gt;\(L\) is motor inductance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This model is what you’ll find if you’re studying DC motors, i.e. the brushed kind. With FOC, we do all of this work to commutate the motor… so that we can ignore that part when we make these models. To disambiguate, in these models the \(V, I\) terms are basically assumed to lie on the quadrature axis.&lt;/p&gt;

&lt;p&gt;Once we fit each of these, we should be able to use \(k_tI\) to measure output torque - since we can measure current directly. Eventually I want to measure each of these automatically, but for now my process to find the physical constants was thsi:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Measure \(R\) with a multimeter (it’s easy)&lt;/li&gt;
  &lt;li&gt;Measure \(J\) by drawing the motor rotor in CAD, and having that software calculate it&lt;/li&gt;
  &lt;li&gt;Measure \(L\) by reading the motor datasheet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A real measurement of \(L\) is probably the most important thing to improve the readings’ precision, same with actual \(R\) measurements, since the datasheet sepcs are for \(\frac{+}{-} 20\%\) (!). Even \(R\) changes by about 10 percent coil-to-coil in the same motor.&lt;/p&gt;

&lt;p&gt;I also found that adding some real inertia to the motor is basically required, since the rotor’s own inertia is normally so small as to be negligible in the face of the torque it can produce. That is, when we spin up &lt;em&gt;just&lt;/em&gt; the rotor, the change in direction is so fast that we can’t really sample any of the dynamic states that happen during that turnaround. So: enter  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;big disk of PLA,&lt;/code&gt;  the world’s simplest dynamometer.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-30_rotor-und-rotor.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The motor’s own rotor has some inertia: for this tiny NEMA14, that’s about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.000001878 kg m^2&lt;/code&gt;. Because this is so small, it was nearly impossible to get meaningful data for a model fit (although I did try): the rotor accelerates to its no-load speed in about 10ms, not enough time to gather very many samples. I bolted this giant PLA disk on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0.002888 kg m^2)&lt;/code&gt; to slow things down.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As an interesting aside, a common rule of thumb for motion systems design is to pick an actuator whose reflected inertia is about equal to the inertia of the component it is pushing around. The inertia that I printed to make sampling simpler is ~ 1000x bigger than the actuator’s rotor.&lt;/p&gt;

&lt;h3 id=&quot;spin-up-data-and-filtering&quot;&gt;Spin-Up Data and Filtering&lt;/h3&gt;

&lt;p&gt;To fit the rest of the parameters, we want to make some time-series data of the system operating and then fit that to a model. For this, I put the motor in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;direct_q_current&lt;/code&gt; mode, where we can just tell it how much current to try to put on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;q&lt;/code&gt; axis. Then we just… swing it around: apply maximum torque in one direction, wait for about ten seconds, and then flip directions, waiting for another 10-or-so. All the while we are sampling a set of the motor / controller states and stashing them for analysis.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;video controls=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot;&gt;
  &lt;source src=&quot;videos/2025-03-30_spinup.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Data collection is simple - I just spin a known inertia around using maximal applied current, flipping directions after some number of seconds and taking a few passes in either direction.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-30_model-ts-00.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Time-series data from the test spin-ups. Note that while &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vcc_q&lt;/code&gt; moves rail-to-rail, the amount of current it generates is highly dependent on the rotor speed - this is back emf in action. Also interesting is to watch the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vcc_rail&lt;/code&gt; signal (bottom trace): this is the power supply sagging when we pull current (it’s a USB-PD brick…).&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-30_model-ts-01.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The observer’s velocity estimate is noisy (and it’s acceleration estimate is almost useless), so I filtered velocity using some newly acquired scipy magic.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-30_model-ts-02.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Once it’s filtered, we can take a discrete derivative of the velocity estimate to get acceleration data. These are still a little noisy, but serviceable.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We do a little filtering, etc (thanks scipy!) and then we can basically plot accelerations vs. speed, i.e. in ‘torque-curve’ land.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-30_04-terqs-startup-clean.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Looking at data from the spin-ups w/ velocity (x) and accelerations (y), with the rotational symmetry because we are spinning in either direction.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;fitting&quot;&gt;Fitting&lt;/h3&gt;

&lt;p&gt;To fit this, I’m just using scipy’s built in optimizer. We write a model with some params, then provide it with data and a loss function and if things aren’t too hairy it tends to work pretty well.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;R&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;6.8&lt;/span&gt;                 &lt;span class=&quot;c1&quot;&gt;# ohms
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;J&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.000001878&lt;/span&gt;         &lt;span class=&quot;c1&quot;&gt;# kg m^2 (rotor) 
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;J&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;J&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.00361&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.8&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# adding le-diskus, plus print density ... 
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vcc_app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vel_now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; 
    &lt;span class=&quot;c1&quot;&gt;# assert that ke works against us regardless of speed dir. 
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;ke&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_now&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cur_now&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vcc_app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ke&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vel_now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;acc_now&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cur_now&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vel_now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;J&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cur_now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;acc_now&lt;/span&gt; 

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vel_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cur_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;acc_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# hmmm 
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;cur_guess&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;acc_guess&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vel_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;alpha&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alpha&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;cur_error&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;square&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cur_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cur_guess&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;acc_error&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;square&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;acc_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;acc_guess&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cur_error&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;acc_error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# outfit an initial guess 
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params_x0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# run the minimizer, from scipy.optimize.minimize 
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;minimize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fun&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;x0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params_x0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
    &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vel_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cur_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;acc_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I had also tried a scheme where we reshape the data into input and output states across time steps, then use the optimizer to tune variables so that an ODE of the system would correctly predict the next step, but I found that pretty ineffective, which is a shame since it’s a cleaner way to articulate dynamics in-general.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-30_05-le-fit.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;A fit! Although some wisdom says that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k_e&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k_t&lt;/code&gt; are equivalent, I let this model fit them independently because I suspect that in this case &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k_e&lt;/code&gt; also captures losses due to phase lag in the commutator - i.e. at high speeds the q-axis is not exactly aligned with the rotor’s real position.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;whats-up-with-the-mismatch-&quot;&gt;What’s up with the Mismatch ?&lt;/h3&gt;

&lt;p&gt;There’s obviously a lot missing in the model. What initially confounded me is that the model suggests that when the applied voltage is &lt;em&gt;positive&lt;/em&gt; but velocity is &lt;em&gt;negative,&lt;/em&gt; (or vise-versa) we should see much more torque, since the effective voltage is greater with EMF working ‘with’ the power supply: \(V_{effective} = V_a - k_e (- \dot{\Theta})\)&lt;/p&gt;

&lt;p&gt;The big mystery to me is how the power supply acts when faced with regenerative loads. For these tests I was using a USB-PD brick with a decoy, which is probably a sin in power electronics. In the data traces where I measured supply voltage during the tests, it doesn’t change meaningfully during these reversals (although it does dip when we use lots of current). In my understanding, even regenerative power supplies will only increase the supply voltage marginally during regeneration, doing so to drive current back into a battery (or dumping it into a resistor), so the back half of this plot should at least be flat.&lt;/p&gt;

&lt;p&gt;I suspect a few things are happening here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(1)&lt;/strong&gt; The commutation speed is not fast enough to keep up with high angular velocities, so ‘on-axis’ current as measured is not totally on-axis, since the rotor’s electrical angle changes so quickly. This is especially true for a stepper motor, where we have 50 electrical rotations in each mechanical revolution. On the scope, I could see instances where one electrical rotation occurred over the course of only four or five PWM cycles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(2)&lt;/strong&gt; There are lots of losses that aren’t modeled here. Motors make a lot of heat in the form of \(I^2R\) and we also heat the stator and rotor iron as we spin it through the magnetic hysteresis loop. This is probably the most obvious contributor to the fact that the output torque drops off irregardless of the sign of speed w/r/t the sign of the applied torque… most of the power in these states is consumed by simply swinging the magnetic field around, rather than turning into useful current. I think on this front I need to understand the motor-as-an-inductor’s frequency dependent impedance… that pesky inductance - actually it is staring at me right in the face: \(V = IR + L\dot{I}\) - the term I didn’t stick into this model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(3)&lt;/strong&gt; The motor may be saturating its magnetic field. Current is equivalent to torque so long as we haven’t reached saturation, but the relationship is nonlinear once we are making lots of field meaning that as we drive higher and higher currents, torque stops increasing. IIRC most magnet steels saturate around 2.0 Tesla of field strength - this is one of the fundamental phyiscal limits to electric motor performance (and transformer performance!). This might contribute to the fact that the torque seems to ‘level off’ around zero velocity (i.e. is bell-shaped).&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/magnet-hysteresis-loop.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Via &lt;a href=&quot;https://en.wikipedia.org/wiki/Magnetic_hysteresis&quot;&gt;Wikipedia&lt;/a&gt;, also see &lt;a href=&quot;https://www.e-magnetica.pl/doku.php/magnetic_saturation&quot;&gt;Encylopedia Magnetica&lt;/a&gt;. The magnetic hysteresis loop - magnetization (m) is related to field applied by an electromagnet (h), but saturates when h is big. The area inside of the loop represents loss.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, modelling is hard and the physics become complicated once we really dive into it. I think it is cool at least to be getting data at this point, and fitting some simple models, although I am weary to trust them too much.&lt;/p&gt;

&lt;p&gt;To continue testing and learning, I think that I should more carefully monitor the power supply’s behaviour. USB-PD bricks are obviously not meant for this kind of thing, and I was hardly even using that much bypass capacitance in these tests.&lt;/p&gt;

&lt;p&gt;I also suspect that losses and winding impedance at higher frequencies are the primary missed-model-components here, so I should endeavor to model them next.&lt;/p&gt;

&lt;p&gt;I should also check to see what the effect of changing the drive mode is in the hbridges: at the moment, when the applied voltage is ‘off,’ current is allowed to recirculate in the low-side of the bridge. In prior controllers, this was different - ‘off’ time used a fast-decay mode. It might be the case that fast-decay is advantageous when we are slowing down, and recirculation is better when we’re accelerating.&lt;/p&gt;

&lt;p&gt;Finally, I should try to improve the commutation speed. Using the observer, I should be able to update the rotor’s position estimate more often than I am able to sample the encoder - which I am already polling as fast as that hardware allows me too.&lt;/p&gt;

&lt;h2 id=&quot;measuring-torque--trajectory-following&quot;&gt;Measuring Torque + Trajectory Following&lt;/h2&gt;

&lt;p&gt;So far, none of this is actually directly useful to me, besides being a really great learning exercise. As soon as I realized how difficult it would be to tune good PID gains for the motor (esp. since changing dynamics…), I was trying to think of a way to measure active torque &lt;em&gt;while running in open loop,&lt;/em&gt; since that control mode is so dead-nuts simple, and the PI gains for the current controller are at least easy to tune if we know the electrical values (R, I) which in this case we can get from the datasheet.&lt;/p&gt;

&lt;p&gt;In practice, I have machines with ~ 8 motors total, and tuning gains for each of them remains too-difficult to be worth it, especially since open loop steppers already work &lt;em&gt;well enough&lt;/em&gt; - instead, I want to be able to use these motors to &lt;em&gt;make models&lt;/em&gt; of the system so that I can i.e. intelligently tune my lookahead controllers, i.e. set maximal accelerations and speeds.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;video controls=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot; muted=&quot;true&quot;&gt;
  &lt;source src=&quot;videos/2025-03-30_current-follow.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Here, the motor is following an open-loop s-curve trajectory generated with my motion controller. It’s using the current controller stage for this, and reading the encoder just to ascertain how much of the current is on the d-axis (in blue) vs. on the q-axis (red) (y-axis is scaled incorrectly, whoops!). Only current on the q-axis is responsible for generation motion, the rest is waste - this is typical of a stepper motor. The oscillation in both signals comes from the motor’s rotor ‘springing’ around to align with the magnetic field.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a while I thought this would &lt;em&gt;require&lt;/em&gt; the position controller, using the output from that controller to measure how much ‘effort’ was required to move the motor under certain tracking conditions - but really it is pretty simple: &lt;em&gt;we can always measure on-axis current&lt;/em&gt; to know how much of it is actually responsible for moving the rotor around.&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;c1&quot;&gt;// in controller.cpp, current control loop &lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CONTROLLER_MODE_CURRENT_FOLLOWER&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// we track active currents w/ the rotor-tracking projection, &lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;active_dq_currents&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lut_trig_xy_2_dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;enc_2_elect_phase_latest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;active_ab_currents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// but we control current on a target angle that is moving in open-loop, &lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// following our interpolator blindly... &lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;follow_dq_currents&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lut_trig_xy_2_dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;follower_target_angle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;active_ab_currents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;active_dq_voltages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pi_d_axis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;follower_d_current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;follow_dq_currents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;active_dq_voltages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;q&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pi_q_axis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;follow_dq_currents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// transforming that back and writing voltage / pwm as usual&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;active_ab_voltages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lut_trig_dq_2_ab&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;follower_target_angle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;active_dq_voltages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;hbridges_write_pwms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;active_ab_voltages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HBRIDGE_PWM_REQ_MAX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;active_ab_voltages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HBRIDGE_PWM_REQ_MAX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is fundamentally the same to any other open loop stepper driver / controller, but we are leveraging the FOC hardware to generate a signal that we can use to see how much force is actually being exerted on the system. The modeling above, also, helps us in setting our feed-forward parameters like maximum acceleration and velocity… soon, I’ll also be able to write about how I’ve been using those models more completely with a model predictive controller deployed for lookahead planning on a machine system.&lt;/p&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;

&lt;p&gt;Well, I set out to make a controller that could turn &lt;em&gt;(almost) any&lt;/em&gt; stepper into a high performance servo - a goal shared by many - but I spent most of the allotted time in the nuts-and-bolts.&lt;/p&gt;

&lt;p&gt;I’m glad to understand the problem a little more in-depth, and to have seen all of the gains we have to tune:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the encoder calibration
    &lt;ul&gt;
      &lt;li&gt;done, reliable, repeatable, and easy to sample&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;current controller PI gains
    &lt;ul&gt;
      &lt;li&gt;easily done with known R and L, but somewhere beyond my current comprehension&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;observer alpha, beta, gamma gains
    &lt;ul&gt;
      &lt;li&gt;should be purely functions of the present noise, but hand-tuned for now&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;position PID gains
    &lt;ul&gt;
      &lt;li&gt;a strong function of the motor’s &lt;em&gt;context&lt;/em&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;model kt, ke, and kd
    &lt;ul&gt;
      &lt;li&gt;made a reasonable fit, but clearly missing the inductor’s impedance&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I would say that at this point I have learned where all of the problems are, and where the holes in my understanding lie - honestly a great success. I would dive back into the impedance issue right now, I think having figured it out as I wrote all of this down, but I have to get back to the main activities.&lt;/p&gt;

&lt;p&gt;I have also been thinking a lot about how to hook this up to the rest of my motion controller. In my machine systems, I stream splines to motors using a fixed point represtentation and a basis spline (aka b-spline) formulation. These have been really productive, and they have the nice property that we can use them to evaluate not just position, but also velocity and acceleration at any time (within a window of available control points).&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/out-05.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;I commonly use basis splines to transmit trajectories from planners to motors. \(P(t)\) is position as a function of time - the first, second and derivatives are also well defined: velocity, acceleration and jerk.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I saw a note recently on feed-forward terms in position controllers like this one, which is that it’s productive to feed forward some gains from a trajectory’s acceleration and speed curves.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2025-03-07_02-loops-hooked-up.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Via &lt;a href=&quot;https://oraclerobotics.com/support/app-notes/&quot;&gt;app note 103 / 102&lt;/a&gt; from Oracle Robotics - shows how acceleration and velocity terms from a known trajectory can be fed into a position control loop - it also notes a gravity compensation term (towards bottom right).&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The splines obviously provide a handy set of accel and velocity values to feed-forward, and the gains I would bet we can find using a more complete motor model and some knowledge of the inertia that the motor is working with… it might even be productive to try an LQR controller with multiple inputs (target accel, vel, pos), rather than i.e. the PID with feed-forward gains.&lt;/p&gt;

&lt;p&gt;I also have lots of work to do in the firmware to i.e. remotely write encoder tables, and work to do in the calibration scripts to integrate it all into one or two steps: plug in a board, get the encoder table, run some tests and fit the rest of the system. Then there is the question of how to cleanly represent all of that to higher level motion planners.&lt;/p&gt;

&lt;p&gt;For a litmus test, I would like to verify the torque measurements with calibrated loadcells - one of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;knuckles&lt;/code&gt; boards is a CS5530 loadccell amplifier that can run up to 4kHz, it would be awesome to see time-series of the motor in action reporting torques and compared to direct measurements with one of those.&lt;/p&gt;

&lt;p&gt;So! I have probably just scraped the surface on this one. I should endeavor to graduate soon and become free to work more intensely on these things; I would really love to provide high-quality, closed loop, comprehendible open source motion control systems that are easy to use… soon!&lt;/p&gt;

</description>
        <pubDate>Mon, 24 Mar 2025 00:00:00 -0400</pubDate>
        <link>http://ekswhyzee.com/2025/03/24/foc-stepper.html</link>
        <guid isPermaLink="true">http://ekswhyzee.com/2025/03/24/foc-stepper.html</guid>
        
        <category>control</category>
        
        <category>circuits</category>
        
        <category>embedded</category>
        
        <category>motors</category>
        
        <category>dyno</category>
        
        
      </item>
    
      <item>
        <title>A Thesis Proposal!</title>
        <description>&lt;p&gt;I’ve been at the CBA for &lt;em&gt;seven and one half years&lt;/em&gt; and it is time to scoot. I have learned very many things, met many exceptional people, and built many circuits, fimwares, machines, systems, etc.&lt;/p&gt;

&lt;p&gt;A ‘thesis proposal’ is what you do when you think you have figured out &lt;em&gt;what you will do&lt;/em&gt; to finish a PhD. Academia is weird because you are inventing new things, and so no-one (besides a collection of your peers / advisors) can decide if you &lt;em&gt;have done “it”&lt;/em&gt; or not. The proposal is to say: I will do &lt;em&gt;this thing, and that will be enough&lt;/em&gt; - if your committee agrees, you do the thing, and then you are done. A bit of a contract.&lt;/p&gt;

&lt;p&gt;I am proposing to outline (and develop!) a systems architecture that could replace GCode. The thing is ostentatiously titled:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://jakeread.github.io/thesis-proposal/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;The End of GCode: Machine Architectures for Feedback Systems Assembly&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I made the document into a website using an awesome tool called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;quarto&lt;/code&gt; that I would recommend to anyone who has to write papers and thinks that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LaTeX&lt;/code&gt; is kind of insane. The website is &lt;a href=&quot;https://jakeread.github.io/thesis-proposal/&quot;&gt;here&lt;/a&gt;. There is also a &lt;a href=&quot;jake-read-thesis-proposal.pdf&quot;&gt;pdf&lt;/a&gt; (auto generated) - but the website is vastly superior, and if you are intrigued by the idea you should read it and tell me what you think. The slides that I used for the presentation are &lt;a href=&quot;https://jakeread.github.io/slides/2024-12_proposal/2024-12_proposal.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/machine-doesnt-know.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Most of the world’s 3D Printers and CNC Milling machines’ realtime controllers know very little about what they are doing, they simply consume and execute series’ of low-level instructions called GCodes. Those codes are generated in software that knows little about the machine (and has no way to verify what it does know).&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GCode is known by many and loved by few (besides cases of trauma bonding). It is an archaic &lt;del&gt;language&lt;/del&gt; script / layer / interface / bungled mess. It gets in between us (programmers, users, people) and machines, which are doing very cool physics but we would hardly know how to ask them about it.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/big-arch-drawing.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;A systems overview of the contributions made in this thesis. I refactor a mostly feed-forward workflow and replace parameter selection with online optimization. In this architecture, machines can continuously improve datasets and update models of their own physics, which they use in order to control their outputs.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The core contribution I am making is basically a system architecture that re-casts machines as networks where path planning, hardware control, and sensing are all bits of the same distributed system. Instead of GCode getting in the way anywhere, we can freely connect various software blocks into one another, running as they are on various pieces of hardware: motor controllers, sensors, ‘big-computers’ (aka PCs: things with lots of compute power), etc. This lets us easily move pieces of the controller around, recombine them, take them apart, inspect them, share them, etc.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;video controls=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot;&gt;
  &lt;source src=&quot;../../../common_videos/2024-07-11_printing-time-sync-enc.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Machines that can measure what they are doing: here, an FFF printer measuring nozzle pressure and filament flowrates during printing.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This seems kind of obvious (aren’t all machines already made of various blocks of compute?) but state-of-the-art controllers are strictly partitioned; parts of the control (CAM, path planning) happen ‘before’ the GCode, and the rest (motor control, acceleration control, etc) happen ‘after’ the GCode. The partitioning is largely, we think, due to historical mistakes, inter-firm boundaries, and limited embedded compute availability.&lt;/p&gt;

&lt;p&gt;The partition has made machine controllers very difficult to modify, measure, and repurpose. It also poses a real problem for high-level planning algorithms (like CAM), whose fundamental constraints (how fast can I push an endmill through this material, how fast can I extrude this filament, and what temperature should I use?) &lt;em&gt;are functions of the machine’s physics&lt;/em&gt; - but we can only measure those physics by interacting with the low-level devices!&lt;/p&gt;

&lt;p&gt;I develop lots of new bits of tech for this: a very simple, flexible and decently performant networking scheme, some simple link layers and data presentation layers, &lt;em&gt;and&lt;/em&gt; of course a new approach to motion control. The controller is composable (easy to edit and configure), and &lt;em&gt;inspectable&lt;/em&gt; - it exposes lots of internal states to programmers.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/motion-internals.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Motion controllers’ internals are normally hidden from high-level programs (like CAM tools). However, low level motion states and optimizer outcomes are important for their performance. In this example, I show the results of rotating &lt;em&gt;the infill&lt;/em&gt; in a 3D print so that it aligns with a machine’s anisotropic acceleration.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But why? Don’t machines work well enough? Well - have you ever tried printing a new filament on a printer that didn’t have a preset set of parameters for it, or tuning the motion system for a new machine you have built, or programming a toolpath on a CNC machine? Or re-purposing a 3D printer’s controller to do something silly or novel? These things are not easy, but they should be. They are difficult primarily because &lt;em&gt;machines don’t know what they’re doing&lt;/em&gt; - they are just following very simple instructions very faithfully: once we have exported GCode, the smarts have left the system.&lt;/p&gt;

&lt;p&gt;So! I then use my fancy systems architecture to develop (1) machines that can tell what they’re doing (sensors!) and then (2) a controller that can use that data to get smart, a &lt;em&gt;model predictive controller&lt;/em&gt; for FFF printing.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;video controls=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot;&gt;
  &lt;source src=&quot;../../../common_videos/2024-10-23_01-motion-solve-with-e-compress.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Distributing controllers lets us more rapidly develop fancy controllers, like this model predictive controller for an FFF machine’s motion &lt;em&gt;and&lt;/em&gt; flow systems, which are optimized together, online.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For more, &lt;a href=&quot;https://jakeread.github.io/thesis-proposal/&quot;&gt;visit the site&lt;/a&gt;, and give me about three or four more months to finish the project and get stamped… after which I will take on the project of trying to explain and share all of the goodies into the commons.&lt;/p&gt;
</description>
        <pubDate>Thu, 12 Dec 2024 00:00:00 -0500</pubDate>
        <link>http://ekswhyzee.com/2024/12/12/thesis-proposal.html</link>
        <guid isPermaLink="true">http://ekswhyzee.com/2024/12/12/thesis-proposal.html</guid>
        
        <category>systems-assembly</category>
        
        <category>systems-design</category>
        
        <category>control</category>
        
        
      </item>
    
      <item>
        <title>Modular Code for Modular Hardware w/ Metaprogramming</title>
        <description>&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/auto-grep-diagram.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Remote Procedure Calls (RPCs) are a great pattern for interfacing with embedded codes - especially when they replace serial string parsers - but they can be tiresome to generate by-hand. In this post, I demo an automatic RPC generator that pulls function signatures directly out of compiled code on embedded devices, and rolls up python interfaces for them.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;automated-rpcs-and-templated-proxy-classes&quot;&gt;Automated RPCs and Templated Proxy Classes&lt;/h2&gt;

&lt;p&gt;A few weeks ago I finally finished the &lt;a href=&quot;../../02/09/no-more-newline.html&quot;&gt;automatic Remote Procedure Call (RPC) class&lt;/a&gt; for arduino, which lets us automatically build interfaces for embedded devices using most basic programming concept: the function call. We write a function on the device, we tag it with an RPC macro, and it becomes available to call over the network.&lt;/p&gt;

&lt;p&gt;This post is about the other half of that workflow: generating software interfaces - &lt;em&gt;“proxies”&lt;/em&gt; - that represent embedded devices in higher level programming languages. In particular, previous work required that we use the &lt;em&gt;auto RPC&lt;/em&gt; pattern within a standalone code editor - in this work we can deploy the modules more fluidly.&lt;/p&gt;

&lt;p&gt;I’ve been interested in this kind of work because I think that Open Hardware doesn’t have a great set of tooling for &lt;em&gt;systems integration,&lt;/em&gt; and I think that good, modular interfaces can lead us into the &lt;a href=&quot;../../10/16/searching-for-oshw.html&quot;&gt;commons of Open Hardware.&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;automated-rpcs-in-the-wild&quot;&gt;Automated RPCs in the Wild&lt;/h2&gt;

&lt;p&gt;So… what is this for? Well, for one fun example, we have this little robot xylophone project that I finished with Quentin. It runs hardware / firmware modules of my design, whose APIs are exposed using the system I’ve described here. Quentin, the CBA’s resident CV lover, used those interfaces to stack an interactive controller w/ a camera and MIDI keyboard onto the machine:&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;video controls=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot;&gt;
  &lt;source src=&quot;../../../common_videos/xylo.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Building good interfaces to our hardware helps other developers build applications &lt;em&gt;on top&lt;/em&gt; of what we’ve done - i.e. this robo-xylophone serves an API that Quentin used to turn it into a MIDI keyboard output device (including CV to track finger position to preempt the key presses).&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I use these also to build bigger systems, like this FDM printer that is collecting data on printing physics while-it-prints. Doing this in embedded alone would be obviously cumbersome, but being able to partition between big-compute (to collect and analyse) and embedded-compute (to listen and actuate) makes it pretty easy.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;video controls=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot;&gt;
  &lt;source src=&quot;../../../common_videos/2024-07-11_printing-time-sync-enc.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Good embedded interfaces also helps us de-clutter big systems, like this FFF 3D printer that captures process data while it operates.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/netmachine-printer.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The printer’s controller is mostly built in python, using interfaces to hardware devices built using the strategy described in this post. Devices are simple and relatively stateless: motors know how to follow spline trajectories (but don’t manage i.e. acceleration control), sensors can generate readings, but high level program state is managed and modified without editing firmware.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a clear example of &lt;em&gt;digital twinning,&lt;/em&gt; when we virtualize our controllers, it becomes also very easy to build parseable virtual models of them, and pipe data back and forth, i.e. to debug kinematics:&lt;/p&gt;

&lt;video controls=&quot;&quot; width=&quot;100%&quot;&gt;
    &lt;source src=&quot;../../../common_videos/little-guy-digital-twin.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;p&gt;In the world beyond our desks at the CBA, &lt;em&gt;many new off-the-shelf scientific devices&lt;/em&gt; already serve python APIs, making the language &lt;em&gt;a productive site for systems integration;&lt;/em&gt; it’s the party that everyone is already attending. We want to be able to quickly bring our own ad-hoc systems to the same venue, and &lt;em&gt;share tools&lt;/em&gt; with device OEMs, whose costs are increased by developing these interfaces. We discussed all of these bottlenecks at some length in April 2024 at Nadya’s &lt;a href=&quot;https://depts.washington.edu/machines/scienceautomation/&quot;&gt;POSE Workshop&lt;/a&gt; at the University of Washington.&lt;/p&gt;

&lt;h3 id=&quot;replacing-serial-string-parsers-with-rpcs-and-proxies&quot;&gt;Replacing Serial String Parsers with RPCs and Proxies&lt;/h3&gt;

&lt;p&gt;One of the most painful, and most often re-engineered components of embedded projects is the serialport parser: a layer that connects to a device and writes / reads characters to the port. In many instances, these characters are ASCII string encodings, and developers craft interfaces on either end of the port (one in embedded, one in i.e. python, processing, or javascript) to develop whatever interconnect they need: for data collection, for sending commands to an embedded device, etc etc.&lt;/p&gt;

&lt;p&gt;This pattern is all over the place, but it’s cumbersome to build and maintain, it sucks space in Flash, and it’s hard to document. I complained about these &lt;a href=&quot;../../02/09/no-more-newline.html#towards-defeating-the-serial-string-parser&quot;&gt;over here as well&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Replacing this with &lt;em&gt;object oriented hardware&lt;/em&gt; is an ongoing effort in my research group at the CBA; see &lt;a href=&quot;https://cba.mit.edu/docs/theses/08.06.Moyer.pdf&quot;&gt;Ilan&lt;/a&gt; and &lt;a href=&quot;https://cba.mit.edu/docs/theses/16.08.Peek.pdf&quot;&gt;Nadya’s&lt;/a&gt; theses. What I’m writing about here is basically an extension of &lt;a href=&quot;https://github.com/modular-things/modular-things&quot;&gt;modular-things&lt;/a&gt;, which I started with Quentin Bolsee and Leo McElroy.&lt;/p&gt;

&lt;p&gt;The gist is that &lt;em&gt;we turn devices into software objects&lt;/em&gt; that I call &lt;em&gt;proxies;&lt;/em&gt; i.e. the stepper motor that controls the x-axis of a machine becomes a &lt;em&gt;proxy object&lt;/em&gt; called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stepper_x&lt;/code&gt; that has multiple &lt;em&gt;rpcs&lt;/em&gt; attached. In our program, and then we can write higher level codes with that, like:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# for example, with a nice low-level API to a motor, we can write 
# higher-level routines (like homing) in a language that is easier 
# to inspect and edit (like python)
# meaning less firmware updates, more flexibility, etc:
&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# our interface (next code snippet) 
&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SimpleStepper&lt;/span&gt; 

&lt;span class=&quot;c1&quot;&gt;# we instantiate each instance by passing in a device name 
# the dname is a circuit-specific string; this is basically an addressing step 
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stepper_x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SimpleStepper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;osap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;stepper_x&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;home_x_motor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# turn the motor on and head towards the switch 
&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stepper_x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_current_scale&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stepper_x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_velocity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;10.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# wait until the limit is hit, 
&lt;/span&gt;  &lt;span class=&quot;nf&quot;&gt;while &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stepper_x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_limit_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;asyncio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stepper_x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The pattern is useful mainly because &lt;strong&gt;embedded development is hard(er)&lt;/strong&gt; than development in high level languages like python. We still &lt;em&gt;need&lt;/em&gt; embedded codes to manage the really high-speed, low-latency actions required for i.e. motor control (for example, a stepper motor needs to ‘tick’ at nearly 100kHz), but it’s a pain to also assemble complex logic in embedded cpp. By bundling the device as a software object, we can interact with it at a high level without sweating the details. If we develop those device APIs well, we can re-use modular hardware across a number of projects.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; codes are a particularely useful tool in this regard: we can issue requests to multiple devices at once, and wait for them all to complete, before moving on to the next action for our system. Doing this in embedded code alone basically requires an RTOS (or some similar homebrew RTOS-like scheduler), but we get it for free in python.&lt;/p&gt;

&lt;h3 id=&quot;whats-in-a-proxy&quot;&gt;What’s in a Proxy&lt;/h3&gt;

&lt;p&gt;Writing proxies is straightforward: we build one &lt;em&gt;rpc_caller&lt;/em&gt; for each function, which gets &lt;em&gt;network addresses&lt;/em&gt; to the &lt;em&gt;rpc_implementation&lt;/em&gt; that we wrote previously in firmware. The &lt;em&gt;rpc_caller&lt;/em&gt; does all the work of serializing data, sending it to the right network coordinates, and deserializing return values. The proxy simply wraps all of these into a class that represents the device, and presents python-native function names and type hints.&lt;/p&gt;

&lt;p&gt;This might look a little clunky, but don’t worry because we don’t need to write it by hand.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;typing&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cast&lt;/span&gt; 
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;osap.osap&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OSAP&lt;/span&gt; 

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SimpleStepper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;osap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OSAP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_set_position_target_rpc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;osap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rpc_caller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;setPositionTarget&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_set_velocity_target_rpc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;osap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rpc_caller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;setVelocityTarget&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_set_position_rpc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;osap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rpc_caller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;setPosition&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_get_error_message_rpc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;osap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rpc_caller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;getErrorMessage&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_get_position_rpc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;osap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rpc_caller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;getPosition&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_get_velocity_rpc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;osap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rpc_caller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;getVelocity&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_set_current_scale_rpc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;osap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rpc_caller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;setCurrentScale&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_get_limit_state_rpc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;osap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rpc_caller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;getLimitState&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;callers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_set_position_target_rpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_set_velocity_target_rpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_set_position_rpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_get_error_message_rpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_get_position_rpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_get_velocity_rpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_set_current_scale_rpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_get_limit_state_rpc&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;caller&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;callers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;caller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;set_position_target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxVelocity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxAccel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_set_position_target_rpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxVelocity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxAccel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;set_velocity_target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;velocity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxAccel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_set_velocity_target_rpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;velocity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxAccel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;set_position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_set_position_rpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_error_message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_get_error_message_rpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_get_position_rpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_velocity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_get_velocity_rpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;set_current_scale&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_set_current_scale_rpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;duty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_limit_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_get_limit_state_rpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Hardware systems can be complex; when we have to remember what all of our RPC function names are while we’re programming, we can easily make mistakes. The interface class basically gives our IDE the ability to auto complete and auto correct for us, and the type-hints are (IMO) the best form of documentation we can ask for.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/auto-complete.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Proxies are invaluable tools when programming, for the simple utility of providing auto-complete and type hints in an IDE. We can also name each proxy to make sense of which device we are addressing, making semantic sense in big systems with many devices.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;the-metaprogramming&quot;&gt;The ‘Metaprogramming’&lt;/h3&gt;

&lt;p&gt;So, the proxyy is rad, but writing it is a time suck - especially if we’re looking at someone else’s firmware. But, all of the information we need to write the interface is already available via the &lt;a href=&quot;../../02/09/no-more-newline.html&quot;&gt;auto-rpc class&lt;/a&gt; - that is, the embedded device knows what its functions are called, what their type signatures are and so on, and we can query whenever they are connected to our network in order to learn these data.&lt;/p&gt;

&lt;p&gt;To turn those function signatures into callable proxy classes, I used &lt;a href=&quot;https://jinja.palletsprojects.com/en/3.1.x/&quot;&gt;jinja&lt;/a&gt; - a python package for string templating. It’s not dissimilar to &lt;a href=&quot;https://shopify.github.io/liquid/&quot;&gt;liquid,&lt;/a&gt; which Jekyll uses (and which this site is built with). String templating is a pattern I became familiar with thanks to &lt;a href=&quot;https://leomcelroy.com/&quot;&gt;Leo&lt;/a&gt;, who showed me &lt;a href=&quot;https://lit.dev/docs/v1/lit-html/introduction/&quot;&gt;lit-html&lt;/a&gt; a while ago and got me thinking about this notion of programs writing programs.&lt;/p&gt;

&lt;p&gt;This is what I mean by ‘metaprogramming’ - although I think this is like &lt;em&gt;soft-core metaprogramming&lt;/em&gt; - the really wild stuff in this domain is i.e. programs that optimize themselves at runtime, etc.&lt;/p&gt;

&lt;p&gt;Jinja can look kind of gnarly, since we are making statements (in jinja) about making statements (in python), here’s the code that writes modules:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;from typing import cast 
from osap.osap import OSAP 

class {{ class_name }}:
    def __init__(self, osap: OSAP, device_name: str):
        {% for func in signatures %}self.{{ func.pythonic_name }}_rpc = osap.rpc_caller(device_name, &quot;{{ func.name }}&quot;)
        {% endfor %}self.callers = [
            {% for func in signatures %}self.{{ func.pythonic_name }}_rpc{% if not loop.last %},
            {% endif %}{% endfor %}
        ]

    async def begin(self):
        for caller in self.callers:
            await caller.begin()

    {% for func in signatures %}async def {{ func.pythonic_name }}(self{% for arg in func.args %}, {{ arg.name }}: {{ arg.type_name }}{% endfor %}){% if func.return_type == &quot;void&quot; %}{% else %} -&amp;gt; {{ func.return_type }}{% endif %}:
        {% if func.return_type == &quot;void&quot; %}{% else %}result = {% endif %}await self.{{ func.pythonic_name }}_rpc.call({% for arg in func.args %}{{ arg.name }}{% if not loop.last %}, {% endif %}{% endfor %}){% if func.return_type == &quot;void&quot; %}{% else %}
        return cast({{ func.return_type }}, result){% endif %}

    {% endfor %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To use these, we connect all of our embedded devices, and then query them for their function signatures, device names, etc. We then feed that data into this structure, and write the output as a .py file.&lt;/p&gt;

&lt;h4 id=&quot;naming-and-case-conventions&quot;&gt;Naming and Case Conventions&lt;/h4&gt;

&lt;p&gt;At this point our system is authored in two languages - probably cpp (in the embedded device, or &lt;em&gt;rust&lt;/em&gt; if we get our act together and learn that new language that everyone is on about…), and in python, where our interface and the rest of our high-level system lives.&lt;/p&gt;

&lt;p&gt;As a minor annoyance, &lt;em&gt;style is language dependent:&lt;/em&gt; javaScript folks tend to prefer &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;camelCase&lt;/code&gt;, python’s &lt;a href=&quot;https://peps.python.org/pep-0008/&quot;&gt;pep8&lt;/a&gt; advocates for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClassNames&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;function_names&lt;/code&gt;, and cpp is something of a free-for-all.&lt;/p&gt;

&lt;p&gt;Mostly for Quentin’s sake, I added some utilities to translate betwixt them all - so if we use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;camelCase&lt;/code&gt; functions in the embedded device, they are served to us as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;snake_case&lt;/code&gt; functions in python.&lt;/p&gt;

&lt;p&gt;Actually, ChatGPT wrote these utilities - regex was always for robots anyways.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;to_camel_case&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Split the string by non-alphanumeric characters
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;words&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[^a-zA-Z0-9]&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Capitalize the first letter of each word and join them together
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;camel_case_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;capitalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;word&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;words&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;word&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;camel_case_name&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;to_snake_case&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Replace dashes with underscores
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Insert underscores before capital letters (considering initial caps and acronyms)
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;(?&amp;lt;!^)(?=[A-Z])&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Convert the whole string to lowercase
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;lower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;writing-a-boilerplate-mainpy&quot;&gt;Writing a Boilerplate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.py&lt;/code&gt;&lt;/h4&gt;

&lt;p&gt;Finally, the code generates an example of how one would deploy these modules - the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.py&lt;/code&gt; - this is of course optional, but I thought it would be nice to have as a quick-start tool. It initializes the proxy objects, connects the networking drivers, and wraps it all in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; nuts and bolts.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;asyncio&lt;/span&gt; 
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;traceback&lt;/span&gt; 

&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;osap.bootstrap.auto_usb_serial.auto_usb_serial&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AutoUSBPorts&lt;/span&gt; 
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;osap.osap&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OSAP&lt;/span&gt; 

&lt;span class=&quot;c1&quot;&gt;# import each interface / device proxy 
&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;proxies.stepper_cl_d51_proxy&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;StepperClD51Proxy&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# init networking service 
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;osap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OSAP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;auto_modules_main&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;asyncio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_event_loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create_task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;osap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; 

        &lt;span class=&quot;c1&quot;&gt;# connect via usb 
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;ports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AutoUSBPorts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ports&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;port&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;osap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;link&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# collect an image of the system 
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;system_map&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;osap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;netrunner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update_map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;system_map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; 

        &lt;span class=&quot;c1&quot;&gt;# instantiate each of our modules, 
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;stepper&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StepperClD51Proxy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;osap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;stepper_cl&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
        &lt;span class=&quot;c1&quot;&gt;# and set each up: this block additionally verifies that proxies 
&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;# are matched to implementation signatures discovered at runtime 
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stepper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# insert test code below... 
&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;#
&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;#
&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;#
&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;asyncio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CancelledError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bailing...&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;traceback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;format_exc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;finally&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# put shutdown codes here, 
&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;# i.e. powering down current sources, etc... 
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;__main__&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;asyncio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The resulting workflow is &lt;em&gt;one of the most productive tools I have written for myself during my PhD&lt;/em&gt; - we do:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;(1) tag some functions in your embedded device using a one-line macro &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BUILD_RPC(func, arg_names)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;(2) connect the device to a PC and run a small script, that:
    &lt;ul&gt;
      &lt;li&gt;(2.1) reads function signatures from the device&lt;/li&gt;
      &lt;li&gt;(2.2) writes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;device_proxy.py&lt;/code&gt; for each embedded device, and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.py&lt;/code&gt; file that initializes and connects to them all&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;(3) add whatever test codes to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.py&lt;/code&gt; file,&lt;/li&gt;
  &lt;li&gt;(4) run the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.py&lt;/code&gt; … modify it, move the interface to some other project, etc etc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bottom line for me is that I don’t have to hand-craft interfaces to embedded devices, even the ones that I am actively developing: when I add a new function to a device, or a new device to a system, I simply re-run these scripts and get back to development. Device names, function names and types, all stay consistent, and matched to hardware implementation. For a mild dyslexia sufferer / maker of small mistakes, this has vastly improved my development cycles.&lt;/p&gt;

&lt;h3 id=&quot;future-efforts-protocols-standards&quot;&gt;Future Efforts: Protocols, Standards&lt;/h3&gt;

&lt;p&gt;Like I mentioned, the motivation for these types of systems is to enable a &lt;em&gt;commons&lt;/em&gt; of open hardware. One of the current impediments to open hardware’s success is that, while many &lt;em&gt;contributions&lt;/em&gt; exist (devices), we still struggle to make &lt;em&gt;systems&lt;/em&gt; out of those building blocks.&lt;/p&gt;

&lt;p&gt;Probably the best integration system for open hardware is still Arduino’s ecosystem, but it is fraught with strange gotchas - i.e. while combining many sensors into one project, we might run out of I2C resources (address space, or time)… and as we add parts and libraries, we eventually eat up all of the RAM and CPU cycles available on our project’s main microcontroller.&lt;/p&gt;

&lt;p&gt;With strategies &lt;em&gt;like&lt;/em&gt; this one - where we combine many devices by networking them together - we can scale systems more rapidly. However, we want &lt;em&gt;to minimize the burden on device developers&lt;/em&gt; and &lt;em&gt;on systems integrators.&lt;/em&gt; If I am going to use a circuit module that someone else designed, I &lt;em&gt;should not&lt;/em&gt; have to understand how their firmware or their build system works in order to deploy it in my own application. I want to plug and play.&lt;/p&gt;

&lt;p&gt;The proposal here is that device &lt;em&gt;firmwares themselves can contain all of the information required to interface to the device,&lt;/em&gt; in a semantically meaningful way, using off-the-shelf programming paradigms. We want to be able to extract that information from &lt;em&gt;a single source of truth&lt;/em&gt; and use it &lt;em&gt;natively.&lt;/em&gt;&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2024-07_machine-osi-proto.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;To improve systems integration in open hardware (and beyond), we should make more explicit use of an OSI model for mechatronic devices. The strategies discussed in this post take place in the presentation layer (near the top); they concern how we represent data and devices to application developers. We also need to work on the rest of the stack.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Besides the networking step (where many people are already sticking USB, BLE or i.e. CAN interfaces on their devices), we still need to make sense of the packets sent between our applications and our devices - and from device to device. Doing so &lt;em&gt;across open hardware projects&lt;/em&gt; would be an effort in standards development: how to encode function signatures, type signatures, and serialize / deserialize data transfers. While I am independently working on each of these things, doing it on my own is not really a viable path towards such a standard - in the future we will all need to agree on their shapes, forms and styles.&lt;/p&gt;

&lt;h3 id=&quot;some-discussion&quot;&gt;Some Discussion&lt;/h3&gt;

&lt;p&gt;I am in no way proposing this as a be-all end-all solution, but I think it’s an interesting step, and has been hugely productive for me personally - so I wanted to discuss it.&lt;/p&gt;

&lt;p&gt;I think that interesting future work would address the issue of versioning and duplication: i.e. a system containing two of the same devices, but with firmwares of variable lineage. There is also still a burden of documentation: functions should probably contain docstrings as well, but storing those all in a microcontroller seems cumbersome - instead, they could perhaps return URIs to expanded docs served online, or some mixture of both.&lt;/p&gt;

&lt;p&gt;The approach also requires really good serialization / deserialization routines, and can lead to lots of manual type-checking, since embedded devices tend to have less flexible native types than i.e. python, so strange things might happen when we send i.e. python’s native &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int64&lt;/code&gt; into a function that declared a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uint8_t&lt;/code&gt; argument.&lt;/p&gt;

&lt;p&gt;This approach is also of the flavour where we basically “throw it all into python” and then hope that there’s enough performance there, but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.py&lt;/code&gt; is not a terribly performant language, so for the integration of high-speed systems, we probably need a better low-level tool that lets us avoid operating systems entirely. To that end, I have been developing &lt;em&gt;graph based&lt;/em&gt; tools that let us pipe data directly between embedded devices (but visualize and edit configs at a high level). If I survive this PhD there will be more on that later.&lt;/p&gt;

&lt;p&gt;In the end, what we want is &lt;strong&gt;for things to ‘just work’&lt;/strong&gt; - we want to build the complex bits, the high level controllers. No-one should be out here re-engineering motor controllers and motion systems just so that they can do science with a robot, etc etc. Systems integration is the stuff that plugs individual engineers into the world’s economies and hooks their contributions up to teams, firms, and &lt;strong&gt;society, man&lt;/strong&gt; … we should spend more time making it beautiful, functional and fun.&lt;/p&gt;

&lt;hr /&gt;
</description>
        <pubDate>Mon, 29 Apr 2024 00:00:00 -0400</pubDate>
        <link>http://ekswhyzee.com/2024/04/29/automatic-rpc-interfaces.html</link>
        <guid isPermaLink="true">http://ekswhyzee.com/2024/04/29/automatic-rpc-interfaces.html</guid>
        
        <category>systems-assembly</category>
        
        
      </item>
    
      <item>
        <title>MUDLink: a Modular UART Duplex Link</title>
        <description>&lt;h3&gt;&lt;a href=&quot;https://github.com/jakeread/mudlink&quot; target=&quot;_blank&quot;&gt;MUDLink on GitHub / Arduino&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;I built a new library for Arduino that lets us promote any Serial port (UART) into a competent link / transport layer with guaranteed delivery of framed packets.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2024-03-09_loss-labels.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;MUDLink adds packet framing, delivery guarantees, and flow control to just about any UART Port. It also records stats on the link’s performance, to enable a litle bit of &lt;em&gt;feedback systems assembly.&lt;/em&gt;&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have wanted to release something like this for a while since just about every microcontroller has at least one UART port, and they are well understood in the OSHW community - making them a good candidate for link layers that could help to enable &lt;a href=&quot;../../../2023/10/16/searching-for-oshw.html&quot; target=&quot;_blank&quot;&gt;a commons of interoperable hardware&lt;/a&gt;. In order to get from UART to Reliable Link Layer, though, we need to add a few things:&lt;/p&gt;

&lt;h3 id=&quot;packet-framing&quot;&gt;Packet Framing&lt;/h3&gt;

&lt;p&gt;UARTs send bytes only, and have no native packet delineation as is the case in i.e. I2C, CANBus etc: this makes it difficult to frame and decode efficient bytecodes of any real complexity.&lt;/p&gt;

&lt;h3 id=&quot;bit-and-byte-errors&quot;&gt;Bit and Byte Errors&lt;/h3&gt;

&lt;p&gt;UARTs are not fault tolerant: each microcontroller simply sends bytes at whatever configured baudrate, and recieves at the same. If bytes are lost or bits are flipped, but programmers don’t anticipate or catch these errors, things can go wrong. This means that folks are left either with unreliable systems (throwing intermittent errors, the worst kind!), or are left re-building fault tolerance when they might rather be finishing the interesting parts of their project.&lt;/p&gt;

&lt;h3 id=&quot;flow-control-and-feedback&quot;&gt;Flow Control and Feedback&lt;/h3&gt;

&lt;p&gt;Since UARTs are mostly used with only TX and RX lines (no CTS/RTS etc), they are not normally flow controlled. This can be troublesome in applications where i.e. one participant can write significantly faster than the other can read (leading to lost bytes).&lt;/p&gt;

&lt;p&gt;It can also be difficult to tell what an appropriate baudrate is: lower rates are typically less prone to errors, but that stability comes at an obvious performance loss. In some cases, we pick what seems like a stable baudrate only to change our environment slightly, and find that it is actually quite error prone… for example we might turn on a high-power motor (injecting a lot of noise) and have our comms break down.&lt;/p&gt;

&lt;p&gt;These kinds of phenomena typically means that (as is the case in much systems assembly), we end up using lower performance parameters than we might be able to, i.e. opting for baudrates that &lt;em&gt;never&lt;/em&gt; fail, even though they may be up to one quarter the speed fo baudrates that fail only &lt;em&gt;very occasionally.&lt;/em&gt;&lt;/p&gt;

&lt;h1 id=&quot;enter-mudlink-a-useful-uart-link&quot;&gt;Enter MUDLink, a Useful UART Link&lt;/h1&gt;

&lt;p&gt;So, the MUDLink is a software bolt-on that effectively &lt;em&gt;promotes&lt;/em&gt; any Arduino-exposed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Serial&lt;/code&gt; object from a PHY (layer one of the &lt;a href=&quot;https://en.wikipedia.org/wiki/OSI_model&quot; target=&quot;_blank&quot;&gt;OSI model&lt;/a&gt;) into a simple Transport layer (although it roughly eschews Layer 3: since it only sends datagrams between two devices, there is no addressing or routing… it also misses &lt;em&gt;segmentation&lt;/em&gt; which is meant to live in Layer 4).&lt;/p&gt;

&lt;p&gt;Longer discussion on any resemblance to the OSI model is mostly useless anyways, as many have stated (most recently, &lt;a href=&quot;https://fab.cba.mit.edu/classes/865.24/topics/computing/comms/&quot; target=&quot;_blank&quot;&gt;Nathan made this point in his Machine Class lecture&lt;/a&gt;) it is a good common understanding or theory of how we might delayer networks, but is rarely followed carefully in practice.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;In simple terms, MUDLink lets you quickly establish a packetized, reliable and inspectable connection between two microcontrollers.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;packet-framing-with-cobs&quot;&gt;Packet Framing with COBS&lt;/h2&gt;

&lt;p&gt;Firstly, we need to make streams of bytes into chunked packets. This is like networking 101: if we don’t know where any given byte goes w/r/t any other byte, we can’t reliably do much (unless each of our messages is just one byte long).&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/cobs_encoding_with_example.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;From the &lt;a href=&quot;https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing&quot; target=&quot;_blank&quot;&gt;Wiki Page on COBS&lt;/a&gt;: COBS transforms an arbitrary string of bytes in the range [0,255] into bytes in the range [1,255]. Having eliminated all zero bytes from the data, a zero byte can now be used to unambiguously mark the end of the transformed data.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing&quot; target=&quot;_blank&quot;&gt;COBS&lt;/a&gt; is &lt;em&gt;Consistent Overhead Byte Stuffing&lt;/em&gt; and is already in most embedded developers’ toolbox. It has the excellent property that it requires only two extra bytes of transmitted data for any packet smaller than 254 bytes (and only one more byte per chunk of 255 thereafter) - and requires those bytes consistently, meaning that we can have more deterministic systems.&lt;/p&gt;

&lt;p&gt;COBS is also simple and fast: about 64 lines of c code (with comments). MUDLink deploys the wikipedia page’s implementation directly (unfortunately I wouldn’t be able to tell you who wrote that, though - thanks to them!).&lt;/p&gt;

&lt;h2 id=&quot;error-catching-with-crc16-ccitt&quot;&gt;Error Catching with CRC16-CCITT&lt;/h2&gt;

&lt;p&gt;Once we have rx’d a sequence of bytes, we need to check that they are valid, since things may have gone awry while they were en route (flipped bits due to noise or missed bytes due to slow interrupts being the most common culprits).&lt;/p&gt;

&lt;p&gt;For this, MUDLink uses a &lt;a href=&quot;https://en.wikipedia.org/wiki/Cyclic_redundancy_check&quot; target=&quot;_blank&quot;&gt;Cyclic Redundancy Check&lt;/a&gt;, particularely the CRC16-CCITT polynomial. For a really well done explainer on CRCs, check out &lt;a href=&quot;https://www.youtube.com/watch?v=izG7qT0EpBw&quot; target=&quot;_blank&quot;&gt;Ben Eater’s video on the topic&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A CRC is like a really fancy checksum: we use a carefully selected function that takes our packet (as a big number) and generates a smaller number that &lt;em&gt;is guaranteed to change&lt;/em&gt; if some changes to the packet occur - for example the CRC16-CCITT sis &lt;em&gt;guaranteed&lt;/em&gt; to change if there is a change to a single bit within a packet of 8000 bytes - so, pretty good. It’s &lt;a href=&quot;https://eklitzke.org/crcs-vs-hash-functions&quot; target=&quot;_blank&quot;&gt;kindof like a hashing function&lt;/a&gt; but different in important ways. Real big brain math stuff going on in there. I would like to point out that I understand the topic only glancingly, and you should refer to others for more detailed and correct explainers (i.e. Ben Eater above).&lt;/p&gt;

&lt;p&gt;CRCs are additionally designed to be quickly implemented: in theory they are basically big long divisions but in practice they are calculated using bitwise XOR operations and bit-shifts.&lt;/p&gt;

&lt;h2 id=&quot;flow-control-and-delivery-guarantees&quot;&gt;Flow Control and Delivery Guarantees&lt;/h2&gt;

&lt;p&gt;So! We can catch packets and make sure they are legit. Now we want the real magic: we want to know from our end (1) that our packet was successfully received and (2) that it has been consumed by our partner, so that they are clear and free to recieve the next message. We accomplish these goals together with an ack-and-retransmit scheme not dissimilar from TCP itself, which is again explained &lt;a href=&quot;https://www.youtube.com/watch?v=4IMc3CaMhyY&quot; target=&quot;_blank&quot;&gt;really well by Ben Eater&lt;/a&gt;.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/mudlink-statemachine.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The MUDLink code is essentially four tiny state machines that signal one another, which I’ve tried to represent here. The left side is application facing states and actions: the programmer can tell if the port is open (not in a failed state), and if it is clear to send or clear to read a packet. On the right side we have, basically, hardware buffers. These send data out into the world and are responsible for catching it. Either side can generate and consume events from the others: for example when we finish RX’ing a packet that passes the CRC, it sets the user-facing CTR state. When they finally read the packet, the CTR state transitions to EMPTY, and an ACK is generated and written into the TX buffer. Keeping a clear separation of these layers normally helps us separate ISR’s from user code, but in this case it just helps clarify the system operation.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;TCP is a lot more complex than this (MUDLink is only 350 lines of code as of this post), but they use similar principles:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We keep a rolling sequence number that increments when we generate a new message.
    &lt;ul&gt;
      &lt;li&gt;We always transmit our most recent segment number, as well as the sequence number of the packet we have most recently read from our partner (as an ack).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;When we transmit a message, we wait until we have recevied an ack from our partner before we transmit any new data.
    &lt;ul&gt;
      &lt;li&gt;We may, however, retransmit the same message if our timeout for that runs out.&lt;/li&gt;
      &lt;li&gt;After each timeout, we increase the timeout interval exponentially.&lt;/li&gt;
      &lt;li&gt;When we have retried the packet a number of times, we enter a failure state.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;When we recieve a new packet, we present the data to the application by setting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Clear To Read&lt;/code&gt; (CTR) high. We generate an ack for our partner &lt;em&gt;only&lt;/em&gt; when the user has consumed that data.&lt;/li&gt;
  &lt;li&gt;When we recieve a re-transmitted packet (that we have already successfully caught and read) we generate a new ACK, because the sender likely missed the previous one (hence their attempt to retransmit).&lt;/li&gt;
  &lt;li&gt;Finally, we run a keepalive timer: if we haven’t transmitted anything in some interval, we ping our partner to make sure they know we’re still open. Similarely, if we don’t hear anything for a while, we consider the port to be closed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;statistics-tracking&quot;&gt;Statistics Tracking&lt;/h2&gt;

&lt;p&gt;So, I had mentioned that it’s often difficult to measure what’s going on in our networks, which makes it difficult to design systems. It turns out this is a bona-fide problem in industry, and especially in safety critical systems: great engineering bookeeping efforts are expended making sure that there will always be enough network bandwidth available in, for example, a 747’s avionics system.&lt;/p&gt;

&lt;p&gt;In OSHW, which currently builds more ad-hoc projects, we rarely take on that level of bookkeeping, so we are left in the relative dark about how much bandwidth we are using in our networks, how fast they are really running, etc.&lt;/p&gt;

&lt;p&gt;MUDLink adds a little to ameliorate this in that it tracks some stats, we have these (self describing) available:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MUDLStats&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rxSuccessCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rxFailureCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;txSuccessCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 
  &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;txFailureCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 
  &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;txTotalRetries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// the longest timeout issued since startup &lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;outgoingTimeoutLengthHighWaterMark&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 
  &lt;span class=&quot;c1&quot;&gt;// maybe the most important, average message trip time, &lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;averageTotalTransmitTime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// comparable to the wire time (bits / baudrate) for most msgs &lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;averageWireTime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// and the num of tx retries normally required &lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;averageRetryCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MUDLStats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These are available to the programmer, to assess their choice of baudrate, quality of underlying link, etc.&lt;/p&gt;

&lt;h2 id=&quot;api-and-protocol&quot;&gt;API and Protocol&lt;/h2&gt;

&lt;p&gt;MUDLink ingests &lt;em&gt;any&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Serial&lt;/code&gt; instance within the Arduino ecosystem, meaning it can be deployed in just about any Arduino project. We instantiate it using a a template like this:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 2nd argument is the baudrate &lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;MUDL_Link&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;decltype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Serial1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mudl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Serial1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2000000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It has a pretty simple API, we can check if the link is clear to send or clear to read, and we can send and read packets.&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appRx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appTx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; 

&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// MUDLink runs on an event loop, &lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;mudl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// check-then-read,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mudl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clearToRead&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()){&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mudl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;appRx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  
  &lt;span class=&quot;c1&quot;&gt;// presuming we have stuffd appTx with a message: &lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mudl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clearToSend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()){&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mudl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;appTx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msgLen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// and we can get those stats, &lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;MUDLStats&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stats&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mudl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getStats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The message spec is pretty simple as well, beneath our COBS encoded packets we have this block:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Bytes 0 … N-5&lt;/th&gt;
      &lt;th&gt;Byte Len-4&lt;/th&gt;
      &lt;th&gt;Byte Len-3&lt;/th&gt;
      &lt;th&gt;Byte Len-2&lt;/th&gt;
      &lt;th&gt;Byte Len-1&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;… Payload&lt;/td&gt;
      &lt;td&gt;ACKD_SEQ&lt;/td&gt;
      &lt;td&gt;TXD_SEQ&lt;/td&gt;
      &lt;td&gt;CRC:1&lt;/td&gt;
      &lt;td&gt;CRC:2&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;So, pretty small overhead, and the payload is simply missing when we send ack-only or keepalive messages.&lt;/p&gt;

&lt;h2 id=&quot;system-limits-future-work-and-what-i-learned-while-i-did-it&quot;&gt;System Limits, Future Work, and What I Learned While I Did It&lt;/h2&gt;

&lt;p&gt;MUDL has a pretty clear limit to performance, which is that it has an overhead floor of 2x: we will always use roughly half (or less) of the available bandwidth in a duplex link, because we are always waiting for our partner’s ack before we can send another. So, on a scope (with TX and RX lines tapped) we see this kind of pattern:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/2024-03-09_back-and-forth.png&quot; alt=&quot;scope&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is ameliorated in grown-up protocols with &lt;em&gt;transmission windows&lt;/em&gt; - each announces how long their receive buffer is, and then we can transmit as many messages as are in the buffer before receiving an ack. TCP does this, and hopefully MUDL will in the future… I have written that protocol out before, but it was a little shaky. Then again, it didn’t have CRC at that time, so I should bring my new learning back to that project.&lt;/p&gt;

&lt;p&gt;MUDL is also limited by the underlying performance of whichever block of Arduino Core code is running the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Serial&lt;/code&gt; implementation. In this case I was using &lt;a href=&quot;https://github.com/earlephilhower/arduino-pico/tree/master&quot; target=&quot;_blank&quot;&gt;Earle the III’d RP2040 Core&lt;/a&gt;, which is wonderful (thanks Earle!), but which doesn’t seem to service the actual transmit interrupt fast enough. So, if we send 1MBaud of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0b01010101&lt;/code&gt; we see this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/2024-03-08_1mbaud-byte-spacing.png&quot; alt=&quot;1meg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But if we config at 3MBaud:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/2024-03-08_3mbaud-byte-spacing.png&quot; alt=&quot;3meg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So - what’s up? Well, we are transmitting at a speedy &lt;em&gt;baudrate,&lt;/em&gt; but every time the RP2040’s TX FIFO dries up, it takes too long to refill (hence the inter-byte gap). Since a 3MBaud Byte (10 bits for UART framing) is only 3.3 microseconds long (at 200MHz, 660 clock cycles), we &lt;em&gt;can&lt;/em&gt; run out of ISR time if we don’t write tight enough code. In practice, this means that while the RP2040 can run a UART up to 3MBaud, we only ever effectively see about 1MBaud of real performance.&lt;/p&gt;

&lt;p&gt;This kind of thing is why the pros use DMA, etc, and why I am thinking I aught to return to that practice. With DMA, our app writes a big TX buffer, and then each byte is ferried into the UART without the processor’s intervention at all - normally triggering an interrupt when the transmit is totally complete.&lt;/p&gt;

&lt;p&gt;High-speed hardo embedded code trades off with portability though, of course. For now, I like the flexible, code-only transport / link layer as it is, but I can look forward to writing custom implementations for some of my favourite microcontrollers. One of the nice things of this scheme is that we can have a sloppy implementation still pair up with a high-speed implementation, and everything should still work.&lt;/p&gt;

&lt;h2 id=&quot;odds-and-ends&quot;&gt;Odds and Ends&lt;/h2&gt;

&lt;p&gt;One other thing I learned during this cycle is that the Arduino I2C library &lt;em&gt;is blocking&lt;/em&gt; - so when we use these OLED displays (see the first picture here), which require a big chunk of data in the screenbuffer, we can get big blocking calls in our Arduino code. In the pictured demo, the display write is over 20ms.&lt;/p&gt;

&lt;p&gt;This could also be ameliorated by some hardo embedded code, but it’s actually been a good foil: MUDLink works &lt;em&gt;despite&lt;/em&gt; the blocking call that hampers performance servicing the transmit routine &lt;em&gt;and&lt;/em&gt; also causes packets to be entirely missed (when the blocking call happens during a packet receive and we can’t run our loop, we miss bytes!).&lt;/p&gt;

&lt;h2 id=&quot;what-about-busses-&quot;&gt;What about Busses !&lt;/h2&gt;

&lt;p&gt;One last note is that I’ve come up with a pinout that suits this protocol (over RS485) as well as a bus topology. I would love in future times to build the protocol to service point-to-point &lt;em&gt;as well as&lt;/em&gt; bus topologies, perhaps dynamically switching between the two.&lt;/p&gt;

&lt;p&gt;Last trick to add would be dynamic baudrate scaling, to make these ports truly plug and play across devices and without any config.&lt;/p&gt;
</description>
        <pubDate>Sat, 09 Mar 2024 00:00:00 -0500</pubDate>
        <link>http://ekswhyzee.com/2024/03/09/mudlink.html</link>
        <guid isPermaLink="true">http://ekswhyzee.com/2024/03/09/mudlink.html</guid>
        
        
      </item>
    
      <item>
        <title>Delete Your &apos;\n&apos; Delimited Firmware Interface!</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;TL:DR, I wrote an RPC protocol and compiler tool for Arduino that lets us quickly turn hardware modules into software modules. I think it’s a good candidate design pattern for replacing newline-delimited string-parsing protocols.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;iframe width=&quot;700&quot; height=&quot;394&quot; src=&quot;https://www.youtube.com/embed/SvYSH5FY3sg?si=X8AEAWjUCPG8StFq&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;remote-function-calls&quot;&gt;Remote Function Calls&lt;/h2&gt;

&lt;p&gt;aka &lt;a href=&quot;https://en.wikipedia.org/wiki/Remote_procedure_call&quot;&gt;RPCs&lt;/a&gt; - are a pretty common design pattern in distributed systems. The basic idea is simple: we use a network connection to ask another computer to run a function for us. In the case of modular, embedded devices (like those in &lt;a href=&quot;https://modular-things.com&quot;&gt;Modular Things&lt;/a&gt;), it’s a great way to centralize systems assembly: each of our modules exposes an API of RPCs and we string those function calls together into new programs - building systems that are collections of modules becomes as simple as writing programs that coordinate multiple software objects&lt;sup id=&quot;fnref:gestalt&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:gestalt&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;h2 id=&quot;discovery&quot;&gt;Discovery&lt;/h2&gt;

&lt;p&gt;One of the main troubles with RPCs is that we need to know &lt;em&gt;what functions&lt;/em&gt; we have available on our network. In many systems, we simply have to know this information before we start programming: what devices are attached, where are they located, and what functions do they contain … and then, what are the names, types, and argument lists for those functions. This kind of &lt;em&gt;“feedforward systems assembly”&lt;/em&gt; can cause all kinds of headaches when we go to run our program, because if we are just a little bit misaligned between what our code expects and what is configured in reality, we run into crashes and failures.&lt;/p&gt;

&lt;p&gt;So, the RPC code I’m presenting here additionally exposes a discovery layer - just a small addition to the protocol that lets other network participants ask for function signatures (names, types…). This means that we can do some measure of &lt;em&gt;“feedback systems assembly”&lt;/em&gt; - we can inspect our remotes and check our local codes against their reported functions at runtime.&lt;/p&gt;

&lt;h4 id=&quot;discovery--lower-documentation-requirements&quot;&gt;Discovery == Lower Documentation Requirements&lt;/h4&gt;

&lt;p&gt;Discovery is also a great way to handle documentation: rather than write datasheets about what functions our devices expose, we can rely on API discovery to handle that… basically, I am proposing to put the documentation in the device itself. I suspect that this could help a great deal with version control as well, since each firmware build can report exactly what it contains (whereas it is common for docs and codes to be out of sync with one another).&lt;/p&gt;

&lt;h2 id=&quot;auto-rollup&quot;&gt;Auto Rollup&lt;/h2&gt;

&lt;p&gt;Older versions of Modular Things also basically deployed RPCs, but the serialization was left up to the module author. As I &lt;a href=&quot;../../../2023/10/16/searching-for-oshw.html&quot;&gt;have discussed&lt;/a&gt;, making it easy for authors to include new modules into a commons is important, so the major effort I’ve made with this code is to make the function-to-rpc rollup really straightforward (for module developers).&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/auto-rpc-patterns-old-vs-new-patterns.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Previously, authors of new modules needed to write matching .js codes for their firmwares. While that pattern is still present in Modular Things (and is useful for more involved device design), it is liable to cause errors when firmwares and higher-level descriptions don’t match up. The new pattern automates this process, and means that we build systems with less sources of truth.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;prototype-protocols-serialization-and-deserialization&quot;&gt;Prototype Protocols, Serialization and Deserialization&lt;/h2&gt;

&lt;p&gt;Any RPC code relies on some protocol - for this project I threw together a semi adhoc protocol since I think a protocol is better designed once we have some experience with a system… and I know that my buddy &lt;a href=&quot;https://fabacademy.org/2020/labs/ulb/students/quentin-bolsee/about/&quot;&gt;Quentin&lt;/a&gt; is working on a more robust protocol design for a similar system.&lt;/p&gt;

&lt;p&gt;At the core of this protocol we have a small set of type-keys for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;float, int, boolean&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string&lt;/code&gt; - then we use those to furnish calls to get function signatures and names, and to serialize arguments (when calling functions) and reply types (on the way back up).&lt;/p&gt;

&lt;p&gt;At the moment, this all works within &lt;a href=&quot;http://osap.tools/&quot;&gt;OSAP&lt;/a&gt;, which has transport protocol built in; I simply instantiate a new OSAP port for each RPC class, and they are each individually addressable in the network… I’ll write more about OSAP hopefully in the next few weeks.&lt;/p&gt;

&lt;h4 id=&quot;type-keys-and-data-serialization&quot;&gt;Type Keys and Data Serialization&lt;/h4&gt;

&lt;table&gt;
  &lt;th colspan=&quot;5&quot;&gt;Type Keys&lt;/th&gt;
  &lt;tr&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;void&lt;/td&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;int&lt;/td&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;bool&lt;/td&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;float&lt;/td&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;string&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;0&lt;/td&gt;
    &lt;td&gt;1&lt;/td&gt;
    &lt;td&gt;2&lt;/td&gt;
    &lt;td&gt;3&lt;/td&gt;
    &lt;td&gt;4&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;Serializations of these are straightforwards, i.e.:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;serialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;inline&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serialize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;inline&lt;/span&gt; 
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serialize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;deserialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;inline&lt;/span&gt; 
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deserialize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rptr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)];&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rptr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rptr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rptr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;inline&lt;/span&gt; 
&lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deserialize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rptr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;protocol-message-types&quot;&gt;Protocol Message Types&lt;/h4&gt;

&lt;table&gt;
  &lt;th colspan=&quot;2&quot;&gt;Function Signature Request&lt;/th&gt;
  &lt;tr&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;B0&lt;/td&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;B1&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;PRPC_KEY_SIGREQ&lt;/td&gt;
    &lt;td&gt;MSG ID&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;

&lt;table&gt;
  &lt;th colspan=&quot;7&quot;&gt;Function Signature Response&lt;/th&gt;
  &lt;tr&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;B0&lt;/td&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;B1&lt;/td&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;B2&lt;/td&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;B3&lt;/td&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;...&lt;/td&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;...&lt;/td&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;...&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;PRPC_KEY_SIGRES&lt;/td&gt;
    &lt;td&gt;MSG ID&lt;/td&gt;
    &lt;td&gt;Return Type Key&lt;/td&gt;
    &lt;td&gt;Number of Args&lt;/td&gt;
    &lt;td&gt;... Arg Type Keys&lt;/td&gt;
    &lt;td&gt;Function Name&lt;/td&gt;
    &lt;td&gt;... Arg Names&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;

&lt;table&gt;
  &lt;th colspan=&quot;3&quot;&gt;Function Call&lt;/th&gt;
  &lt;tr&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;B0&lt;/td&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;B1&lt;/td&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;B2 ...&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;PRPC_KEY_FUNCCALL&lt;/td&gt;
    &lt;td&gt;MSG ID&lt;/td&gt;
    &lt;td&gt;... Arg Data&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;

&lt;table&gt;
  &lt;th colspan=&quot;3&quot;&gt;Function Return&lt;/th&gt;
  &lt;tr&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;B0&lt;/td&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;B1&lt;/td&gt;
    &lt;td style=&quot;border-top: 1px solid;&quot;&gt;B2 ...&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;PRPC_KEY_FUNCRETURN&lt;/td&gt;
    &lt;td&gt;MSG ID&lt;/td&gt;
    &lt;td&gt;Return Data&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;

&lt;h3 id=&quot;spec--protocol-based-systems-allow-interoperability-without-open-source&quot;&gt;Spec / Protocol Based Systems allow Interoperability without Open Source&lt;/h3&gt;

&lt;p&gt;This is probably worth a lengthier discussion, but I think that we often reach for Open Source when all we want is Open Interoperability. This may be especially true in industrial settings, where closed protocols prevent interoperation in order to lock customers into particular ecosystems.&lt;/p&gt;

&lt;p&gt;This is also a current site of tension in Open Hardware: folks are struggling to understand how to make money while exercising their craft as hardware engineers. Personally, I think the overarching goal is maybe more aligned with a theme of “Collective Intelligence,” of which Open Source is just one flavour… What we want is to be able to put our heads together, and build big complex systems using contributions made by many individuals. Interoperation achieves that, and allows individual contributors a clearer path to profitability - and we shouldn’t be afraid of that.&lt;/p&gt;

&lt;h2 id=&quot;towards-defeating-the-serial-string-parser&quot;&gt;Towards Defeating the Serial String Parser&lt;/h2&gt;

&lt;p&gt;All highfalutin discussions aside, there is one nasty design pattern that I think &lt;em&gt;this&lt;/em&gt; design pattern can help to vanquish, which is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt; newline-delimited string parser. By which I mean any (mostly Arduino-based) firmware that contains some snippet like this:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;received_bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;readBytesUntil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;&apos;\n&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;INPUT_BUFFER_LENGTH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;received_bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;input_buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;received_bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sc&quot;&gt;&apos;\0&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/* .... */&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// basically just a function call to &quot;move_axes:&quot; &lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;move_rel &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mr &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)){&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//relative move&lt;/span&gt;
      &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;preceding_space&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;displacement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_motors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;EACH_MOTOR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//read three integers and store in steps_remaining&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;preceding_space&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;preceding_space&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;preceding_space&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Error: command is mr &amp;lt;int&amp;gt; &amp;lt;int&amp;gt; &amp;lt;int&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;displacement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;preceding_space&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toInt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;move_axes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;displacement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;done.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/* .... */&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// basic getters &lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ramp_time?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)){&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ramp time &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ramp_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/* .... */&lt;/span&gt;

  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;This is a snippet from the &lt;a href=&quot;https://gitlab.com/bath_open_instrumentation_group/sangaboard/-/blob/master/arduino_code/sangaboard/sangaboard.ino?ref_type=heads&quot;&gt;sangaboard&lt;/a&gt;; the firwmare that runs the &lt;a href=&quot;https://openflexure.org/&quot;&gt;open flexure&lt;/a&gt;, an exemplary piece of Open Hardware. I don’t include it here to pick on them (this is a really well-written parser), but just to point out that these string-based protocols (that are prevalent in OH), are often re-written project-to-project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;String Parsers are often shaky, ad-hoc protocols, that suck time and FLASH from our codes, and make difficult to maintain, read, and extend embedded systems.So, if you’ve a hardware project in need of a comms layer, perhaps give &lt;a href=&quot;http://modular-things.com/&quot;&gt;modular-things&lt;/a&gt; a shot. At the moment, this works just in the browser in Modular Things, but next weeks’ update will deploy a design pattern that makes it easy to drop these into native JS, TS and Python projects.&lt;/p&gt;

&lt;h1 id=&quot;limits-and-future-work&quot;&gt;Limits and Future Work&lt;/h1&gt;

&lt;h3 id=&quot;compiler-caveats&quot;&gt;Compiler Caveats&lt;/h3&gt;

&lt;p&gt;This works right now where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c++17&lt;/code&gt; or newer is available; of my favourite microcontrollers (RP2040, SAMD51, the Teensy, and the SAMD21) (all deployed on various arduino cores), that leaves just the RP2040. So, that’s a problem.&lt;/p&gt;

&lt;p&gt;AFAIK, this is mostly due to my use of &lt;a href=&quot;https://en.cppreference.com/w/cpp/utility/apply&quot;&gt;std::apply&lt;/a&gt; and also some &lt;a href=&quot;https://en.cppreference.com/w/cpp/language/fold&quot;&gt;fold expressions&lt;/a&gt;, both of which are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c++17&lt;/code&gt; onwards. However, the core utilities that I use (&lt;a href=&quot;https://en.cppreference.com/w/cpp/utility/tuple&quot;&gt;std::tuple&lt;/a&gt;, &lt;a href=&quot;https://en.cppreference.com/w/cpp/language/decltype&quot;&gt;decltype&lt;/a&gt; and &lt;a href=&quot;https://en.cppreference.com/w/cpp/language/parameter_pack&quot;&gt;parameter packs&lt;/a&gt;) are all available from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c++11&lt;/code&gt; - so I should be able to hack this back to thence, using &lt;a href=&quot;https://kubasejdak.com/techniques-of-variadic-templates&quot;&gt;recursive techniques&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is also the trouble of templating as-well against the full mixture of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void-void, void-args, ret-void, ret-args&lt;/code&gt; - for those I currently use &lt;a href=&quot;https://en.cppreference.com/w/cpp/language/if&quot;&gt;if constexpr&lt;/a&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c++17&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Anyways, that’s all a TODO. AFAIK, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c++14&lt;/code&gt; is available across the SAMD core (which makes up very-many arduinos), and the Teensy core, so bumping down to that level would at least satisfy most of my constraints. IDK what other popular MCUs use, i.e. the ESP32 series would be awesome to include. AVR is probably another question.&lt;/p&gt;

&lt;h3 id=&quot;stronger-type-serialization--deserialization&quot;&gt;Stronger Type Serialization / Deserialization&lt;/h3&gt;

&lt;p&gt;Alongside the templating tricks, the core of this protocol is a prototype of a set of serialization (and deserialization!) functions to turn all of our typed values into bytes-on-the-wire and back. These are valuable across protocols, so I would like to have a core set that work well across platforms. Requirements would be like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;hot&lt;/li&gt;
  &lt;li&gt;fast&lt;/li&gt;
  &lt;li&gt;flexible&lt;/li&gt;
  &lt;li&gt;self reporting&lt;/li&gt;
  &lt;li&gt;small code size&lt;/li&gt;
  &lt;li&gt;highly portable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are many systems out there that do this type of work, including:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://cbor.io/&quot;&gt;cbor&lt;/a&gt;: Concise Binary Object Representation&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://msgpack.org/index.html&quot;&gt;msgpack&lt;/a&gt;: “It’s like JSON but Fast and Small”&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://capnproto.org/&quot;&gt;capn’proto&lt;/a&gt;: ex ProtoBuf engineer&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://protobuf.dev/&quot;&gt;protobuf&lt;/a&gt;: the serdes tool from Google&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AFAIK, capn’proto and protobuf don’t support discoverability, and the other two have been on my homework list for a while. Anyways, we need good serdes for this kind of stuff.&lt;/p&gt;

&lt;h3 id=&quot;embedded-device-to-embedded-device&quot;&gt;Embedded Device to Embedded Device&lt;/h3&gt;

&lt;p&gt;The current pattern works well to centralize embedded modules in one higher level code, but we can’t chat between devices. It would be awesome to be able to configure an “RPC Caller” as well as the implementer, in embedded codes. This would be an odd way to configure systems though, because the whole “view” of the system would be distributed across multiple firmware source codes…&lt;/p&gt;

&lt;p&gt;In my &lt;a href=&quot;https://cba.mit.edu/docs/theses/20.02.read.pdf&quot;&gt;Masters&lt;/a&gt; degree I made a totally bonkers system called &lt;a href=&quot;https://mtm.cba.mit.edu/2019/2019-01_squidworks/&quot;&gt;squidworks&lt;/a&gt; where embedded devices talked directly to one another and systems were represented in a graph view.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2019-11_dex-vision-controller.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Co-ordinating and designing large distributed systems can be a major PITA, especially when it comes to orchestrating direct communication between multiple embedded devices. In my own prior work (above), &lt;a href=&quot;https://mtm.cba.mit.edu/2019/2019-01_squidworks/&quot;&gt;squidworks&lt;/a&gt; used a graph representation to do so. RPCs on the other hand are typically used distributed systems where most control is centralized within one computer and ‘other’ devices are simple service workers, as is the case with &lt;a href=&quot;https://modular-things.com/&quot;&gt;Modular Things&lt;/a&gt;. However, it may still be useful to be able to implement RPC Callers in firmwares (the current build only builds RPC Implementers).&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Graphs make for an excellent overview tool (to grep the whole state of some distributed computer program), but are sometimes clunky programming tools (or perhaps they just expose the spaghetti that is fundamentally inevitable). In any case, some “heavy mixture” of code- and graph-based programming seems like a likely candidate for sense-making of modular embedded worlds, and I would like to return to that question.&lt;/p&gt;

&lt;h3 id=&quot;py-to-js-and-vise-versa&quot;&gt;.py to .js, and vise-versa.&lt;/h3&gt;

&lt;p&gt;Along the same lines, I simply haven’t written all of the code with-which a .py code would announce itself to a .js code, etc etc etc.&lt;/p&gt;

&lt;p&gt;Everything in time, perhaps.&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:gestalt&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;This is an idea with a long legacy at the CBA, notably in &lt;a href=&quot;https://cba.mit.edu/docs/theses/08.06.Moyer.pdf&quot;&gt;Ilan Moyer&lt;/a&gt; and &lt;a href=&quot;https://cba.mit.edu/docs/theses/16.08.Peek.pdf&quot;&gt;Nadya Peek’s&lt;/a&gt; theses. &lt;a href=&quot;#fnref:gestalt&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Fri, 09 Feb 2024 00:00:00 -0500</pubDate>
        <link>http://ekswhyzee.com/2024/02/09/no-more-newline.html</link>
        <guid isPermaLink="true">http://ekswhyzee.com/2024/02/09/no-more-newline.html</guid>
        
        <category>open-hardware</category>
        
        <category>systems-design</category>
        
        
      </item>
    
      <item>
        <title>Some Link Layer Speed Testing</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;A lotta charts and graphs in this one, friends!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href=&quot;https://gitlab.cba.mit.edu/jakeread/low-level-link-speed-tests&quot;&gt;Low Level Link Speed Tests on the CBA GitLab&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ve been developing an FDM 3D printer that can produce oodles of data. Our collaborators at NIST are interested in this because it will allow them to build better models of the FDM process, and we are interested in that because it will allow us to better optimize FDM slicing, machine control, etc - and I probably don’t have to tell you that consuming realtime data is the first step towards doing realtime closed-loop control.&lt;/p&gt;

&lt;p&gt;For a systems redesign, I laid out a table of data sources and rates, and found that (with an amount of packet framing and overhead), &lt;strong&gt;I’ll be producing around 5Mbit/s total from the machine.&lt;/strong&gt; Perhaps more on this in a later post.&lt;/p&gt;

&lt;p&gt;That’s a 5MHz clock, each cycle transmitting one bit - it seems a measly number of bits to fit inside of each second (given our acclimatization with “Gigabit” ethernet and the like), but (as we will see) getting this amount of data off-chip and into a useful high-level context is not something that I found easy.&lt;/p&gt;

&lt;h2 id=&quot;usb-serial-rp2040&quot;&gt;USB Serial: RP2040&lt;/h2&gt;

&lt;p&gt;So, this post is also something of a challenge: many may cast their gaze here and think “this lad is an absolute Hack, I just plugged my 12Mbit/s (specified) embedded USB device into my PC and wham, it worked!” - well, of course I tried that as well and here’s the results: histograms record inter-packet spacing (in microseconds) for some packet length, and overlay that with equivalent realized bandwidth in Mbit/sec (bits per second, not bytes).&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2023-12-27_usbserial-02-cobsless.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;pyserial -&amp;gt; USB -&amp;gt; RP2040 Earle Core SDK USB&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Overclocking the RP2040 to 240MHz does help, organizing the distribution into a tighter spike at the fast end:&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2023-12-27_usbserial-03-cobsless-240mhz.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;pyserial -&amp;gt; USB -&amp;gt; RP2040 @240MHz Earle Core SDK USB&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;usb-serial-samd21&quot;&gt;USB Serial: SAMD21&lt;/h2&gt;

&lt;p&gt;And the SAMD21, at 48MHz clock, does much worse (notice the stretched axis):&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2023-12-27_usbserial-04-cobsless-d21.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;pyserial -&amp;gt; USB -&amp;gt; SAMD21 XIAO&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;All in, these results were dissapointing, so I elected to circle back with the Teensy 4.0, a 600MHz bad-boy that advertises a 480Mbit/sec USB implementation.&lt;/p&gt;

&lt;h2 id=&quot;usb-serial-teensy-40&quot;&gt;USB Serial: Teensy 4.0&lt;/h2&gt;

&lt;p&gt;OK, these were all upsetting and I tested Ethernet and the PI next, but then I circled back to try the Teensy, since I think that Paul is an absolute wizard and I know that his Teensy core is spectacularly well engineered - I was actually not dissapointed:&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2024-01-04_teensy-usb-32.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Pyserial -&amp;gt; USB -&amp;gt; Teensy 4.0, with 32 Byte Packets (note the x-axis change)&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2024-01-04_teensy-usb-128.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Pyserial -&amp;gt; USB -&amp;gt; Teensy 4.0, with 128 Byte Packets&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is wonderful - and a relief honestly, I wish I had tried it earlier. We are up to, easily, our spec of 5 Mbit/sec, and then well past it. Those deltas are wild: tens of microseconds.&lt;/p&gt;

&lt;h2 id=&quot;ethernet&quot;&gt;Ethernet&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;python -&amp;gt; Ethernet -&amp;gt; Switch -&amp;gt; Ethernet -&amp;gt; WizNet -&amp;gt; SPI -&amp;gt; RP2040&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Eyeing that sweet sweet 100/10 Ethernet link, I collected the WizNet W5500-EVB-PICO, (an RP2040 Pico with the Wiz strapped on) and connected it to my PC.&lt;/p&gt;

&lt;p&gt;A few notes on this setup; (1) the Wiz connects to the RP2040 via SPI, which (as we will see later) means we are basically SPI bottlenecked, in addition to the Ethernet’s own packet time, etc. (2) we connect &lt;em&gt;via a switch&lt;/em&gt; to our local network, i.e. our PC. So, the setup goes from the RP2040, over SPI to the Wiz, over Ethernet to &lt;em&gt;a switch,&lt;/em&gt; back over Ethernet to our PC.&lt;/p&gt;

&lt;p&gt;Setting this up also required some static IP configuration: in Windows, I configured the Ethernet interface to have a static IP on the local network, and configured the RP2040 to aim packets at the same (it also got a static IP).&lt;/p&gt;

&lt;p&gt;So, the results:&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2023-12-28_eth-oneway-64.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;RP2040 -&amp;gt; WizNet -&amp;gt; Ethernet -&amp;gt; Switch -&amp;gt; Ethernet -&amp;gt; PC; one-way, Python ingesting.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2023-12-28_eth-ping-64.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;RP2040 -&amp;gt; WizNet -&amp;gt; Ethernet -&amp;gt; Switch -&amp;gt; Ethernet -&amp;gt; PC; round-trip (packets down and back up), Python, 64 Bytes / Packet&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ethernet does better than most USB here, and we can strap mega-sized packets through it. While packet size increases delay, total bandwidth goes up:&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2023-12-28_eth-ping-256.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;RP2040 -&amp;gt; WizNet -&amp;gt; Ethernet -&amp;gt; Switch -&amp;gt; Ethernet -&amp;gt; PC; round-trip (packets down and back up), Python, 256 Bytes / Packet&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2023-12-28_eth-ping-768.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;RP2040 -&amp;gt; WizNet -&amp;gt; Ethernet -&amp;gt; Switch -&amp;gt; Ethernet -&amp;gt; PC; round-trip (packets down and back up), Python, 768 Bytes / Packet&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We still have a real bottleneck around 2Mbit/sec, much less than we would surmize on a “100Mbit” link, so what’s up? Well, if we look at the RP2040-to-WizNet SPI link on the scope, we get a sense:&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2023-12-28_ethernet-wiz-spi-01.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;CH1: Transaction Flag, CH2: Chip Select and CH3: CLK - we can see that the single Ethernet transaction is made of multiple SPI transactions between the RP2040 and the Wiz…&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2023-12-28_ethernet-wiz-spi-02.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;CH1: Transaction Flag, CH2: Chip Select and CH3: CLK - … and that within each transaction, the actual data transfer (on CH3, where the CLK is active) are pretty well spaced out!&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I.E. even though we are running 12.5MHz SPI to our 100Mbit Ethernet device, whatever in-between is happening (the Arduino Ethernet library, on top of abstracted SPI interfaces, etc etc) is introducing up to 5x overhead, so we are left with tops 2Mbit/sec of real data rate.&lt;/p&gt;

&lt;h2 id=&quot;raspberry-pi-and-spi&quot;&gt;Raspberry Pi, and SPI&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;RPi 4 -&amp;gt; ? / SPI -&amp;gt; RP2040 Hardware SPI SDK&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Given that the WIZ communicates to the uc via SPI anyways, I thought I would hook it up directly. Being able to do this is basically what SBC’s are for anyways (their CPU cores having direct access to some GPIO, just like an MCU).&lt;/p&gt;

&lt;p&gt;This has the caveat that we are now in-context on this SBC (rather than, i.e, our beefy-ass laptop with a hella GPU), but maybe we need this middle ground. So, the setup is: RP2040 via SPI directly to the RPi 4.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2023-12-28_eth-setup.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The RPI/SPI Setup…&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;About the TTL levels here, traces look solid up to around 20Mbit, where I suspect we would genuinely loose some bits in the PHY:&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2023-12-29_spi-waveq-020MBit.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;At 20MHz, we start to see some shaky TTL…&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In any case, this tests pretty well. In these cases, we are “transacting” - SPI out begets SPI in - and counting bytes that are out-of-sequence (missed). IIRC, I did all of these tests at 240MHz on the RP2040, to help with ISR time.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2024-01-04_spi-histo-10Mb.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;RP2040 @ 240MHz -&amp;gt; SPI -&amp;gt; RPi 4 Python w/ spidev: 10Mbit/sec&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;10Mbit underlying rate gets us into 5Mbit of realized bandwidth, which at this point was some great news for me. It falls off of a cliff after this though (check the missed-bytes ratio in the plot titles), probably the MCU running out of time to service ISRs:&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2024-01-04_spi-histo-11Mb.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;RP2040 @ 240MHz -&amp;gt; SPI -&amp;gt; RPi 4 Python w/ spidev: 11Mbit/sec&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2024-01-04_spi-histo-15Mb.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;RP2040 @ 240MHz -&amp;gt; SPI -&amp;gt; RPi 4 Python w/ spidev: 15Mbit/sec&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We also come up against a bandwidth limit around 5Mbit. There’s an observable delay between transmissions on the line, around 10us, which is the RPi chunking between python loops. It would sometimes stretch to 50us or so…&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2024-01-04_spi-traces-10Mb.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;10Mbit/sec, CH: Chip Select, CH2: DO&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But, all-in, SPI-direct is a promising approach, or the best performing so far: it shows big bandwidth &lt;em&gt;and&lt;/em&gt; small loop times, I mean, that’s &lt;strong&gt;100us&lt;/strong&gt; period for cyclic transactions on these plots, which is killer, that’s potential for 10KHz of action (if we can hook everything else up…) between a Python process on a decent-sized CPU and some embedded worlds.&lt;/p&gt;

&lt;h2 id=&quot;whats-the-point-&quot;&gt;What’s the Point ?&lt;/h2&gt;

&lt;h3 id=&quot;latency-and-throughput-both-matter&quot;&gt;Latency and Throughput &lt;em&gt;Both&lt;/em&gt; Matter&lt;/h3&gt;

&lt;p&gt;When we’re building machines, robotics, or whatever mechatronic devices, we are always interested in how fast we can get data into- and out-of the world; see also our &lt;a href=&quot;https://pub.pages.cba.mit.edu/ring/&quot;&gt;Ring Test Page&lt;/a&gt; where we track how fast we can get between an MCU’s computer-part and its physical-part (the pins). In most systems, we are interested in pure throughput, but with machines are interested in both: data &lt;em&gt;freshness&lt;/em&gt; as well as total data per second. Given that most “off-the-shelf” links report just their &lt;em&gt;maximum throughput&lt;/em&gt; and not latencies, or actually-realized throughput, I thought it wise to test.&lt;/p&gt;

&lt;h3 id=&quot;many-links-are-not-as-advertised&quot;&gt;Many Links are Not As Advertised&lt;/h3&gt;

&lt;p&gt;I started doing &lt;em&gt;these&lt;/em&gt; tests because I wanted to sanity check the USB link, which I assumed to be easily within range of 5Mbit/sec, given that USB 2.0 operates at 12Mbit/sec, and I was dead wrong. So, that’s a lesson learned: &lt;strong&gt;measure your systems.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is because we normally talk about a link’s “underlying speed” when we discuss; it’s easy to measure how long it takes for one bit to be transmitted. However, with all of the rest of the framing (software on either end, protocol, etc), measurements get messier.&lt;/p&gt;

&lt;p&gt;Even the Teensy’s USB Serial, which is &lt;em&gt;wonderful&lt;/em&gt; compared to the rest, gives us 20Mbit/s on top of a 480Mbit/s link (others use, AFAIK, the 12Mbit/s USB spec).&lt;/p&gt;

&lt;p&gt;For a common-day example of this, observe that most SD cards advertise above 80MB/sec, but we typically see ~ 20MB/sec in practice.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2024-01-04_real-sd-rate.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Even on (what I assume to be) a highly optimized system: an SD card (80Mbit/sec advertised rate) plugged directly into my XPS 15’s SD card reader, we only realize, normally, 1/4 of that rate.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;simple-and-device-specific&quot;&gt;Simple and Device-Specific&lt;/h3&gt;

&lt;p&gt;I also had a bias of mine confirmed (so, beware the validity of this statement); simple, low-level systems, custom-built, often out-perform fancy, layers-of-abstraction codes. In embedded, where we all &lt;em&gt;want&lt;/em&gt; to have portable code, but basically &lt;em&gt;can’t really,&lt;/em&gt; this feels especially true. It’s also not that difficult to (for example) spin up a UART or a SPI peripheral from scratch, it’s just a rare expertise.&lt;/p&gt;

&lt;p&gt;This also means that many of the results above could be improved with other folks’ better code, though I’ve started with what I can tell to be the best in the open-source business. There is a larger point to be made here about open source ecosystems vs. code portability vs. performance; the gist of which being that device-specific engineering is typically the route to performance, but the open hardware community is focused (at the moment) on trying to make code portability work in embedded systems (see Arduino and Micro/Circuit-Python, for example). IMO, this is not the way, but I will discuss that at more length at some other time.&lt;/p&gt;

&lt;h3 id=&quot;interrupt-handling-is-often-the-bottleneck&quot;&gt;Interrupt Handling is Often the Bottleneck&lt;/h3&gt;

&lt;p&gt;This is something I’ve seen again and again: we aren’t really bandwidth limited when we use simple embedded links: UART and SPI, for example. The drivers can drive it, the rx’ers can catch it, and the noise is “not so bad”. Under heavy loads, though, we become limited by the time it takes for our MCU to service the associated interrupts.&lt;/p&gt;

&lt;p&gt;This has another hairy connotation for open hardware: it means that, “normally,” we need to know how hard the MCU is going to be working before we can appropriately engineer the networking links. If the MCU is terribly busy handling SPI comms to a sensor, then we come along and ask it to please deliver those data over a network link at some absurd rate, we may very well lock it up, miss some readings, or miss some packets. So! More trouble for layer-agnosticity.&lt;/p&gt;

&lt;h2 id=&quot;what-next-&quot;&gt;What Next ?&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;(1)&lt;/strong&gt; These codes are not optimal (implementations) and might be worth trying again with more focus/time, esp. the RP2040 PIO, which seemed so promising to me. Also the WizNet SPI library could probably be done up all-fancy like, to delete some of that blank time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(2)&lt;/strong&gt; We can/should have more-better high level protocols (CRC, flow control, perf measuring) for these “generic” low-level interfaces (SPI, UART) that would make it easy to bootstrap new MCUs, and promote their peripherals to proper link layers. A goal of that development should be that they can auto-test themselves routinely, so that we can do this kind of work in-situ.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(3)&lt;/strong&gt; There are many more to test: Teensy’s Ethernet implementation probably &lt;em&gt;rippeth,&lt;/em&gt; the SAMD51 (another favourite MCU of mine), the ever-popular ESP32’s Bluetooth and WiFi links, etc etc.&lt;/p&gt;

&lt;p&gt;That’s all for this one, TTFN.&lt;/p&gt;
</description>
        <pubDate>Thu, 04 Jan 2024 00:00:00 -0500</pubDate>
        <link>http://ekswhyzee.com/2024/01/04/link-layer-speed-tests.html</link>
        <guid isPermaLink="true">http://ekswhyzee.com/2024/01/04/link-layer-speed-tests.html</guid>
        
        <category>systems-design</category>
        
        <category>link-layers</category>
        
        <category>embedded</category>
        
        
      </item>
    
      <item>
        <title>Searching for the Commons of OSHW</title>
        <description>&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/building-stuff-haystack.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;&lt;strong&gt;Open Hardware has the potential to change the way we make and maintain our world - but we need to learn how to put our heads together to do it.&lt;/strong&gt;&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;This post contains ideas that I’ve been bothering my friends about in conversation over the course of many years, and whose inputs (and prior works!) have been hugely valuable, so, shoutouts to &lt;a href=&quot;http://depts.washington.edu/machines/&quot;&gt;Nadya&lt;/a&gt;, &lt;a href=&quot;http://samcalisch.com/&quot;&gt;Sam&lt;/a&gt;, &lt;a href=&quot;https://scholar.google.com/citations?user=uXMmC_kAAAAJ&amp;amp;hl=en&quot;&gt;Anna WB&lt;/a&gt;, &lt;a href=&quot;https://fabacademy.org/2020/labs/ulb/students/quentin-bolsee/about/&quot;&gt;Quentin&lt;/a&gt;, &lt;a href=&quot;https://leomcelroy.com/&quot;&gt;Leo&lt;/a&gt;, &lt;a href=&quot;https://web.mit.edu/imoyer/www/index.html&quot;&gt;Ilan&lt;/a&gt;, and many more - I am lucky to have found myself in this community of passionate folks!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;the-question-of-open-hardwares-sustainability&quot;&gt;The Question of Open Hardware’s Sustainability&lt;/h3&gt;

&lt;p&gt;Open Hardware is facing a small crisis; we have long standing incumbents who &lt;a href=&quot;https://blog.prusa3d.com/the-state-of-open-source-in-3d-printing-in-2023_76659/&quot;&gt;feel ripped off&lt;/a&gt; and a simmering of &lt;a href=&quot;https://www.youtube.com/watch?v=68FkIwCc_eo&quot;&gt;similar sentiments&lt;/a&gt; in the community. Many companies that were “open by default” are reconsidering or switching to hybrid and closed-source models as they discover that cost-competing with global manufacturers is difficult to do whilst also maintaining R&amp;amp;D, Documentation and Support roles. Indeed, many of these companies find themselves working to support users who have purchased their designs from some third party vendor.&lt;/p&gt;

&lt;p&gt;At the same time, it was always the point that Open Hardware meant &lt;em&gt;Open&lt;/em&gt; Hardware: unprotected designs that could be replicated elsewhere. Until now, OSH businesses were relying mostly on a community ethos to survive; hoping that buyers would choose to purchase from the firms who published the original designs&lt;sup id=&quot;fnref:open-purchase&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:open-purchase&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. This was based on a shared understanding that everyone benefits from open designs and interoperability, and in the FDM world that ethos worked for a long while, ushering in a “next generation” of machines that are radically more reliable, easy to use, and faster than the prior art.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;div style=&quot;display: flex; margin-bottom: 0.8rem;&quot;&gt;
  &lt;div style=&quot;padding-right: 3px&quot;&gt;
    &lt;img src=&quot;images/printer-prusa.jpg&quot; /&gt;
  &lt;/div&gt;
  &lt;div style=&quot;padding-left: 3px;&quot;&gt;
    &lt;img src=&quot;images/printer-bambu.jpg&quot; /&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Many of the hard-won insights uncovered by the Open Source FDM community have been recently deployed in the hugely successful (but closed-source) Bambu P1P, causing some to question the viability of open development practices in a field where incremental costs are non-zero. In this write-up, I want to show how we can have our cake (shared knowledge, community of practice, and Open Source) and eat it too (making profits, making complex systems, and making them easy to use and understand).&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This level of refinement is the kind of engineering that normally takes place within “real” firms and behind closed doors and with centralized coordination and control of development. With the newest FDM generation, we saw that Open Hardware can do what Open Software has been doing for a while: replace centralized design of complex systems with distributed groups of individuals working asynchronously with no real coordination other than a common goal. The result of the FDM community’s work is a profound drop in cost (towards $200) and radical improvement to the quality of machines that just about anyone can buy and use in their own home to build designs of their making… it is worth a moment to appreciate what a great outcome that is.&lt;/p&gt;

&lt;p&gt;But in some way, the FDM community has been done in by its own success: the dominant design patterns and technologies that have emerged from their hard work have become ubiquitous enough that the average FDM user doesn’t recognize where they came from, and so they simply purchase the best and the cheapest incantation of these designs that they can get their hands on. Manufacturers are responding, and it seems that as soon as one participant breaks the game-theoritic trance and develops open designs in closed source products, the ethos threatens to dissolve.&lt;/p&gt;

&lt;h3 id=&quot;on-the-flip&quot;&gt;On the Flip&lt;/h3&gt;

&lt;p&gt;At the same time, Open Hardware is showing huge promise: during COVID-19 we saw &lt;a href=&quot;https://dl.acm.org/doi/10.1145/3495262&quot;&gt;collective action&lt;/a&gt; where individuals turned their FDM machines into PPE factories. Digital Fabrication is continuing to make its way into STEM education, and a new generation of young engineers is growing up in a land filled with Arduinos, Breadboards, Fritzing and FreeCAD. Every year it seems like one or two new Open Hardware Machine Companies emerge; this year we got an excellent &lt;a href=&quot;https://www.opulo.io/&quot;&gt;pick and place&lt;/a&gt; machine, 2024 may be the year we finally get &lt;a href=&quot;https://github.com/MillenniumMachines/Milo-v1.5&quot;&gt;‘the prusa of milling’&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Organizations like OSHWA and Hackaday / Crowdsupply and Hackster are expanding, and scientists and academics are increasingly turning to OSH in order to increase accessibility of experimental hardware. &lt;a href=&quot;https://openhardware.science/&quot;&gt;GOSH&lt;/a&gt; has emerged around Open Hardware in Science and organizations like &lt;a href=&quot;https://gitlab.com/bath_open_instrumentation_group&quot;&gt;BOING&lt;/a&gt; are building open instrumentation, an effort that the &lt;a href=&quot;http://cba.mit.edu/&quot;&gt;CBA&lt;/a&gt; (where I work) is also &lt;a href=&quot;https://cba.mit.edu/events/22.08.OM/&quot;&gt;contributing to&lt;/a&gt;… we imagine a future where scientists who publish data also ubiquitously publish designs, algorithms, and controllers for their experimental hardware - and where that hardware is easily sourced, assembled, and commissioned so that others can re-create and expand the frontier.&lt;/p&gt;

&lt;h3 id=&quot;whats-the-vision-and-whats-the-point&quot;&gt;What’s the Vision, and What’s the Point?&lt;/h3&gt;

&lt;p&gt;What’s at stake is, in my opinion, the most important quality of the whole human project; &lt;em&gt;our ability to put our heads together and make meaningful stuff&lt;/em&gt; - to build off of one anothers’ intellectual outputs as we pursue our personal and common goals.&lt;/p&gt;

&lt;p&gt;For a long time, the dominant narrative around the design and production of complex things (engineering artefacts, let’s say) was that it took place within firms, was mostly proprietary, and that the outcomes were protected by patents. This has never been completely true&lt;sup id=&quot;fnref:nuvolari&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:nuvolari&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, but Open Source Software blew it entirely out of the water as it came of age in the late 90’s / early aughts. Open Software completely changed the way we write code, and to have similar impacts in the realm of &lt;em&gt;real stuff&lt;/em&gt; would be meaningful in ways I probably do not have to explain at length: with hardware &lt;em&gt;we’re talking about healthcare, housing, agriculture, transportation, energy, science…&lt;/em&gt; not just apps.&lt;/p&gt;

&lt;p&gt;What made Open Software so powerful was a reorganization that started as recently as 2008; it transitioned from a focus on monolithic, end-user facing products (like LibreOffice and Linux) to a &lt;em&gt;commons&lt;/em&gt; of &lt;em&gt;functional modules&lt;/em&gt; that are rapidly re-assembled into application specific tools. I think that Open Hardware is on the brink of a similar transition, and that it can lead us to a world where we can have our cake (interoperability, shared community of practice, open designs) and eat it too (making profits, making complex systems, making them easily understood and replicatable, and making sustainable hardware companies).&lt;/p&gt;

&lt;h3 id=&quot;whats-in-this-post&quot;&gt;What’s in This Post&lt;/h3&gt;

&lt;p&gt;In this post, I want to start exploring this idea of modularity in Open Source, looking in particular at well-developed theory from the sphere of Open Source Software. The goal is to learn what we might apply from that domain into ours, in Hardware. This breaks down into a few sections:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;(1)&lt;/strong&gt; Some motivating thoughts, exemplary failures, and historical lessons from interoperable computer hardware.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;(2)&lt;/strong&gt; Insights from economists and practitioners of the (hugely successful) Open Source Software ecosystems: how do they work and how do they emerge?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;(3)&lt;/strong&gt; Understanding key technical properties of the commons.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This review should elucidate the core theory around &lt;em&gt;Commons Based Peer Production,&lt;/em&gt; and ground it in a discussion of hardware modules. Over the next year, I want to follow this up with two or three more posts; one that provides an overview of the “state of the commons” in open hardware, a second that proposes device-to-device interoperability as a way forwards towards an even more productive commons, and perhaps a third that goes into more detail on the foundational economics and imagines business models that could thrive in the commons.&lt;/p&gt;

&lt;h1 id=&quot;1-from-monoliths-to-modules&quot;&gt;1. From Monoliths to Modules&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;“Hardware is Hard”&lt;/em&gt; is an adage that gets thrown around a lot in the Open Hardware community, but I don’t think it needs to be this way. The utterance normally comes from folks who are accustomed to writing software and then find themselves in the wee hours of the morning trying to reconcile their circuit schematic with a sensor’s datasheet and someone else’s pinout diagram. In software development, we don’t have to sweat these details: libraries and lower layers are successfully encapsulated into (mostly) well-designed APIs, and we simply invoke them with function calls in our favourite language. The result is that we get to use tens or hundreds of other developers’ expertise in our own projects at a low economic and intellectual cost: we don’t pay for them, nor do we have to invest hours of learning in order to understand how to use them.&lt;/p&gt;

&lt;!-- Imagine if a programmer had to understand how every sub-routine in their code works, even in glancing detail: no-one would get anything done. --&gt;

&lt;p&gt;With a little bit of effort, I think that Open Hardware can become this easy. We are reaching a critical mass of expertise and energy, the cost of embedded computing continues to drop, and more and more valuable systems components and design patterns are emerging. &lt;em&gt;There is a chance to transition from monolithic to modular contributions, enabling open hardware to cross a threshold into the production of complex, systems-scale artefacts.&lt;/em&gt; This would have ramifications wherever we design and build hardware; from toy-building to energy infrastructure and medical equiment. Success could mean that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Individuals and firms would be able to build new mechatronic equipment as easily as they write new software today (in weeks, not years).&lt;/li&gt;
  &lt;li&gt;More individuals would be engaged in the active making and maintenance of our world: more meaningful jobs and more ‘hands on deck’ to face meaningful challenges.&lt;/li&gt;
  &lt;li&gt;Society as a whole would be better equipped to face radical changes: economic upheavals, pandemics, existential (warming) threats - by producing real, useful artefacts and infrastructures at a lower engineering cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When we take a cold-hearted look at the state of open hardware today, these three outcomes can seem fairly pie-in-the-sky. On the other hand, when we think of how many engineering-hours are saved with Open Source in software systems, and how many large firms have adopted OSS as a core practice, it is hard to deny that open access to a commons of hardware designs and design patterns would represent a vastly more efficient economy than the one we see in action today, where re-engineering is the norm.&lt;/p&gt;

&lt;p&gt;I would also call upon the doubtful reader to consider that as recently as 2004, &lt;a href=&quot;https://en.wikipedia.org/wiki/Butler_Lampson&quot;&gt;Butler Lampson&lt;/a&gt; reported that “a general library of software components […] is unlikely to work”&lt;sup id=&quot;fnref:lampson&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:lampson&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; - just a few years later, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node.js&lt;/code&gt; emerged - which currently plays host to over &lt;em&gt;1.3 million&lt;/em&gt; such modular components used by millions more developers. And if Open Hardware seems ‘too dinky’ to face ‘real engineering challenges’ in the future, I would also point out that the worlds’ best cryptographic algorithms are Open Source (more eyes see more potential pitfalls), and that the whole Personal Computing revolution was founded by small groups of hobbyists working “for fun” on systems that basically just &lt;a href=&quot;https://en.wikipedia.org/wiki/Altair_8800&quot;&gt;blinked&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;11-scoping-the-discussion&quot;&gt;1.1 Scoping the Discussion&lt;/h2&gt;

&lt;p&gt;All of this is background to work that I do mostly in the area of machine building on &lt;a href=&quot;https://cba.mit.edu/docs/papers/23.10.MAXL-Motion.pdf&quot;&gt;modular motion controllers,&lt;/a&gt; &lt;a href=&quot;https://cba.mit.edu/docs/papers/23.04.Modular-Things.pdf&quot;&gt;feedback programming models,&lt;/a&gt; and &lt;a href=&quot;https://cba.mit.edu/docs/theses/20.02.read.pdf&quot;&gt;networked systems assembly tools&lt;/a&gt;. When we talk about &lt;em&gt;hardware&lt;/em&gt; it’s very easy to go off the rails, since the whole world is made of it. In this discussion, I want to stay focused on &lt;em&gt;functional blocks&lt;/em&gt; of hardware, and since the world is filled with (and mostly operated by) computing these days, I’m mostly taking about &lt;em&gt;mechatronic modules.&lt;/em&gt; These are components that we can imagine re-using in multiple contexts like motor controllers, encoders, computer visions solutions, end-effectors, etc; the things that are often the most valuable parts of a project, but are the most difficult to copy from someone else’s contribution. The Mechaduino is a great example:&lt;/p&gt;

&lt;h2 id=&quot;12-mechaduino-in-search-of-a-systems-integration-tool&quot;&gt;1.2 Mechaduino: in Search of a Systems Integration Tool&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;when do we get to the point where, if I plug something in, it just works? 
  - Joe Jacobson (Lab Lunch, 2021)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Getting to this level of modular interoperability probably needs a new kind of &lt;em&gt;systems integration&lt;/em&gt; (SI) tool, something like an &lt;em&gt;NPM for Mechatronics.&lt;/em&gt; To explain what I mean by this, I want to remind readers of the &lt;a href=&quot;https://tropical-labs.com/mechaduino/&quot;&gt;Mechaduino&lt;/a&gt;. This is a closed-loop driver that represents a clear improvement in positioning accuracy, speed and reliability for one of the most common components in open machine design (the NEMA 17 Stepper Motor).&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/mechaduino-fab.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The &lt;a target=&quot;new&quot; href=&quot;https://tropical-labs.com/mechaduino/&quot;&gt;mechaduino&lt;/a&gt; was an early OSH module that represents a significant improvement (open to closed loop) on motor control. However, since its launch in 2016, no open motion controllers can integrate with it appropriately.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a great example of a hardware module: it packages the developers’ expertise in a niche area (closed-loop control of a stepper motor) into a functional block for an application that many other developers are interested in (motion systems). But the Mechaduino was missing a larger architecture to live in; users were left to customize and re-compile the device’s firmware in order to integrate it into their application, and it didn’t serve a clear interface with which it could be “plugged in” to other systems. Although it was released in 2016, the mechaduino has not yet made its way into any other substantial open hardware project, nor has its core design pattern (although many clones exist, even I have &lt;a href=&quot;https://gitlab.cba.mit.edu/jakeread/ucbus-stepper&quot;&gt;designed a few!&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This is perhaps because closed-loop drivers are not natively supported by open motion controllers, or because (more broadly) we don’t have a good framework for projects that deploy on multiple microcontrollers (i.e. a motion planner talking to multiple modular motor controllers simultaneously). It may also be that the mechaduino is simply more expensive than its alternatives (at $65 vs ~ $20), despite my own anecdotal evidence of seeing many machine builders over the years spend above $250 per motor for proprietary closed-loop solutions. It seems that the mechaduino was a great module without a modular architecture to live in.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/expressdep-2d.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Rendered from the &lt;a href=&quot;https://npm.anvaka.com/#/view/2d/express&quot;&gt;NPM graph viewer&lt;/a&gt;, the NPM package &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;express&lt;/code&gt; - a server middleware code - easily bundles tens of others` modular contributions into a new contribution.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If we look instead at Open Software, interoperability is the name of the game. Contributions are made of other contributions, and are all integrated seamlessly within some shared language: JavaScript or Python, for example. I think this is worth a second thought: &lt;em&gt;programming languages are systems integration tools.&lt;/em&gt; Any function written in Python can be called natively in anyone else’s Python script. This is obvious, but it is also Open Software’s super power: between high-level languages (with &lt;a href=&quot;https://peps.python.org/pep-0008/&quot;&gt;standardized&lt;/a&gt; &lt;a href=&quot;https://docs.python.org/3/library/typing.html&quot;&gt;self-documentation&lt;/a&gt; practices to boot), and the operating system (which abstracts hardware heterogeneity away from systems assemblers), anything written anywhere can be deployed anywhere else. Open Hardware will have to work harder for interoperability.&lt;/p&gt;

&lt;h2 id=&quot;13-for-reference-ibms-open-hardware-mistake-was-a-silicon-valley-catalyst&quot;&gt;1.3 For Reference: IBM’s Open Hardware Mistake Was a Silicon Valley Catalyst&lt;/h2&gt;

&lt;p&gt;Interoperability is also the basis of &lt;em&gt;computer hardware&lt;/em&gt; at large - servers, PCs, etc are all made from components that work together based on well defined standards. It’s an economic pattern that we take largely for granted, but which has a worthwhile origin story.&lt;/p&gt;

&lt;p&gt;In the 1970’s, IBM’s flagship mainframe was the &lt;strong&gt;Systems/360&lt;/strong&gt; - and it represented the first computing system to intentionally &lt;em&gt;generalize&lt;/em&gt; across markets: the 360 was used in business and scientific applications alike. It was also one of the most complex systems IBM had ever developed. A modular systems architecture helped them take on both challenges.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;div style=&quot;display: flex; margin-bottom: 0.8rem;&quot;&gt;
  &lt;div style=&quot;padding-right: 3px&quot;&gt;
    &lt;img src=&quot;images/baldwin-clark-ibm-sys-02.jpg&quot; /&gt;
  &lt;/div&gt;
  &lt;div style=&quot;padding-left: 3px;&quot;&gt;
    &lt;img src=&quot;images/baldwin-clark-ibm-sys-01.jpg&quot; /&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Images from &lt;a target=&quot;new&quot; href=&quot;https://www.ibm.com/ibm/history/ibm100/us/en/icons/system360/impacts/&quot;&gt;IBM&lt;/a&gt;. System/360 was an early mainframe computer that paved the way for modularity throughout the IT sector.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Internally, modularity was a wonderful engineering-labour-coordination trick: it meant that when someone in the user-input team made a change, it wasn’t coupled to any design choices being made in the memory-control team&lt;sup id=&quot;fnref:dsms&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:dsms&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; (for example). Today, this kind of separation of interests is commonplace in systems design, but the 360 was emerging in a world where prior art was mostly monolithic, and where the first nuggets of information hiding theory and abstracted programming languages were still just emerging&lt;sup id=&quot;fnref:parnas&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:parnas&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/dsms-and-labour.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Image (and much of this sidebar) from &lt;a target=&quot;new&quot; href=&quot;https://dl.acm.org/doi/abs/10.5555/555152&quot;&gt;Design Rules Vol. 1&lt;/a&gt;. Figure is of a &lt;strong&gt;Design Structure Matrix&lt;/strong&gt;&lt;sup&gt;&lt;a href=&quot;#fn:dsms&quot;&gt;ref&lt;/a&gt;&lt;/sup&gt; that relates tasks (and designers) to one another. Tasks are enumerated on both axes of the matrix, and where two tasks constrain one another we mark an x. A design task is modularized when we can diagonalize that matrix to some high degree.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Externally, modularity allowed IBM to sell their mainframe to a wide selection of customers. Consumers of computing at the time were highly sophisticated users (banks, universities), who hired groups of staff to run their machines - not unlike the current state of the art with industrial Digital Fabrication - these users wanted to be able to modify their equipment, and understand it. So, IBM also released the 360’s instruction set, bus architecture spec, and the busses’ plug design.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/baldwin-clark-ibm-arch-03.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Image via WikiPedia contributor &lt;a href=&quot;https://commons.wikimedia.org/w/index.php?curid=38411260&quot;&gt;Tom94022&lt;/a&gt;. System/360 was an early “plug and play” architecture.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This has some real inlkings of the so-called &lt;em&gt;platform economy,&lt;sup id=&quot;fnref:platforms&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:platforms&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/em&gt; that emerged decades later, but IBM forgot to do the lock-in part, bless their hearts. As a result, their market share tanked - as tens of other high-tech competitors emerged.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/drv-plate1-1-ibm-market-caps.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Image from &lt;a target=&quot;new&quot; href=&quot;https://dl.acm.org/doi/abs/10.5555/555152&quot;&gt;Design Rules Vol. 1&lt;/a&gt;. When IBM released the System/360, their open architecture led to the emergence of many competitors, who ate up much of their market share in later decades. IBM`s share is in the back of this image, in blue.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This was bad news for IBM, but it was a major contributor to the historical bifurcation that made the Silicon Valley ecosystem into a world center for innovation in the following decades. Baldwin and Clark describe the resulting situation well:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;“Our theory is that modular designs evolve via a decentralized search by many designers for valuable options that are embedded in six modular operators.”&lt;sup id=&quot;fnref:drv&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:drv&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Basically, we got into a situation where all of these (now big-and-fancy, then small-and-scrappy) firms were effectively working together to improve one modular system: the modern computer. Parts were interchangeable, individual contributors could focus on highly specialized parts of the whole, and the systems still clicked together and worked - even though very few of the specialized engineers even knew who the others were, or how their counterparts’ contributions worked.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/drv-consortia.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The System/360 modularity lives on in today`s (mostly consortia-standardized) interoperability layers. &lt;a href=&quot;https://www.press.jhu.edu/books/title/11653/engineering-rules&quot;&gt;Engineering Rules&lt;/a&gt; covers their development, maintenance, and dissemination.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Over time, the underlying architectures mutated as well, and new ones popped up. The &lt;em&gt;particular design&lt;/em&gt; of the System/360’s architecture didn’t survive beyond the mainframe’s existence, but the &lt;em&gt;design pattern&lt;/em&gt; survived and improved, and it continues to do so&lt;sup id=&quot;fnref:intel&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:intel&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;. It is still possible to build your own computer using graphics cards from AMD or Nvidia, CPUs from AMD or Intel, memory from tens of different vendors, etc etc etc - all of this happens because this modular architecture is still with us, today in the form of tens of different specifications: DDR, PCIe, USB, … Standards that are developed and maintained by numerous consortia and working groups, which (if you’re into that kind of stuff) is explored in depth in Yates and Murphy’s &lt;em&gt;Engineering Rules&lt;/em&gt;&lt;sup id=&quot;fnref:eng-rules&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:eng-rules&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;9&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;!-- &gt; Silicon Valley today is far more than an agglomeration of individual technology firms. Its networks of interdependent yet autonomous producers are increasingly organized to grow and innovate reciprocally [...]. They spur the diffusion of new technologies by facilitating information exchange and joint problem solving between firms and even industries... --&gt;

&lt;p&gt;Economists all basically agree that this situation is universally beneficial: there are minimal re-engineering efforts, there is less monopolization, and we end up building better-performing systems overall. However, it seems difficult to get “into the groove” of it, so to speak: it requires large activation energy or, in the case of the Systems/360, it requires that a major incumbent makes a strategic misstep.&lt;/p&gt;

&lt;p&gt;There’s of course more to the info-tech story than I am presenting here, especially in the decades of work spent building the technologies that bring modularity to computers (compilers, languages, ISAs, etc) - as well as &lt;a href=&quot;https://www.youtube.com/watch?v=rXS6lTYSgCw&quot;&gt;this story&lt;/a&gt; on ‘unbundled software’ - inall, I think that the resulting economic setup is fascinating - even some kind of a miracle - and it provides a good backdrop for the rest of this discussion.&lt;/p&gt;

&lt;h1 id=&quot;2-commons-based-peer-production&quot;&gt;2. Commons Based Peer Production&lt;/h1&gt;

&lt;p&gt;On top of all that interoperable hardware, we eventually built Open Source Software (OSS), and since the late 90’s, economists and social scientists have been trying to figure out how it works. Not how the code works, but how the people coordinate themselves to work on the code.&lt;/p&gt;

&lt;p&gt;The first mystery was that highly talented individuals would give up their valuable time and energy to build free things, and the second (larger) mystery was how they were able to &lt;em&gt;build complex things without central coordination.&lt;/em&gt; Software like Unix, Apache and LibreOffice was normally built using hierarchichal coordination within a firm, but then suddenly there were these FOSS nerds doing it on message boards without any real bosses. Sounds cool, but it didn’t make any sense in the old models.&lt;/p&gt;

&lt;p&gt;The mystery has since been mostly solved, though the landscape continutes to evolve. Economists&lt;sup id=&quot;fnref:benkler&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:benkler&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;10&lt;/a&gt;&lt;/sup&gt;&lt;sup id=&quot;fnref:tirole&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:tirole&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;11&lt;/a&gt;&lt;/sup&gt; came to a few conclusions:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;(1)&lt;/strong&gt; People are naturally motivated to build stuff that they’re interested in.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;(2)&lt;/strong&gt; People are excited to share those things that they’ve built, and see their contributions realized in the world.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;(3)&lt;/strong&gt; Radically open “labour markets” like those existing in OSS projects (where anyone can work on anything) do a better job of matching interest and talent to problems that need solving (or that require invention, etc).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So, Open Source turns out to be a really, really good way to build things that require specialized skillsets - like Operating Systems or, perhaps next up, robotic systems. It also requires &lt;em&gt;good plumbing&lt;/em&gt; so that individuals’ niche contributions can easily be assembled into complete systems.&lt;/p&gt;

&lt;p&gt;The most revelatory book I read on the topic &lt;a href=&quot;https://press.stripe.com/working-in-public&quot;&gt;(Working in Public by Nadia Eghbal)&lt;/a&gt; &lt;sup id=&quot;fnref:wip&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:wip&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;12&lt;/a&gt;&lt;/sup&gt; compares OSS to social media, showing that folks are also motivated by &lt;em&gt;clout&lt;/em&gt;, and that most software these days is “open by default” (whereas earlier ethos were more evangelical&lt;sup id=&quot;fnref:cathedral&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:cathedral&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;13&lt;/a&gt;&lt;/sup&gt;&lt;sup id=&quot;fnref:stallman-misses&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:stallman-misses&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;14&lt;/a&gt;&lt;/sup&gt;&lt;sup id=&quot;fnref:manifesto&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:manifesto&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;15&lt;/a&gt;&lt;/sup&gt;). It also cites &lt;em&gt;Commons Based Peer Production&lt;/em&gt; (CBPP, a theory developed by Yochai Benkler &lt;sup id=&quot;fnref:benkler:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:benkler&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;10&lt;/a&gt;&lt;/sup&gt;) as the working method behind OSS. CBPP is &lt;em&gt;“a form of collective intelligence”&lt;/em&gt;&lt;sup id=&quot;fnref:mako-collective&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:mako-collective&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;16&lt;/a&gt;&lt;/sup&gt; that most software developers will these days be well acquainted with. In CBPP, there exists a large collection (a commons) of generalized, functional building blocks (libraries and packages) that can be easily integrated into new, specific applications (using APIs and high-level languages).&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/dev-charts-oss.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Commons Based Peer Production is an economic mode of production linked to a practice of systems development (pictured here), where specific applications are made from a &lt;em&gt;Commons&lt;/em&gt; of generalized functional modules. CBPP is prevalent (and powerful) in Open Software, but relies on technical infrastructures that are somewhat difficult to replicate in Open Hardware.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These commons are wonderful repositories of human intelligence, but not without their flaws. In particular, it is difficult to regulate the quality of their contributions, especially when contributions are made in multiple tiers (dependencies of dependencies, etc…). There are horror stories of singular, simple packages breaking entire swathes of the internet. That said, packages are chosen by intelligent developers, use-base consensus tends to emerge as to which are most useful, and safety and stability is part of that selection process.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;div style=&quot;display: flex; margin-bottom: 0.8rem;&quot;&gt;
  &lt;div style=&quot;padding-right: 3px&quot;&gt;
    &lt;img src=&quot;images/dependency.png&quot; /&gt;
  &lt;/div&gt;
  &lt;div style=&quot;padding-left: 3px;&quot;&gt;
    &lt;img src=&quot;images/python_environment.png&quot; /&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;&lt;a target=&quot;new&quot; href=&quot;https://xkcd.com/2347/&quot;&gt;XKCD: Dependency&lt;/a&gt; and &lt;a target=&quot;new&quot; href=&quot;https://xkcd.com/1987/&quot;&gt;XKCD: Python Environment.&lt;/a&gt; The systems that make up CBPP have many flaws, but its overall usefulness and value is clear to anyone who writes software.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;CBPP also stands in contrast to earlier Open Source ethos’ in its positioning; proponents of the 1990’s era movement supposed that legions of OS developers would build prevalent end-user facing products (word processors, operating systems etc) that would liberate end-users from for-profit software companies. &lt;em&gt;“Why pay for code when incremental costs are zero?”&lt;/em&gt; was a sensible and courageous stance that didn’t pan out: Microsoft Word still Reigns Supreme.&lt;/p&gt;

&lt;p&gt;While LibreOffice developers and their ilk were on a valiant mission to deliver free software to the people (but sort of failed), the current paradigm of CBPP has just about everyone on earth consuming Open Source contributions without their even knowing it - and it seems to have done it accidentally: as a by-product of developers just building better components for one another to use.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/dev-charts-producers-and-consumers.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Early models of Open Source (left) supposed that large groups of developers would work together on large, end-user facing products like word processors. The dominant, emergent model (right, Commons Based Peer Production) is one where small groups of developers work on small, functional building blocks (libraries and packages) that are accumulated in commons (like PIP and NPM) and subsequently re-assembled by other developers into end-user facing products. This has the effect of freeing Open Source developers, who are normally motivated by personal interest, from building “the boring components” of any given system (like UIs, user manuals, etc) and allows them to make contributions on “fun” parts like advanced algorithms, architectures, data structures, etc.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Open Source turns out to be better at producing infrastructural codes (server operating systems, network protocols, security algorithms) and generalized software modules (databases, mathematic routines, graphics tools, form validation…). This is partially because those are &lt;em&gt;the fun problems&lt;/em&gt; - and OS developers prefer the fun problems - and also because none of them require OS developers to author much in the way of user-facing documentation. Or, rather, that their users are other developers, and a &lt;em&gt;community of practice&lt;/em&gt; and common knowledge means that documentation can be minimal.&lt;/p&gt;

&lt;p&gt;This is another moment worthy of a second thought. At the moment, most Open Hardware projects need to be documented up the wazoo. When I deployed &lt;a href=&quot;https://ekswhyzee.com/2021/01/09/clank-lz.html&quot;&gt;clank!&lt;/a&gt; for &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.20/&quot;&gt;HTMAA&lt;/a&gt; students in 2020, I had to explain to everyone what a &lt;em&gt;socket head cap screw&lt;/em&gt; was, what a &lt;em&gt;NEMA 17 motor&lt;/em&gt; is, etc etc; we have students who have never held a power drill. Documenting machine kits to a level that everyone could be successful was a huge undertaking, one that I haven’t had the time to do since then, even though I have improved designs. Were it the case that we had a recognizeable community of machine builders (and it &lt;em&gt;is&lt;/em&gt; emerging), who shared contributions in a collective practice, the documentation burden would be much smaller. This is all just to say that speaking to a topic expert about their topic requires far fewer words: and that is effectively what happens in Open Software and what could be happening in Open Hardware. Two programmers need only explain their APIs to one another, not how to instantiate an object, how to call a function, etc etc…&lt;/p&gt;

&lt;p&gt;… OK, we were talking about how Open Source is consumed like a public good. Well, those infrastructural codes and modules we were talking about, those are widely used by other developers who package them up into application-specific user-facing products that they often make a profit on. This might seem like a devilish take for the Open Source evangelicals among us, but they should consider just how little most software costs, and just how many niches it is able to fill. Good software would be more expensive and less widely applicable if it were not written on the back of the commons. As Nadia Egbhal relates, &lt;em&gt;“Open Source is produced like a commons but consumed like a public good.”&lt;/em&gt;&lt;sup id=&quot;fnref:wip:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:wip&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;12&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Along this tangent is a field of research on &lt;em&gt;information commons&lt;/em&gt; writ large (like Wikipedia) of which OSS is one of many, and of which OSH would be one as well (itself not really being made up of hardware, but of hardware designs). For background in that domain we have i.e. Elinor Ostrom&lt;sup id=&quot;fnref:ostrom-commons&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:ostrom-commons&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;17&lt;/a&gt;&lt;/sup&gt;&lt;sup id=&quot;fnref:ostrom-info&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:ostrom-info&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;18&lt;/a&gt;&lt;/sup&gt; and Lawrence Lessig&lt;sup id=&quot;fnref:lessig-info&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:lessig-info&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;19&lt;/a&gt;&lt;/sup&gt;. There is also fascinating work on how open-ness (as in, information sharing) helped to advance the design of steam engines in the very early industrial revolution&lt;sup id=&quot;fnref:nuvolari:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:nuvolari&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;sup id=&quot;fnref:allen&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:allen&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;20&lt;/a&gt;&lt;/sup&gt;, as well as Eric von Hippel’s famous exploration of Open Source patterns in extreme sports and other hardware&lt;sup id=&quot;fnref:evh&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:evh&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;21&lt;/a&gt;&lt;/sup&gt;, and Baldwin and Clark’s really-super-neat work on modularity in the &lt;em&gt;hardware of information systems&lt;/em&gt;&lt;sup id=&quot;fnref:drv:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:drv&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;7&lt;/a&gt;&lt;/sup&gt; (i.e. computers) whose development they describe as another form of collective intelligence&lt;sup id=&quot;fnref:mako-collective:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:mako-collective&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;16&lt;/a&gt;&lt;/sup&gt;. Finally, we shouldn’t forget that much of the hardware world is already made from a commons of standardized components; this is well documented in Yates and Murphy’s Engineering Rules&lt;sup id=&quot;fnref:eng-rules:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:eng-rules&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;9&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;h2 id=&quot;21-design-patterns-proceed-direct-integration-of-designs&quot;&gt;2.1 Design Patterns Proceed Direct Integration of Designs&lt;/h2&gt;

&lt;p&gt;So, Commons Based Peer Production is a wonderful framework to understand how our technical world can be built out of a kind of collective intelligence, but it mostly explains how &lt;em&gt;direct commons&lt;/em&gt; operate &lt;em&gt;once they have been established.&lt;/em&gt; Since this little essay is supposing that OSH would be helped along by establishing more of these types of commons’, we want to understand also how they emerge.&lt;/p&gt;

&lt;p&gt;To differentiate, we call a &lt;em&gt;direct commons&lt;/em&gt; any identifiable collection of &lt;em&gt;designs&lt;/em&gt; - these are concrete, dimensioned, rigorously specified drawings, source codes, schematics, circuit layouts, documents, etc: these are all contributions that can be immediately put to use. Before these emerge, we can identify &lt;em&gt;commons of practice.&lt;/em&gt; These are just as real, but they are made up of &lt;em&gt;design patterns;&lt;/em&gt; ideas about how to generally go about making a thing. This might include sets of kinematic arrangements like &lt;a href=&quot;http://corexy.com&quot;&gt;corexy&lt;/a&gt;, belt tensioning strategies, embedded programming patterns like static memory allocation, etc. Design patterns could probably be conveyed successfully in conversation, or on a whiteboard, but &lt;em&gt;designs&lt;/em&gt; require more tooling: dimensioned drawings, file formats, etc. Between direct commons and common practices, the latter are probably more important overall, but they’re squishier: harder to delineate and measure. They also tend to emerge as pre-requisites.&lt;/p&gt;

&lt;p&gt;I like to take Object Oriented Programming (OOP) as an example. The idea has been around for nearly half of a century, kicked off with Parnas’ ideas about information hiding&lt;sup id=&quot;fnref:parnas-info-hiding&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:parnas-info-hiding&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;22&lt;/a&gt;&lt;/sup&gt;&lt;sup id=&quot;fnref:parnas-criteria&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:parnas-criteria&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;23&lt;/a&gt;&lt;/sup&gt;, and popularized by the likes of Christopher Alexander&lt;sup id=&quot;fnref:pattern-language&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:pattern-language&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;24&lt;/a&gt;&lt;/sup&gt;. So, it was in the soup - and then formalized into handfuls of programming languages. Without this idea and the associated ethos (encapsulation, modularization, organization of function), we wouldn’t have anything like PIP or NPM, whose users deploy this design pattern without thinking twice about it.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/design-patterns-timeline-improve.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Design Patterns emerge first, and last longer than literal Designs themselves. Common understanding of a set of Design Patterns is normally a pre-requisite for the emergence of a Direct Commons.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is all to say that need a collective understanding of how our shared systems work before we can succesfully use them; NPM requires OOP and APIs which require a broader idea of information hiding. Open Hardware might need some new (or more practice with) design patterns as well: networking, concurrency, asynchronousness, reflective programming, feedback systems, interface design, etc.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;div style=&quot;display: flex; margin-bottom: 0.8rem;&quot;&gt;
  &lt;div style=&quot;padding-right: 3px&quot;&gt;
    &lt;img src=&quot;images/nuvolari-cornish-engine.png&quot; /&gt;
  &lt;/div&gt;
  &lt;div style=&quot;padding-left: 3px;&quot;&gt;
    &lt;img src=&quot;images/von-hippel-kiting.jpg&quot; /&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For more thinking on shared design patterns, consult Nuvolari&lt;sup id=&quot;fnref:nuvolari:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:nuvolari&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; and Allen&lt;sup id=&quot;fnref:allen:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:allen&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;20&lt;/a&gt;&lt;/sup&gt; who showed that openly disseminated plans for steam engines (at left in the figure above) formed a backbone of the English industrial revolution. Eric von Hippel&lt;sup id=&quot;fnref:evh:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:evh&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;21&lt;/a&gt;&lt;/sup&gt; explores how design patterns bounce around in communities of users, and looks in particular at how innovation happens ‘on the ground’ in niche extreme sports (at right).&lt;/p&gt;

&lt;h2 id=&quot;22-commons-emerge-around-systems-integration-methods&quot;&gt;2.2 Commons Emerge Around Systems Integration Methods&lt;/h2&gt;

&lt;p&gt;Once a common practice is established, there is viable grounds on which to build a direct commons: a place where literal, immediately-useful designs can be shared. But there is one more component needed, which is some organizing integration method or deployment tool.&lt;/p&gt;

&lt;p&gt;This notion really struck me when Prusa renamed their design-sharing site to just &lt;a href=&quot;https://blog.prusa3d.com/prusaprinters-org-is-now-printables-com-ultimate-database-of-3d-models-for-everyone_66880/&quot;&gt;“Printables”&lt;/a&gt; - it’s a commons made up of designs that can be deployed immediately by any member of the community who knows how to use a 3D Printer.&lt;/p&gt;

&lt;div style=&quot;display: flex;&quot;&gt;
  &lt;div style=&quot;flex-basis: 100%; padding: 5px&quot;&gt;
    &lt;img src=&quot;images/logos/npm.png&quot; /&gt;&lt;br /&gt;
  &lt;/div&gt;
  &lt;div style=&quot;flex-basis: 100%; padding: 15px&quot;&gt;
    &lt;img src=&quot;images/logos/pypi.svg&quot; /&gt;&lt;br /&gt;
  &lt;/div&gt;
  &lt;div style=&quot;flex-basis: 100%; padding: 5px&quot;&gt;
    &lt;img src=&quot;images/logos/arduino.png&quot; /&gt;&lt;br /&gt;
  &lt;/div&gt;
  &lt;div style=&quot;flex-basis: 100%; padding: 5px&quot;&gt;
    &lt;img src=&quot;images/logos/printables.jpg&quot; /&gt;&lt;br /&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;div style=&quot;display: flex;&quot;&gt;
  &lt;div style=&quot;flex-basis: 100%; padding: 5px&quot;&gt;
    &lt;strong&gt;NPM&lt;/strong&gt;&lt;br /&gt;
    Things you can use in your JavaScript program.
  &lt;/div&gt;
  &lt;div style=&quot;flex-basis: 100%; padding: 5px&quot;&gt;
    &lt;strong&gt;PIP&lt;/strong&gt;&lt;br /&gt;
    Things you can use in your Python program.
  &lt;/div&gt;
  &lt;div style=&quot;flex-basis: 100%; padding: 5px&quot;&gt;
    &lt;strong&gt;Arduino (libraries)&lt;/strong&gt;&lt;br /&gt;
    Things you &lt;em&gt;might&lt;/em&gt; be able to use in your Arduino project.
  &lt;/div&gt;
  &lt;div style=&quot;flex-basis: 100%; padding: 5px&quot;&gt;
    &lt;strong&gt;Printables&lt;/strong&gt;&lt;br /&gt;
    Things you can Print!
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;These all have some caveats, mostly because users’ actual systems are all a little different: i.e. some Printables Prints will only work on multi-filament machines, many Arduino libraries are not compatible with all Arduinos&lt;sup id=&quot;fnref:arduino-libs&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:arduino-libs&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;25&lt;/a&gt;&lt;/sup&gt;, and NPM and PIP packages will grant variable milage depending on their users’ setups: typescript-or-not, esm-version, dependencies… The world is heterogeneous, and commons-es require &lt;em&gt;some&lt;/em&gt; homogeneity, but luckily not absolute homogeneity.&lt;/p&gt;

&lt;p&gt;And that’s the point here: a stable &lt;em&gt;method of integration&lt;/em&gt; provides the homogeneity required for a technical commons to emerge. NPM and PIP were built on the back of JS and Python’s ubiquity, Printables on the ubiquity of the FDM printer. Those are simple examples, but Open Hardware in general is an umbrella of many other disciplines, each of which has variable integration systems (most of which are either ad-hoc or proprietary). But, it also points the way: if we can make &lt;em&gt;good systems integration tools&lt;/em&gt; for various sub-disciplines in hardware, we stand a better chance at building in the commons.&lt;/p&gt;

&lt;p&gt;Thinking about this might also help us identify missing commons. For example, everyone is basically agreed on how to draw a schematic for a circuit, and how to render and share design files (with Gerbers). BOMs, also, are relatively easy to standardize. GitHub is (so far) poorly suited to host these types of content because it doesn’t have built-in tools to render them and because some of the formats are difficult to track using git’s versioning system. However, &lt;a href=&quot;https://kitspace.org/&quot;&gt;kitspace&lt;/a&gt; has emerged as a commons for circuits and it provides the utility of automatically ordering boards and BOMs of anything contributed there.&lt;/p&gt;

&lt;h1 id=&quot;3-technical-properties-of-the-commons&quot;&gt;3. Technical Properties of the Commons&lt;/h1&gt;

&lt;p&gt;So, we see that together with a set of ubiquitous design patterns, systems integration tools enable commons’ of directly shareable designs to emerge. For modularity, we need modular systems architectures. A quote from Benkler is worth our time here:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The remaining obstacle to effective peer production is the problem of integration of the modules into a finished product. […] In order for a project to be susceptible to sustainable peer production, the integration function must be either low-cost or itself sufficiently modular to be peer-produced in an interative process.&lt;br /&gt;
From &lt;em&gt;Coase’s Penguin, or, Linux and ‘The Nature of the Firm.’&lt;/em&gt;&lt;sup id=&quot;fnref:benkler:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:benkler&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;10&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Researchers who study these tools note that many of them exhibit similar properties, each of which gets a sub-section here.&lt;/p&gt;

&lt;h2 id=&quot;31-modularity&quot;&gt;3.1 Modularity&lt;/h2&gt;

&lt;p&gt;This property is pretty obvious and tautological; commons’ are made of modular bits. Hidden behind this is the more complex requirement for a module to &lt;em&gt;generalize&lt;/em&gt; - even if we make a stepper motor driver, say, if it doesn’t generalize across machine &lt;em&gt;and&lt;/em&gt; robotics &lt;em&gt;and&lt;/em&gt; home automation applications, we don’t have a very good module. Hardware modules have the added challenge of scaling across applications: the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;motor&lt;/code&gt; we want for an excavator is a very different one than we want for a surgical robot, so even in domains where functional blocks can be abstracted using similar descriptors, their hardware instantiations will still need to be wide-ranging. This one of the reasons why I’m also trying to build &lt;a href=&quot;../../../2023/05/24/dynamical-descriptions.html&quot;&gt;self-tuning motion systems&lt;/a&gt;, to automatically align generalizeable, abstract models with the real world’s heterogeneity.&lt;/p&gt;

&lt;p&gt;Modularity &lt;em&gt;always&lt;/em&gt; poses a challenge to interface and API designers: how to expose enough of a component’s functionality so that it can be altered for multiple uses, while not over-burdening users with configurations. It is normally impossible for module designers to foresee all possible uses of their contribution, so API design remains something of an art; it is also a domain that will get many software engineers hot under the collar… strong opinions abound, and there are not many rigorous methods available with which to evaluate them.&lt;/p&gt;

&lt;h2 id=&quot;32-granularity&quot;&gt;3.2 Granularity&lt;/h2&gt;

&lt;p&gt;One of the core tensions of modular systems design regards the &lt;em&gt;size of a module.&lt;/em&gt; Larger modules can be more performant and faster to write for a specific system, but they tend not to generalize as well into components for other applications (complexity tends to go hand-in-hand with specificity). Larger modules also require more time up-front to understand and edit, and can be harder to modify down the road; technical debt is commonly found in the form of ad-hoc, monolithic chunks of code that are difficult to decipher.&lt;/p&gt;

&lt;p&gt;Successful commons’ tend to be highly granular, meaning they are made up of many small contributions rather than a few big ones. This has the technical advantages of comprehensibility and extensibility but it also has the economic function of breaking complex work into smaller chunks of labour. CBPP theorists point this out as an important feature because most contributors to Open Source do so “on the side” - meaning that they don’t have the weeks and months of time required to author large, complex contributions. Instead, they have nights and weekends and tend to make small additions at a time.&lt;/p&gt;

&lt;h2 id=&quot;33-minimal-burden-of-integration&quot;&gt;3.3 Minimal Burden of Integration&lt;/h2&gt;

&lt;p&gt;This is perhaps the most important property of a succesful commons: it should be very easy to contribute a new module to the commons, and it should be very easy to deploy a module into an application. This extends to the perhaps unintuitive notion that, &lt;em&gt;in order to use someone’s module, we should not have to understand it,&lt;/em&gt; which is where the notion of collective intelligence&lt;sup id=&quot;fnref:mako-collective:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:mako-collective&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;16&lt;/a&gt;&lt;/sup&gt; emerges. If we can use the outputs of one anothers’ intellect without comprehending it, the systems we build from those components can embody an intelligence greater than our own &lt;em&gt;and&lt;/em&gt; we get to keep some left-over brainpower with which to make our own new and specialized contribution.&lt;/p&gt;

&lt;p&gt;We’ve talked about how easy module integration is with software, where programming languages are de-facto systems integration tools and modular design patterns are already diffused in the discipline of computer science. That baseline has been aided as languages have added first-class support for module importing to further reduce the burden of integration, like ES6’s &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import&quot;&gt;import&lt;/a&gt; statement (that was originally a node.js &lt;a href=&quot;https://nodejs.org/api/modules.html&quot;&gt;kludge&lt;/a&gt;). In languages like C++, where no language-level import statement has existed (until &lt;a href=&quot;https://en.cppreference.com/w/cpp/language/modules&quot;&gt;recently&lt;/a&gt;), no ubiquitous package manager (or commons of libraries) exists.&lt;/p&gt;

&lt;p&gt;The arithmetic on why this metric is important is pretty simple; developers weigh the time it takes to contribute their module to the value they get from contributing it. Where incentives are small, the time required needs to be small. If developers don’t contribute, the commons doesn’t grow. Similarely, if it’s difficult to include someone else’s work, users are more likely to write things from scratch - meaning they will build less of the new stuff, and our collective ability dwindles.&lt;/p&gt;

&lt;p&gt;Along these lines, I recently pushed an &lt;a href=&quot;../../../2024/02/09/no-more-newline.html&quot;&gt;update to Modular Things&lt;/a&gt; that makes it easier to build high-level computing interfaces for hardware modules, in an effort to lower the barrier for device-to-device integration (by allowing folks to “do it in software”).&lt;/p&gt;

&lt;p&gt;People are also not likely to use an ecosystem of components when plugging into that ecosystem is difficult, or when large changes to their existing project are required in order for them to interface with it. This is why the start-up cost of these commons’ is often quite high. We need to build out enough of the systems such that developers will be incentivized to use it as a first-class citizen in their projects. Or, systems need to be simple and flexible enough that they are easily added to existing projects.&lt;/p&gt;

&lt;h1 id=&quot;4-to-the-commons-&quot;&gt;4. To the Commons !&lt;/h1&gt;

&lt;p&gt;So, this ends Part One. I hope you’ve seen how &lt;em&gt;the commons&lt;/em&gt; can help us build complex and meaningful technologies using open contributions, and how they emerge and grow - and what we need to build to enable them.&lt;/p&gt;

&lt;p&gt;Perhaps you are thinking “gee, much of OSH is already looking like this” - and you would be correct; I plan to explore that topic next in more detail next. I think our current state is modular-ish, but that we haven’t accomplished true ‘plug-and-play’ interoperability that lets a commons flourish.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/dev-charts-producers-and-consumers.png&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;I want to return to this central idea: OSH is trying to build end-user facing products with a lot of re-engineering. Instead, we should look towards modular systems, and build a commons of interoperable parts. This would let us build a more diverse set of more complex artefacts that are easier to use and understand.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But I started by considering the plight of Open Hardware firms, who are still relatively plucky (vs. their Open Software counterparts, and vs. the industrial incumbents who dominate our economy). A lot of the people I talk to want to launch these kinds of firms. They want to &lt;em&gt;build stuff,&lt;/em&gt; to do R&amp;amp;D, maintain a shop, make meaningful contributions and share them in the open. This means they effectively want to donate their expertise as a public good: free and useful information. They just want to do it sustainably, and they probably want a little bit of clout, as a treat - and as they are warranted.&lt;/p&gt;

&lt;p&gt;I think that governments, businesses and academic institutions aught to be listening to them, and that we should all be trying to figure out how to enable their dream; these are talented individuals whose other door opens into giant tech salaries and NDAs with businesses that &lt;em&gt;dont pay their taxes.&lt;/em&gt; It would be a shame if we let this generations’ engineering talents continue to build up those walled gardens when we have such a promising opportunity to divert them into a shared pool of collective intelligence and wealth, and so many meaningful problems to solve.&lt;/p&gt;

&lt;p&gt;I think that &lt;em&gt;building the commons&lt;/em&gt; is where it begins, but it is for sure not the whole picture… next up is a discussion on the current state of modularity, then a look at device-to-device integration systems, and finally I’ll explore some forwards-looking economics for the commons.&lt;/p&gt;

&lt;h2 id=&quot;notes--references&quot;&gt;Notes / References&lt;/h2&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:open-purchase&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Consumers are also incentivized to to purchase from original developers because they know they are getting a product that has been manufactured to the original intent, whereas third-party vendors occasionally use i.e. off-brand chips that cause compatibility issues, or otherwise cut corners. &lt;a href=&quot;#fnref:open-purchase&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:nuvolari&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Nuvolari, A. “Collective Invention during the British Industrial Revolution: The Case of the Cornish Pumping Engine.” Cambridge Journal of Economics, vol. 28, no. 3, May 2004, pp. 347–63. DOI.org (Crossref), https://doi.org/10.1093/cje/28.3.347. &lt;a href=&quot;#fnref:nuvolari&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:nuvolari:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:nuvolari:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:lampson&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Lampson, Butler W. “Software components: Only the giants survive.” &lt;em&gt;Computer Systems: Theory, Technology, and Applications.&lt;/em&gt; New York, NY: Springer New York, 2004. 137-145. &lt;a href=&quot;#fnref:lampson&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:dsms&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Today, researchers use Design Structure Matrices to study these kinds of couplings. DSMs are also one of the only quantitative manners I have found to measure modularity, and they’re a useful thinking tool. For more, there’s Alan MacCormack’s &lt;a href=&quot;https://doi.org/10.1287/mnsc.1060.0552&quot;&gt;Exploring the Structure of Complex Software Designs: An Empirical Study of Open Source and Proprietary Code.&lt;/a&gt; wherein the structure of Mozilla Firefox is analyzed in the context of Open Source (Firefox’s authors were preparing to OS the code, and wanted to do a modular refactor beforehand; they understood that the code would only be successful as an open project were it made modular) and similar work: &lt;a href=&quot;https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1071720&quot;&gt;The Impact of Component Modularity on Design Evolution&lt;/a&gt;. &lt;a href=&quot;#fnref:dsms&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:parnas&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;The first notion of &lt;em&gt;information hiding&lt;/em&gt; that I’ve found is Parnas’ (1972) &lt;a href=&quot;https://dl.acm.org/doi/abs/10.1145/361598.361623&quot;&gt;On the criteria to be used in decomposing systems into modules&lt;/a&gt;, also-interesting is his (1985) &lt;a href=&quot;https://ieeexplore.ieee.org/abstract/document/1702002&quot;&gt;The Modular Structure of Complex Systems&lt;/a&gt;. Since we’re on the topic, I think the first paper is a fascinating read in the context of &lt;strong&gt;hardware vs. software modularity&lt;/strong&gt; because it reminds us that &lt;strong&gt;compilers remove modularity&lt;/strong&gt; when they optimize a program… this seems to be a good reminder that modularity, which seems to easy in computing systems, &lt;em&gt;actually tends to come with a huge performance cost.&lt;/em&gt; We get away with building modular systems in software because we have automatic translators (compilers) between bespoke systems (code as compiled) and the systems representations we use to design them (modular codebases). Bespoke things are faster. Everyone knows this, it’s why &lt;em&gt;bespoke&lt;/em&gt; or &lt;em&gt;proprietary&lt;/em&gt; is synonymous with &lt;em&gt;hot, fast and sexy.&lt;/em&gt; In hardware, we have to reckon directly with the thing itself, and live with the real-world costs of our modular systems descriptions. Maybe. &lt;a href=&quot;#fnref:parnas&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:platforms&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;This is hot in the business science world, or it was a decade ago. See i.e. Martin Kenney’s (2016) &lt;a href=&quot;https://brie.berkeley.edu/sites/default/files/kenney-zysman-the-rise-of-the-platform-economy-spring-2016-istx.pdf&quot;&gt;The Rise of the Platform Economy&lt;/a&gt;. &lt;a href=&quot;#fnref:platforms&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:drv&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Baldwin, Carliss Young, and Kim B. Clark. Design rules: The power of modularity. Vol. 1. MIT press, 2000. &lt;a href=&quot;#fnref:drv&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:drv:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:intel&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;For example Intel, who championed the currently dominant instruction set (x86) was founded in 1968, two years after the System/360s release. We are already seeing a shift to ARM-based instruction sets, which itself should give way to RISC-V, an open-source ISA. &lt;a href=&quot;#fnref:intel&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:eng-rules&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Yates, JoAnne, and Craig N. Murphy. &lt;em&gt;Engineering rules: Global standard setting since 1880.&lt;/em&gt; JHU Press, 2019. &lt;a href=&quot;#fnref:eng-rules&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:eng-rules:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:benkler&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Benkler, Yochai. “Coase’s Penguin, or, Linux and ‘The Nature of the Firm.’” The Yale Law Journal, vol. 112, no. 3, Dec. 2002, p. 369. DOI.org (Crossref), &lt;a href=&quot;https://doi.org/10.2307/1562247&quot;&gt;https://doi.org/10.2307/1562247&lt;/a&gt;. &lt;a href=&quot;#fnref:benkler&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:benkler:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:benkler:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:tirole&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Lerner, Josh, and Jean Tirole. “Some simple economics of Open Source.” The journal of industrial economics 50.2 (2002): 197-234. &lt;a href=&quot;#fnref:tirole&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:wip&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Eghbal, Nadia. Working in public: the making and maintenance of Open Source software. San Francisco: Stripe Press, 2020. &lt;a href=&quot;#fnref:wip&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:wip:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:cathedral&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Raymond, Eric. “The Cathedral and the Bazaar.” Knowledge, Technology and Policy, vol. 12, no. 3, 1999, pp. 23–49. &lt;a href=&quot;#fnref:cathedral&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:stallman-misses&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Stallman, Richard. “Why ‘Open Source’ Misses the Point of Free Software.” Communications of the ACM, vol. 52, no. 6, June 2009, pp. 31–33. DOI.org &lt;a href=&quot;#fnref:stallman-misses&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:manifesto&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Stallman, Richard. The GNU Manifesto. 1985. &lt;a href=&quot;#fnref:manifesto&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:mako-collective&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Benkler, Yochai, Aaron Shaw, and Benjamin Mako Hill. “Peer production: A form of collective intelligence.” Handbook of collective intelligence 175 (2015). &lt;a href=&quot;#fnref:mako-collective&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:mako-collective:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:mako-collective:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:ostrom-commons&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Ostrom, Elinor. Beyond Markets and States: Polycentric Governance of Complex Economic Systems. Nobel Prize Lectures, 8 Dec. 2009. &lt;a href=&quot;#fnref:ostrom-commons&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:ostrom-info&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Ostrom, Elinor, and Charlotte Hess. Understanding Knowledge as a Commons: From Theory to Practice. MIT Press, 2007. &lt;a href=&quot;#fnref:ostrom-info&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:lessig-info&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Lessig, Lawrence. The Future of Ideas: The Fate of the Commons in a Connected World. “Chapter 2: Building Blocks” and “Chapter 14: Alt.Commons” 1st ed., Random House, 2001. &lt;a href=&quot;#fnref:lessig-info&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:allen&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Allen, Robert C. “Collective Invention.” Journal of Economic Behavior &amp;amp; Organization, vol. 4, no. 1, 1983, pp. 1–24. &lt;a href=&quot;#fnref:allen&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:allen:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:evh&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Hippel, Eric von. Democratizing Innovation. MIT Press, 2005. &lt;a href=&quot;#fnref:evh&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:evh:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:parnas-info-hiding&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Parnas, David Lorge, Paul C. Clements, and David M. Weiss. “Enhancing reusability with information hiding.” Proceedings of the ITT Workshop on Reusability in Programming. 1983. &lt;a href=&quot;#fnref:parnas-info-hiding&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:parnas-criteria&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Parnas, David Lorge. “On the criteria to be used in decomposing systems into modules.” Communications of the ACM 15.12 (1972): 1053-1058. &lt;a href=&quot;#fnref:parnas-criteria&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:pattern-language&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Alexander, Christopher. &lt;em&gt;A pattern language: towns, buildings, construction.&lt;/em&gt; Oxford university press, 1977. &lt;a href=&quot;#fnref:pattern-language&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:arduino-libs&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;On the topic of messy-arduino-gotchas, I could go on for a while. While Arduino attempts to enable code portability across microcontrollers, it is a largely leaky abstraction: libraries compile on some devices and not others, libraries can collide with one another (when they both need to use the same UART or SPI peripheral, for example), and can also easily overflow any givne microcontrollers’ compute or memory resources. I think it boils down to the fundamental non-portability of embedded code. With microcontrollers, more than any other computing system, we need to align our programs with their physical situation: not just the particular MCU running underneath the Arduino layer, but also &lt;em&gt;the circuit&lt;/em&gt; that the MCU is attached to. Arduino basically relegates this alignment-hunting to the CPP compiler, which is not made for such a task (compilers being purely feed-forward tools). Template programming might be able to do it, but that sounds like hell also. I remain convinced that doing Arduino’s job well requires a ground-up new-arduino, and probably something akin to a DSL, and probably something based on a more universal hardware description language for circuits. My buddy &lt;a href=&quot;https://erik.pages.cba.mit.edu/home/&quot;&gt;Erik&lt;/a&gt; is cooking something up along these lines, bless his soul. &lt;a href=&quot;#fnref:arduino-libs&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Mon, 16 Oct 2023 00:00:00 -0400</pubDate>
        <link>http://ekswhyzee.com/2023/10/16/searching-for-oshw.html</link>
        <guid isPermaLink="true">http://ekswhyzee.com/2023/10/16/searching-for-oshw.html</guid>
        
        <category>open-hardware</category>
        
        <category>economics</category>
        
        <category>commons</category>
        
        
      </item>
    
      <item>
        <title>Clangers and Bangers</title>
        <description>&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;video controls=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot;&gt;
  &lt;source src=&quot;videos/2023-07-21_bangers-and-clangers.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Werner Herzog does Music Machines&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We had a fun week trying to make this off-the-research-piste project at Haystack this July. This was part of &lt;a href=&quot;https://www.haystack-mtn.org/haystack-labs&quot;&gt;Haystack Labs 2023&lt;/a&gt;, and I had the pleasure of working with &lt;a href=&quot;https://onwisconsin.uwalumni.com/the-frankenpianist/&quot;&gt;Christopher Taylor&lt;/a&gt;, &lt;a href=&quot;https://fabacademy.org/2020/labs/ulb/students/quentin-bolsee/&quot;&gt;Quentin&lt;/a&gt; and &lt;a href=&quot;https://www.ivanadama.com/&quot;&gt;Ivana Dama&lt;/a&gt;.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2023-07-12_jewelery-studio.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;In the Jewelery Studio at Haystack&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;about-composable-systems&quot;&gt;About Composable Systems&lt;/h2&gt;

&lt;p&gt;I’ve always wanted to build some kind of prototyping kit for music machines. I’m not a musician myself but it seems that the medium is highly &lt;strong&gt;composable&lt;/strong&gt; - meaning it’s hard to make permutations of a musical system that are fundamentally broken.&lt;/p&gt;

&lt;p&gt;That seems like a crazy statement if you’re just interested in music making. I’ve spent a long while trying to build machine controller systems; i.e. for 3D printers, laser cutters, cutting machines and their ilk. We want their assembly, repair and modification to be playful, fun … inviting for newcomers. This is work towards the end that more amongst us might end up engaged in machines’ making and maintenance, and that as a result more could engage with the material world in a less alienating manner, or be more likely to sieze the economic means to do automation, precision fabrication, etc (and all that would entail).&lt;/p&gt;

&lt;p&gt;In any case, when we build machine systems, there are lots of ways to break them. Things can go wrong pretty quickly, basically: &lt;strong&gt;machine systems are not very composable&lt;/strong&gt; - that’s the whole challenge. On the other hand, &lt;strong&gt;music is composable&lt;/strong&gt; - we can throw new things in the mix, improvise, bang on pots and pans etc, and nothing goes dangerously wrong.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2023-07-12_blackboard.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Scheming on protocols, and schemas of keymaps.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;what-we-built&quot;&gt;What We Built&lt;/h2&gt;

&lt;p&gt;So we brought a kit of circuits for whacking (with solenoids) and thrumming (with transducers), and a new bus protocol we had been working on. None of which were especially finished.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2023-07-12_thwapper-detail.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Our ad-hoc pots-and-pans banger fixture.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;p style=&quot;margin-bottom: 0.8rem&quot;&gt;
  
  &lt;img src=&quot;images/2023-07-12_its-an-instrument-right.jpg&quot; /&gt;
  
&lt;/p&gt;
&lt;!-- likewise a video,  --&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;The above, as deployed.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The results are evident in the video at the head of this page (and below); not wonderful, but kind of funny. I wanted badly to make something that would bring Christopher some joy as he played it, but I’m not convinced we managed it.&lt;/p&gt;

&lt;!-- get the file format  --&gt;

&lt;!-- if it&apos;s an image, do image stuff  --&gt;

&lt;video controls=&quot;&quot; width=&quot;100%&quot; style=&quot;margin-bottom: 0.8rem&quot;&gt;
  &lt;source src=&quot;videos/2023-07-12_clangers-and-bangers.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;blockquote style=&quot;font-size: medium; font-style: italic; padding-left: 1.2rem; margin-bottom: 3.2rem;&quot;&gt;
  &lt;p style=&quot;padding-bottom: 0.8rem&quot;&gt;
    &lt;p&gt;Christopher Taylor, virtuosic pianist (and talented programmer), banging and clanging.&lt;/p&gt;

  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think Christopher did have a blast programming though - he helped debug our protocol, and invented the keymapping system we used (to translate midi from our keyboard into various solenoid-strokes).&lt;/p&gt;

&lt;h2 id=&quot;what-we-learned&quot;&gt;What We Learned&lt;/h2&gt;

&lt;p&gt;This was a fun little misadventure on my part - and I even learned a few valuable things;&lt;/p&gt;

&lt;h3 id=&quot;the-world-of-musical-systems-assembly-is-rich-and-full-of-ad-hoc-solutions-that-work&quot;&gt;The world of Musical Systems Assembly is rich, and full of ad-hoc solutions that &lt;strong&gt;work.&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;I’m often complaining that the CAD/CAM/Machining world suffers from a lack of &lt;strong&gt;field mechanic-ness&lt;/strong&gt; - i.e. the property (often found on i.e. farms, on the ISS, or amongst other infrastructure maintainers) that tools and systems are built, modified and maintained by those who use them.&lt;/p&gt;

&lt;p&gt;In particular, I think that &lt;strong&gt;mechatronic systems&lt;/strong&gt; present a barrier to this kind of work because their complexity is often large &lt;em&gt;and&lt;/em&gt; those who use them (largely physical-world dwellers) tend not to intermingle with those who develop them (largely mathematics-loving, abstract-space dwellers). I suspect that CNC remains clunkly largely because of this.&lt;/p&gt;

&lt;p&gt;There’s a much larger discussion in there, but the gist here is that &lt;strong&gt;musicians are good at making composable systems&lt;/strong&gt; and their tools, which are authored largely by their own community-members, are awesome. I’m thinking &lt;a href=&quot;https://puredata.info/&quot;&gt;puredata&lt;/a&gt;, &lt;a href=&quot;https://cycling74.com/products/max&quot;&gt;MaxMSP&lt;/a&gt;, &lt;a href=&quot;https://opensoundcontrol.stanford.edu/&quot;&gt;open sound control&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I’m thinking also about &lt;a href=&quot;https://en.wikipedia.org/wiki/MIDI&quot;&gt;MIDI&lt;/a&gt; (~ three bytes per event), and &lt;a href=&quot;https://en.wikipedia.org/wiki/CV/gate&quot;&gt;Control Voltage&lt;/a&gt; (~ one volt per octave) - really slick, simple, and easily grokk’d protocols. It’s like, &lt;a href=&quot;https://www.youtube.com/watch?v=MijmeoH9LT4&quot;&gt;UTF-8 level extensibility&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One of the reasons these are so wonderful is probably that you don’t need to pull anyone’s code to start using them: they’re easy enough to understand that you can build their guts from-scratch in whatever system you want in a few hours. That might be difficult for embedded systems in general, but it might be critical.&lt;/p&gt;

&lt;h3 id=&quot;construction-kits-need-to-deploy-in-a-batteries-included-state&quot;&gt;Construction Kits need to deploy in a “Batteries Included” state.&lt;/h3&gt;

&lt;p&gt;We didn’t show up to this party with everything finished, we should have. That means earlier testing, and more scaffolding:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;circuit should not be fragile&lt;/li&gt;
  &lt;li&gt;we shouldn’t have to wait for any 3D prints to finish before we can do stuff&lt;/li&gt;
  &lt;li&gt;systems should not require explainers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;https://dl.acm.org/doi/abs/10.1145/1109540.11095560&quot;&gt;We already know most of this.&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;construction-kits-need-out-of-the-box-examples&quot;&gt;Construction Kits need out-of-the-box Examples&lt;/h3&gt;

&lt;p&gt;This is basically the same point as above; we should have four or five fun things to do with the kit before we leave the lab. They should work right away, and they should make what-all is going on behind the systems clear to everyone involved.&lt;/p&gt;

&lt;h2 id=&quot;fin&quot;&gt;Fin&lt;/h2&gt;

&lt;p&gt;So, good to learn in practice what we suspected to be true, and to have some motivation to do a little better next time, scaffolding wise.&lt;/p&gt;

&lt;p&gt;Huge thanks to Haystack for hosting us!&lt;/p&gt;
</description>
        <pubDate>Fri, 21 Jul 2023 00:00:00 -0400</pubDate>
        <link>http://ekswhyzee.com/2023/07/21/clangers-and-bangers.html</link>
        <guid isPermaLink="true">http://ekswhyzee.com/2023/07/21/clangers-and-bangers.html</guid>
        
        <category>systems</category>
        
        
      </item>
    
  </channel>
</rss>
