<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>CodeCell | Antoine Weill--Duflos</title>
    <link>https://antoine.weill-duflos.fr/en/tag/codecell/</link>
      <atom:link href="https://antoine.weill-duflos.fr/en/tag/codecell/index.xml" rel="self" type="application/rss+xml" />
    <description>CodeCell</description>
    <generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Sat, 30 May 2026 00:00:00 +0000</lastBuildDate>
    <image>
      <url>https://antoine.weill-duflos.fr/media/icon_hu_d686267daab28486.png</url>
      <title>CodeCell</title>
      <link>https://antoine.weill-duflos.fr/en/tag/codecell/</link>
    </image>
    
    <item>
      <title>A Pocket Rolling Stone, Part 2: Driving the Ball with an H-Bridge on a CodeCell ESP32-C6</title>
      <link>https://antoine.weill-duflos.fr/en/post/esp32-rolling-stone-hbridge/</link>
      <pubDate>Sat, 30 May 2026 00:00:00 +0000</pubDate>
      <guid>https://antoine.weill-duflos.fr/en/post/esp32-rolling-stone-hbridge/</guid>
      <description>&lt;p&gt;















&lt;figure  id=&#34;figure-the-finished-h-bridge-unit-this-is-the-build-i-actually-assembled-and-keep-around-a-codecell-c6-drive-wrapped-up-small-with-a-usb-c-cable-for-power-and-flashing-the-audio-build-from-part-1-never-got-boxed-up-like-this-it-stayed-on-the-bench&#34;&gt;
  &lt;div class=&#34;d-flex justify-content-center&#34;&gt;
    &lt;div class=&#34;w-100&#34; &gt;&lt;img alt=&#34;A small black tape-wrapped rectangular block held in one hand, with a USB-C cable plugged into one end&#34; srcset=&#34;
               /en/post/esp32-rolling-stone-hbridge/featured_hu_55609ed5e5d778d1.webp 400w,
               /en/post/esp32-rolling-stone-hbridge/featured_hu_51bd6e9e4f3cc1cd.webp 760w,
               /en/post/esp32-rolling-stone-hbridge/featured_hu_1438624f51795477.webp 1200w&#34;
               src=&#34;https://antoine.weill-duflos.fr/en/post/esp32-rolling-stone-hbridge/featured_hu_55609ed5e5d778d1.webp&#34;
               width=&#34;573&#34;
               height=&#34;760&#34;
               loading=&#34;lazy&#34; data-zoomable /&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;figcaption&gt;
      The finished H-bridge unit. This is the build I actually assembled and keep around: a CodeCell C6 Drive wrapped up small, with a USB-C cable for power and flashing. The audio build from Part 1 never got boxed up like this, it stayed on the bench.
    &lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;In &lt;a href=&#34;../esp32-rolling-stone/&#34;&gt;Part 1&lt;/a&gt; I built a handheld device that lets you feel a virtual ball rolling inside a tube, based on &lt;a href=&#34;https://cim.mcgill.ca/~haptic/pub/HY-VH-EH-06.pdf&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Yao and Hayward&amp;rsquo;s 2006 length-perception experiment&lt;/a&gt;, and I rendered the ball as audio through an I2S amplifier driving a TITAN Haptics actuator. This is the other half of the project: the &lt;strong&gt;same ball, same physics, no amplifier&lt;/strong&gt;. Here the haptic output is a vibration motor driven directly by an &lt;strong&gt;H-bridge&lt;/strong&gt;. It is also the build I actually finished and keep around, because the all-in-one board packs everything into a smaller unit than the Feather plus amplifier.&lt;/p&gt;
&lt;p&gt;If you have not read Part 1, the short version is: an ESP32-C6 reads tilt from a BNO085 IMU, runs a 1 kHz physics simulation of a ball rolling and sliding in a tube (plus a 3D marble-box mode), and turns the ball&amp;rsquo;s motion into a vibration in real time. A single &lt;code&gt;#define HBRIDGE&lt;/code&gt; build flag in &lt;code&gt;platformio.ini&lt;/code&gt; switches between the two output stages, and the physics code is identical between them. So everything here is about the output.&lt;/p&gt;
&lt;h2 id=&#34;why-a-second-build-at-all&#34;&gt;Why a second build at all&lt;/h2&gt;
&lt;p&gt;The I2S build is great, but it carries an audio amplifier, and you do not actually need one to drive a haptic actuator. A very common, simpler approach is an &lt;strong&gt;H-bridge&lt;/strong&gt;: two half-bridges that let you push current through the actuator in either direction, with the average voltage set by PWM duty cycle. The TacHammer Drake is perfectly happy being driven this way; TITAN even lists H-bridge motor drivers among the recommended ways to run it.&lt;/p&gt;
&lt;p&gt;So the question for this build was: can I get the same convincing rolling-stone feel by driving the same Drake LFi straight from an H-bridge, with no audio path at all? Mostly, yes, and for our needs it makes a much simpler and more compact solution. With the right board there is no soldering required at all.&lt;/p&gt;
&lt;h2 id=&#34;the-hardware-codecell-drive-and-drivecell&#34;&gt;The hardware: CodeCell Drive and DriveCell&lt;/h2&gt;
&lt;p&gt;This build runs on a &lt;strong&gt;CodeCell ESP32-C6 Drive&lt;/strong&gt; board from &lt;a href=&#34;https://microbots.io&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Microbots&lt;/a&gt;. It is a tidy little board for exactly this kind of thing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ESP32-C6, same chip as the Feather build, so the physics is unchanged.&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;onboard BNO085&lt;/strong&gt; IMU, wired internally on I2C (SDA on IO8, SCL on IO9) and managed by the CodeCell library. No external sensor, no STEMMA cable.&lt;/li&gt;
&lt;li&gt;An onboard &lt;strong&gt;DriveCell&lt;/strong&gt; H-bridge, exposed in the library as &lt;code&gt;Drive1&lt;/code&gt; and &lt;code&gt;Drive2&lt;/code&gt;. I use &lt;code&gt;Drive1&lt;/code&gt; (IN1 on IO22, IN2 on IO21) as the haptic actuator and leave &lt;code&gt;Drive2&lt;/code&gt; spare.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One nice consequence of the onboard everything is that the build is physically smaller than the Feather plus amplifier plus separate IMU. For a handheld device that matters.&lt;/p&gt;
&lt;p&gt;















&lt;figure  id=&#34;figure-an-earlier-angle-before-i-taped-it-fully-shut-the-board-and-foam-padding-nested-in-the-3d-printed-shell-once-closed-it-becomes-the-small-black-block-in-the-photo-at-the-top&#34;&gt;
  &lt;div class=&#34;d-flex justify-content-center&#34;&gt;
    &lt;div class=&#34;w-100&#34; &gt;&lt;img alt=&#34;A 3D-printed black shell held in one hand with a strip of dark foam on top and kapton tape, the board and padding nested inside before the case was closed&#34; srcset=&#34;
               /en/post/esp32-rolling-stone-hbridge/assembled-internals_hu_7d6dd65a917418e.webp 400w,
               /en/post/esp32-rolling-stone-hbridge/assembled-internals_hu_1a2061d719fc69c7.webp 760w,
               /en/post/esp32-rolling-stone-hbridge/assembled-internals_hu_2dbb5aca3cbb1831.webp 1200w&#34;
               src=&#34;https://antoine.weill-duflos.fr/en/post/esp32-rolling-stone-hbridge/assembled-internals_hu_7d6dd65a917418e.webp&#34;
               width=&#34;573&#34;
               height=&#34;760&#34;
               loading=&#34;lazy&#34; data-zoomable /&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;figcaption&gt;
      An earlier angle, before I taped it fully shut: the board and foam padding nested in the 3D-printed shell. Once closed it becomes the small black block in the photo at the top.
    &lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id=&#34;bill-of-materials-h-bridge-build&#34;&gt;Bill of materials (H-bridge build)&lt;/h3&gt;
&lt;p&gt;The appeal of this build is how short the list is, and it is exactly why this is the version I finished. The CodeCell C6 Drive already has the IMU and the H-bridge on board.&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Part&lt;/th&gt;
          &lt;th&gt;Full reference&lt;/th&gt;
          &lt;th&gt;Where to buy&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;All-in-one board&lt;/td&gt;
          &lt;td&gt;Microbots CodeCell C6 Drive. Module ESP32-C6-MINI-1-H8 (8 MB flash), with an onboard BNO085 IMU, a VCNL4040 light/proximity sensor, and a dual H-bridge driver&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://microbots.io/products/codecell-c6-drive&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;microbots.io/products/codecell-c6-drive&lt;/a&gt;, €32.99&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Haptic actuator&lt;/td&gt;
          &lt;td&gt;A TITAN Haptics TacHammer Drake LFi, driven from the H-bridge&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://titanhaptics.com/drake/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;titanhaptics.com/drake&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Power&lt;/td&gt;
          &lt;td&gt;3.7 V LiPo with JST connector, plus a USB-C cable&lt;/td&gt;
          &lt;td&gt;any LiPo distributor&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Enclosure&lt;/td&gt;
          &lt;td&gt;3D-printed shell, acoustic foam, kapton tape&lt;/td&gt;
          &lt;td&gt;self-sourced&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That is the whole bill. No separate amplifier, no external IMU, no driver board. For a cheap, self-contained unit you can actually close up and pocket, that is exactly the point.&lt;/p&gt;
&lt;h2 id=&#34;generating-the-signal-with-ledc-pwm&#34;&gt;Generating the signal with LEDC PWM&lt;/h2&gt;
&lt;p&gt;There is no DMA audio stream here. Instead I drive the two H-bridge input pins with the ESP32&amp;rsquo;s &lt;strong&gt;LEDC&lt;/strong&gt; peripheral: a 20 kHz PWM carrier at 8-bit duty resolution. 20 kHz is above hearing, so the carrier itself is silent, and the actuator only responds to the envelope I impose on the duty cycle.&lt;/p&gt;
&lt;p&gt;The clever bit from Part 1 carries straight over. The rolling vibration is the same &lt;strong&gt;position-indexed wavetable&lt;/strong&gt;, one period of a negative sine arch, indexed by ball position in millimetres. Because it is indexed by position rather than time, the perceived pitch rises with ball speed automatically, exactly as in the audio build, even though here I am only updating the duty cycle once per physics tick at 1 kHz rather than streaming 22050 samples a second. The 1 kHz update is fast enough to carry the rolling texture and the impact transients that the skin cares about.&lt;/p&gt;
&lt;h3 id=&#34;unipolar-routing-rumble-on-one-pin-impact-on-the-other&#34;&gt;Unipolar routing: rumble on one pin, impact on the other&lt;/h3&gt;
&lt;p&gt;The audio build had it easy: an I2S sample is signed, so a bipolar waveform swinging positive and negative is the natural thing. PWM duty cycle is unsigned. You cannot write a negative duty.&lt;/p&gt;
&lt;p&gt;The trick I settled on is to &lt;strong&gt;split the signal by sign across the two H-bridge pins&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The rolling rumble uses the negative-arch wavetable, which is always less than or equal to zero. I route that to the &lt;strong&gt;minus&lt;/strong&gt; pin (IN2): duty proportional to the magnitude.&lt;/li&gt;
&lt;li&gt;The impact pulse is a short positive burst. I route that to the &lt;strong&gt;plus&lt;/strong&gt; pin (IN1).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On any given tick, exactly one pin is active and the other is held at zero duty. So the actuator gets a clean rumble drive in one polarity and a sharp impact kick in the other, all from a single signed signal computed exactly the way the audio build computes it. The signed value is the shared language between the two builds, and each output stage just renders it the way its hardware wants.&lt;/p&gt;
&lt;p&gt;The impact, as in Part 1, scales both amplitude and duration with impact speed: from a 2 ms tick for a gentle touch up to a 9 ms thump for a hard wall hit.&lt;/p&gt;
&lt;h2 id=&#34;embedded-gotchas-worth-writing-down&#34;&gt;Embedded gotchas worth writing down&lt;/h2&gt;
&lt;p&gt;A few things bit me on this board and are worth recording, because they are the kind of thing you lose an evening to.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GPIO hold across deep sleep.&lt;/strong&gt; The CodeCell can deep-sleep, and the ESP32 can latch GPIO states across sleep with a hold feature. If a pin was held from a previous power cycle, configuring it again does nothing until you explicitly release the hold. So at startup I call &lt;code&gt;gpio_hold_dis&lt;/code&gt; on the H-bridge pins, the sensor power pin, and the I2C pins before configuring them. Without that, the first boot after a sleep can come up with a dead actuator or a dead sensor bus, intermittently, which is the worst kind of bug.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sensor and LED power enables.&lt;/strong&gt; On this board the sensor and the status LED sit behind enable pins (IO18 and IO20). They have to be driven high early in &lt;code&gt;setup()&lt;/code&gt;, before the I2C scan, or the BNO085 simply is not there.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Single-core watchdog.&lt;/strong&gt; Same as the Feather build: the C6 is single core, my &lt;code&gt;loop()&lt;/code&gt; parks forever, so I delete the Arduino loop task from the task watchdog or the board resets every ten seconds.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Avoid the JTAG pins.&lt;/strong&gt; GPIO 4 through 7 are JTAG on the ESP32-C6. Easy to grab one by accident for an output and then wonder why debugging is weird. I keep them clear on both boards.&lt;/p&gt;
&lt;h2 id=&#34;how-it-compares-to-the-i2s-build&#34;&gt;How it compares to the I2S build&lt;/h2&gt;
&lt;p&gt;Side by side, the two builds feel different in instructive ways.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bandwidth.&lt;/strong&gt; The audio build runs the output at 22050 Hz and can render fine texture and crisp transients. The H-bridge build updates at 1 kHz. For the rolling rumble and the impacts that is plenty, and honestly most of what your skin resolves in this band comes through fine. The audio build still has the edge on the sharpest, highest-frequency detail.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simplicity and size.&lt;/strong&gt; The H-bridge build is smaller and has fewer parts: no amplifier, onboard IMU, onboard H-bridge. It is the build I would reach for to make a cheap, self-contained unit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The signal model is shared.&lt;/strong&gt; This is the part I like most. The same position-indexed wavetable, the same impact model, the same signed envelope drive both. The audio build streams it as samples, the H-bridge build splits it across two pins. The ball is the same ball.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The companion app does not care which build is on the other end of the wire. It speaks the same serial and BLE protocol either way, so I can watch and tune both:&lt;/p&gt;
&lt;p&gt;















&lt;figure  id=&#34;figure-the-same-companion-app-from-part-1-here-watching-the-rolling-physics-the-protocol-is-identical-between-the-two-builds-so-the-tooling-is-shared&#34;&gt;
  &lt;div class=&#34;d-flex justify-content-center&#34;&gt;
    &lt;div class=&#34;w-100&#34; &gt;&lt;img alt=&#34;Screenshot of the companion app in rolling mode: a dark UI with a grey tube tilted down to the left with an orange ball on it and a red wall flash, position and velocity history traces on the right, and a telemetry panel showing mode ROLLING, position, velocity, and a 1000 Hz physics rate&#34; srcset=&#34;
               /en/post/esp32-rolling-stone-hbridge/viz-rolling_hu_13e405a38069a73f.webp 400w,
               /en/post/esp32-rolling-stone-hbridge/viz-rolling_hu_ca995601256eb4c5.webp 760w,
               /en/post/esp32-rolling-stone-hbridge/viz-rolling_hu_d354f1c1785d795c.webp 1200w&#34;
               src=&#34;https://antoine.weill-duflos.fr/en/post/esp32-rolling-stone-hbridge/viz-rolling_hu_13e405a38069a73f.webp&#34;
               width=&#34;760&#34;
               height=&#34;485&#34;
               loading=&#34;lazy&#34; data-zoomable /&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;figcaption&gt;
      The same companion app from Part 1, here watching the rolling physics. The protocol is identical between the two builds, so the tooling is shared.
    &lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id=&#34;where-this-is-going&#34;&gt;Where this is going&lt;/h2&gt;
&lt;p&gt;Both builds are working and both produce a convincing rolling-stone feel, which still slightly surprises me given there is nothing moving inside. The project is ongoing: the enclosure is a prototype, the device stays handheld (it is small), and the next steps are a longer tube to make the length illusion more convincing, an onboard battery to cut the cord, and a small length-perception test in the spirit of the original paper.&lt;/p&gt;
&lt;p&gt;If you want to build one, the firmware for both targets is on GitHub at &lt;a href=&#34;https://github.com/Leicas/esp32-ball&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;github.com/Leicas/esp32-ball&lt;/a&gt;, with the two PlatformIO environments (&lt;code&gt;feather&lt;/code&gt; for the I2S build, &lt;code&gt;hbridge&lt;/code&gt; for this one) and the Python companion app. Start with &lt;a href=&#34;../esp32-rolling-stone/&#34;&gt;Part 1&lt;/a&gt; for the physics and the audio path, then flip the build flag and feel the difference.&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
