<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.cranberrygrape.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.cranberrygrape.com/" rel="alternate" type="text/html" /><updated>2025-11-21T10:51:18-05:00</updated><id>https://www.cranberrygrape.com/feed.xml</id><title type="html">Cranberry Grape | Cosmic Bee</title><subtitle>Website / news feed associated with the workings of Cosmic Bee / Tim Lovett. Machine learning, hobbyist PCB design, rust programming, 3D printing, and embedded programming.</subtitle><author><name>Cosmic Bee</name></author><entry><title type="html">Glowbug Mini - Mini USB PD based WLED controller</title><link href="https://www.cranberrygrape.com/pcb/glowbug-mini/" rel="alternate" type="text/html" title="Glowbug Mini - Mini USB PD based WLED controller" /><published>2025-06-24T14:01:00-04:00</published><updated>2025-06-24T14:01:00-04:00</updated><id>https://www.cranberrygrape.com/pcb/glowbug-mini</id><content type="html" xml:base="https://www.cranberrygrape.com/pcb/glowbug-mini/"><![CDATA[<p>Glowbug Mini is a new WLED control board with USB power delivery and CH224K powered 5V 2A output</p>

<p><a href="https://www.pcbway.com/project/shareproject/Glowbug_Mini_77f439ee.html">Project Link</a></p>

<!-- Courtesy of embedresponsively.com -->

<div class="responsive-video-container">
    <iframe src="https://www.youtube-nocookie.com/embed/euuNeTG7BHU" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
  </div>

<p>Like my Glowbug Project this PCB is designed to use <a href="https://kno.wled.ge/">WLED</a> a firmware that works on many Espressif microcontrollers to handle LED effects and control. WLED is fairly extensible with various usermods to handle things like audioreactive light displays, OLED screen output, and so on. This particular PCB is setup to be small form factor but still have the main elements of a fully featured WLED PCB like a fuse, large capacitor, relay, and in addition uses a WCH 224K to allow for 5V 2A power delivery via the attached USB input.
I’ve <a href="https://www.cranberrygrape.com/breakout%20boards/board%20overviews/ch224k-usb-power-delivery-overview/">previously discussed</a> the CH224K so opted to go that direction.</p>

<p>This smaller board has the following features:</p>
<ul>
  <li>USB power delivery power</li>
  <li>Uses a relay to keep power consumption in check</li>
  <li>Uses a fuse to prevent issues</li>
  <li>Capacitor for the LED line</li>
  <li>3 or 4 wire support for LED strips</li>
  <li>Level shifting the data and clock lines</li>
  <li>IR receiver</li>
</ul>

<p>In this article I’ll be discussing the schematic for my board, <a href="https://www.pcbway.com/project/shareproject/Glowbug_Mini_77f439ee.html">linking the project</a> for the associated board, and demonstrating it in practice.</p>

<p class="notice--danger"><strong>Careful:</strong> Use this board at your own peril. Things seem to be working for me but further testing is warranted before anyone uses it in a production setting.</p>

<h2 id="project-sponsorship">Project Sponsorship</h2>

<p><img src="https://www.cranberrygrape.com/assets/images/sponsors/PCBWay.webp" alt="PCBWay" style="padding: 20px; background-color: #FFF;" /></p>

<p>At this time I want to thank PCBWay for sponsoring this project by providing the PCBs. I’m grateful they put faith in my project and gave me this opportunity. Their <a href="https://www.pcbway.com/">website</a> can be accessed for more details about their product line. I like how easy it was to go from my gerber to the shipped product and the boards I received seem of high quality. Their <a href="https://www.pcbway.com/capabilities.html">capabilities page</a> has a lot information about what they can help you with. PCBWay really does make it easy to get started, build professional PCBs for your project, and see your ideas come to fruition. Love it.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/pcb/glowbug-mini/glowbug_mini_pcb_front.webp" alt="Glowbug Mini PCB Front" style="padding: 20px; background-color: #FFF;" /></p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/pcb/glowbug-mini/glowbug_mini_pcb_back.webp" alt="Glowbug Mini PCB Back" style="padding: 20px; background-color: #FFF;" /></p>

<h2 id="schematic-rundown">Schematic rundown</h2>

<p>The entire schematic can be referenced from the EasyEDA project directly or via the <a href="https://www.cranberrygrape.com/assets/images/projects/pcb/glowbug-mini/Schematic_Glowbug-Mini.pdf">associated PDF linked here</a>.</p>

<h3 id="power">Power</h3>

<p>This PCB includes a WCH CH224K used for USB PD. As mentioned previously I was grateful to have used this IC a few times prior with WeAct boards I grabbed on aliexpress so I had a decent idea of its capabilities. I took a look over at the <a href="https://www.wch-ic.com/downloads/CH224DS1_PDF.html">WCH documentation for CH224K</a> and from there adjusted my schematic.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/pcb/glowbug-pd-1.1/ch224-power.webp" alt="CH224K Power" style="padding: 20px; background-color: #FFF;" /></p>

<p>This is a fragment of the WCH CH224K documentation linked above. As you can see in this configuration mode one can adjust the resistance of the given pins to request different voltage levels.</p>

<h3 id="wled-configuration">WLED configuration</h3>

<p>In WLED you can further configure the device. For the ESP32S3 I utilized platform.io with the wled firmware and selected the 4M qspi environment.</p>

<p>Here I’ve selected several GPIO for the various elements such as the IR receiver and relay. These correspond to the values printed on the PCB itself for reference and ease of access. I used the pinout of the Xiao ESP32S3 to find the associated GPIO to use.</p>]]></content><author><name>Cosmic Bee</name></author><category term="pcb" /><category term="Project" /><category term="WLED" /><category term="ESP32S3" /><category term="WS2812" /><category term="Lights" /><category term="PCBWay" /><summary type="html"><![CDATA[Small Size PCB design with a WLED controller board]]></summary></entry><entry><title type="html">Freeflow Mouse</title><link href="https://www.cranberrygrape.com/mini-projects/freeflow-mouse/" rel="alternate" type="text/html" title="Freeflow Mouse" /><published>2024-09-16T11:30:00-04:00</published><updated>2024-09-16T11:30:00-04:00</updated><id>https://www.cranberrygrape.com/mini-projects/freeflow-mouse</id><content type="html" xml:base="https://www.cranberrygrape.com/mini-projects/freeflow-mouse/"><![CDATA[<p>For this project I put together a custom mouse. I did as part of the <a href="https://www.hackster.io/contests/buildtogether2">Build2gether 2.0 — Inclusive Innovation Challenge</a> hosted on <a href="https://www.hackster.io/">Hackster.io</a>.</p>

<!-- Courtesy of embedresponsively.com -->

<div class="responsive-video-container">
    <iframe src="https://www.youtube-nocookie.com/embed/cysmEqcd9EI" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
  </div>

<h2 id="sponsor">Sponsor</h2>

<p>Thank you JLC3DP for sponsoring the resin print I used for this project! They also sponsored the large white PCB that connected with the core board. You can visit their site <a href="https://jlc3dp.com/?from=timlovett">here</a>. I really like the resin print in the transparent style (they sand and use oil for it) as it felt smooth to the touch and it was cool that you can see right through it (I utilized an LED on the board so the transparent cover allowed me to see the LED as it lit up which was great). I highly recommend their services as the 8001 resin type really looks great in this way!</p>

<h2 id="project-links">Project Links</h2>

<ul>
  <li><a href="https://www.hackster.io/timo614/finger-freeflow-mouse-f71a30">You can see my full article here on the Hackster.io site</a></li>
  <li><a href="https://www.pcbway.com/project/shareproject/Freeflow_Mouse_Core_Board_1f81019f.html">Mouse core board</a></li>
  <li><a href="https://www.pcbway.com/project/shareproject/Mouse_Touch_Board_4ad88205.html">Larger board that attaches with the core board</a></li>
</ul>

<h1 id="some-background">Some background</h1>

<p>The contest is an inclusivity based one where entry participants are able to get a “Superbox” of electronics hardware to utilize for various ideas to help people with mobility and vision issues. For this project I was addressing the issue of mobility troubles inside the home. I had taken part in the previous Build2gether challenge and realized that mice generally aren’t designed with disabilities in mind – one of the previous contest masters had discussed the discomfort of normal mice so I opted to use this as my focus.</p>

<h1 id="my-mouse">My mouse</h1>

<p>My mouse focused on trying to reduce the amount of movement that would be needed while also making the buttons easy to use with a simple touch. To accomplish this I utilized elastic strings on the perimeter to pull the mouse back to the default position between pushes. When the user puts their thumb on the lowest touch pad I allow the mouse to be moved. In this way the mouse can be reset simply by lifting your thumb and allowing it to naturally pull back into position so one can move around very easily by timing their thumb position as they need to move. Given a mouse allows more control than a thumbstick I believe this allows for maximum control for someone with limited movement range.</p>]]></content><author><name>Cosmic Bee</name></author><category term="mini-projects" /><category term="Project" /><category term="PCB" /><category term="JLC3DP" /><category term="JLCPCB" /><category term="PCBWay" /><category term="Seeed Studio" /><summary type="html"><![CDATA[Custom mouse build]]></summary></entry><entry><title type="html">Foot Pedal Enhanced</title><link href="https://www.cranberrygrape.com/mini-projects/foot-pedal-enhanced/" rel="alternate" type="text/html" title="Foot Pedal Enhanced" /><published>2024-07-13T12:30:00-04:00</published><updated>2024-07-13T12:30:00-04:00</updated><id>https://www.cranberrygrape.com/mini-projects/foot-pedal-enhanced</id><content type="html" xml:base="https://www.cranberrygrape.com/mini-projects/foot-pedal-enhanced/"><![CDATA[<p>For this project I’ve modified a <a href="https://s.click.aliexpress.com/e/_Dma95rN">pcsensor FS2020U1 USB Foot Switch Control device</a> from aliexpress a bit back (from a different seller) to work with my microcontroller for use in various projects.</p>

<!-- Courtesy of embedresponsively.com -->

<div class="responsive-video-container">
    <iframe src="https://www.youtube-nocookie.com/embed/37s4px8k248" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
  </div>

<p>Printables link: <a href="https://www.printables.com/model/941756-foot-pedal-adapter">https://www.printables.com/model/941756-foot-pedal-adapter</a></p>

<p>Additional Project items (affiliate links):</p>
<ul>
  <li><a href="https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Xiao SAMD21</a></li>
  <li><a href="https://s.click.aliexpress.com/e/_Dma95rN">pcsensor FS2020U1 USB Foot Switch Control device</a></li>
  <li><a href="https://amzn.to/4cDorwm">22 Gauge 5 Conductor Electrical Wire</a></li>
</ul>

<h2 id="background">Background</h2>

<p>Awhile back I purchased this foot pedal from Aliexpress as a way to add an additional control device to my set of random devices. A big driving factor was that I know it’ll make for a great component to use with future videos so I wanted to do the legwork to get this piece in place ahead of a need.</p>

<h2 id="initial-investigations">Initial investigations</h2>

<p>I removed the screws from the device’s bottom and began investigating the layout of the circuit board. Upon opening it was clear the circuit is a simple IR emitter and receiver (versus a mechanical switch). The mechanism for signaling in this case was to break that emitter’s line of sight to the receiver with a piece of plastic that falls into the cutout between them as the spring is compressed down by a foot.</p>

<p>My initial investigations of the board left me fairly puzzled. I could see each of the boards was the same. I could also see the power lines were connected.</p>

<!-- Courtesy of embedresponsively.com -->

<div class="responsive-video-container">
    <iframe src="https://www.youtube-nocookie.com/embed/-hLTz7C-poc" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
  </div>

<p>Initially I was going to use the existing wires coming from the board but realized it powered the sensor by doing a round-robin power setup powering one signal pin at a time while checking the state of the others (using a reverse biased diode to power the board from the main board). This worked well during my testing (setting one pin as power and checking the state of the pedals) but wasn’t feasible for the microcontroller to source that much current from the signal pins. This falstad circuit shows power from the <a href="https://www.falstad.com/circuit/circuitjs.html?ctz=CQAgjCAMB0l3BWcMBMcUHYMGZIA4UA2ATmIxAUgoqoQFMBaMMAKAC9wAWTkFPPXvl79eIACZ0AZgEMArgBsALgzZ0AdnQBOLADIgGeKnwEGqYPlHAgZ8gM51qkFgHdBA425Cc4UFgGNPbzNCKiDLMFh4KOi4MEZyTmhOFEwEDGJsQkIwQjwM8EiYoriGDBYAcy4eTmwUKq9DSydNerCwbhBcHjMol0CfNAEauqccurBiOuHW2tEJGQVleToxKyp12FZXduqfHa9Z5sDZianDkFynbY6Pfa7fV0HhdxEPK68BkU5Gt77sTOenQB9yOYWm2Dw1XOlz+ALC-0IBxGLDGnUhSKBiOmdXmciUDGWqwg6ygmz6YJ8YMOLBaCI+VDpIOQ8AqmIxdO+JKcAQpDIQUx8ZkKRWiJQSSRSGDSGSyOTyPAiIuK8XJnyGlMF7DZHjCHhxUjxylUGm0AFltSI6eYBEZoAg+nrXiJiIj3h4XQ0qB73pyQB7fb8Wo6BPc3synLZOpAeB46WGIDZ7DTwCF6a1BaSoqRsznc6Rk9h+WmORmYFm8xX8y0PbdzICenAWJHmHVbjcRGZrNI7HQ+vs2zGEK6+oQY87EYH9KO06Y0xty5W8039C3AbP412e8mmBYPEw6-GWa5px7p79XDuBKeeN6+pe19a-cPzf2h-VH7b7Rx1yImBPXuIBqLCo6haHeOQPlimoBH+M7TmEQpKjEYpeBKqTpJk2QkEIipIVEYqsrO0xEY06zbo0YSzkyYC9BeEGUfR1KogwszERRsz6gs+KEmsTRkheFE+ER1ItLBxGsdChh3j8v6NCC4H-gIZ4diON6ItO07vBpY5Kd0DoiMpV7DkAA">left signal wire</a>, <a href="https://www.falstad.com/circuit/circuitjs.html?ctz=CQAgjCAMB0l3BWcMBMcUHYMGZIA4UA2ATmIxAUgoqoQFMBaMMAKAC9wAWTkFPPXvl79eIACZ0AZgEMArgBsALgzZ0AdnQBOLADJceKBIX3gwKKOBAz5AZzrVILAO6CBfN0M5woLAMauQLyowQiogizBYeGiYuDBGck5oThQwfAI+DDxITkJzSNjCuISWAHMTTmx87kDsi0dNCu8wGtweYOjnAPC0AUrzRxD84nN+iqrRCRkFZXk6MUsqJdhWFxaecPXAiYbuibAR7YGQQmyurfcTNp8XXuE3EUvHFx6RTjqnruxsY0vv42uu3CY2weA2OxOZxc-0C3hhY0GeRAoPB5nhE3MUzkSgYcwWECWUBWXWB3mBOxYjXhcJ+yJyEU65XRaNp70Jjn8pKo2AQo28wSiRRi8QYiWSqXSKEy2Vy2HAgqF8BFGBJ3kuXJ8HBh6rVj3EUmxylUGm0AFlkbS-rSzAIqChoAgupdnSJiMZnvcQG7alRvR62V7jAHPo0XQJrk9kHAWDY6QYRNqRMErNJbHRKeBQrDgjVwstoqRC0Xi6QMzy+dzWfyiQWS3XS41vZdmOZI2lo7GW56LknLNY7Ocas2h0Ybid4wIm0mMwxCBtvAw6nma-B6-WYyAmGZPYu7b2qP3040mHwd122-AunPA+PPR6T5PjNe-V0H2fTy-zbud3UbRZ7Y6HDfpcTC-HqWIzCo6haK+IRnkG-J+JucHhLO86EgUirRMqgTimk-BSngWQ5CQ8pYdhJTlN+YzUXUSwzkuC51ICUbRi4oHZshQYUkMm4TDRS4Yvq0w4niiz1MS7GMVQ1EUseKETAw-EQqc94fCI36ArBYECNenwuM+T48NeHrXnpIgmU6FkTjejhAA">from the middle signal wire</a> and <a href="https://www.falstad.com/circuit/circuitjs.html?ctz=CQAgjCAMB0l3BWcMBMcUHYMGZIA4UA2ATmIxAUgoqoQFMBaMMAKAC9wAWTkFPPXvl79eIACZ0AZgEMArgBsALgzZ0AdnQBOLADIhs2QsIEGjYPlHAgZ8gM51qkFgHdBAvu6Gc4UFgGM3EG8qMEIqYMswWHgY2LgwRnJOaE4wYk4MMEgwTOJsMggouOL4xJYAcy4eTmwUKqC8KiaWTXqIsG59SB4QmJdAiLQBGrqnULq0upG22tEJGQVleToxKyaoWFZXDuqfHaDZp1ahg4niKcOQQkb+-Y963B7+k-vXkSdXQZFORuNfV1Mf0Bj18rQi02weGql2uH30hiCPkB0zGhDqkOh6IR0zq8zkSgYy1WEHWMGy-XBPnBhxa8KMEWB3UifUqyNmyN+zQClKo2AQUx8IWiJViCQYSRSaQyWRyaXymXAwpF8DFGApPnuPN8HEBmo1IlxUnxylUGm0AFl6igEGZOsxRrxoAhngbXQJiEY4fcPQ0qD64T8-fTfvcjn97iDQ8g4CxbF0eBGEVGIDZ7LTQuE9p0Ik0laR8wXC6RaXyBbzsYKNjEizX87Sffd7X9ejG403G50o1RU3Rbp2RHcbf8rgmRA33rSGIRdlQGL8c1X4LXa7GQExzH851Qu9ZpHZe60mBZ7uu6sn4P1pyAfVfQ-0j+6jFf-ffzO4RA-r56WJat5vfm+lgoE67BriGH6hOG4hGosKjqFor5GCekE5v4a4oT4U4zpESrKtkiRBJK6SZNkuTyoUuHKqqFRgeEsx-oGlhhgxmG-CCLYxq4TD0phKE0uMa6zNMDGzIaCwEkSaxMZs97zqxdGjJOfF1AwQkwjcXHgQIf7sYhfy3hOrjPk+PBXnCV4GQIZkulZo6Pr4QA">from the right signal wire</a> illustrates a similar circuit setup as the one used by the controller with the 4 wire approach. One of the pins as a power source with the other two acting as an open collector. This approach does work as shown above but it could present a problem for a microcontroller without additional components as the pins wouldn’t be able to source enough current without use of a mosfet for each signal line (albeit one could just use two pins for power).</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/seeed/foot-pedal-enhanced/foot-pedal-open.webp" alt="Foot Pedal With Enclosure Open" style="padding: 20px; background-color: #FFF;" /></p>

<p>Luckily, the main board still surfaced the power pin though (each of the side boards is a duplicate of the main) so I realized I could easily replace the wire with a <a href="https://amzn.to/4cDorwm">5 wire one I grabbed from Amazon</a> and be able to use this with my projects going forward.</p>

<h2 id="wire-strain-relief-component">Wire strain relief component</h2>

<p>The original setup used an injection molded piece around the 4 wire cable. This helped to provide strain relief for the wire keeping it from being dislodged from the circuit board. This also unfortunately meant the existing piece would need to be replaced completely. I measured the existing part, adjusted the design slightly, and printed a component.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/seeed/foot-pedal-enhanced/foot-pedal-existing-part.webp" alt="Foot Pedal Existing Part" style="padding: 20px; background-color: #FFF;" /></p>

<p>My original design actually didn’t work here but involved using a PG-7 cable gland for preventing cable movement. The problem related to the top component of the foot pedal as it has a plastic piece which comes rather low. The PG-7, even at the bottom of the replacement piece, just was too high in the piece with its backing nut so it couldn’t be inserted without modifying the pedal shell which I did not want to do here.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/seeed/foot-pedal-enhanced/foot-pedal-replacement-part-original.webp" alt="Foot Pedal Replacement" style="padding: 20px; background-color: #FFF;" /></p>

<p>As a result I <a href="https://www.printables.com/model/941756-foot-pedal-adapter">designed a replacement piece</a> that was much simpler and only had the wire going through versus any additional hardware. To aid with wire strain I added holes for a zip tie which can be used to keep the wire from moving.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/seeed/foot-pedal-enhanced/foot-pedal-replacement-part-complete.webp" alt="Foot Pedal Replacement Complete" style="padding: 20px; background-color: #FFF;" /></p>

<h2 id="wire-attachment">Wire attachment</h2>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/seeed/foot-pedal-enhanced/tinned-wires.webp" alt="Tinned Wires" style="padding: 20px; background-color: #FFF;" /></p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/seeed/foot-pedal-enhanced/dupont-connectors.webp" alt="Dupont Connectors Attached" style="padding: 20px; background-color: #FFF;" /></p>

<p>I tinned and soldered the wires to the main board, used a zip tie, and crimped the stripped wires on the other end of the board for use with a dupont connector.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/seeed/foot-pedal-enhanced/soldered-wires-with-zip-tie-strain-relief.webp" alt="Soldered Wires with Zip Tie Strain Relief" style="padding: 20px; background-color: #FFF;" /></p>

<p>At this point the device is ready for use with a microcontroller.</p>

<h2 id="microcontroller-use">Microcontroller use</h2>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/seeed/foot-pedal-enhanced/xiao-samd21-connected.webp" alt="Microcontroller Connected" style="padding: 20px; background-color: #FFF;" /></p>

<p>Now with the 5 wires soldered and with dupont connectors on the other side it’s easy to get running with a sketch. I connected the red wire to 3V3, black to GND, and used D0-D2 as signal wires.</p>

<p>The code for the logic used in my example is rather simple as well:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"Keyboard.h"</span><span class="cp">
</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">leftPin</span> <span class="o">=</span> <span class="n">D0</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">middlePin</span> <span class="o">=</span> <span class="n">D1</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">rightPin</span> <span class="o">=</span> <span class="n">D2</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
  <span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">9600</span><span class="p">);</span>
  
  <span class="n">pinMode</span><span class="p">(</span><span class="n">leftPin</span><span class="p">,</span> <span class="n">INPUT_PULLUP</span><span class="p">);</span>
  <span class="n">pinMode</span><span class="p">(</span><span class="n">middlePin</span><span class="p">,</span> <span class="n">INPUT_PULLUP</span><span class="p">);</span>
  <span class="n">pinMode</span><span class="p">(</span><span class="n">rightPin</span><span class="p">,</span> <span class="n">INPUT_PULLUP</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">loop</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">digitalRead</span><span class="p">(</span><span class="n">leftPin</span><span class="p">)</span> <span class="o">==</span> <span class="n">LOW</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">Keyboard</span><span class="p">.</span><span class="n">press</span><span class="p">(</span><span class="sc">'a'</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="n">Keyboard</span><span class="p">.</span><span class="n">release</span><span class="p">(</span><span class="sc">'a'</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">if</span> <span class="p">(</span><span class="n">digitalRead</span><span class="p">(</span><span class="n">middlePin</span><span class="p">)</span> <span class="o">==</span> <span class="n">LOW</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">Keyboard</span><span class="p">.</span><span class="n">press</span><span class="p">(</span><span class="sc">'w'</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="n">Keyboard</span><span class="p">.</span><span class="n">release</span><span class="p">(</span><span class="sc">'w'</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">if</span> <span class="p">(</span> <span class="n">digitalRead</span><span class="p">(</span><span class="n">rightPin</span><span class="p">)</span> <span class="o">==</span> <span class="n">LOW</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">Keyboard</span><span class="p">.</span><span class="n">press</span><span class="p">(</span><span class="sc">'d'</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="n">Keyboard</span><span class="p">.</span><span class="n">release</span><span class="p">(</span><span class="sc">'d'</span><span class="p">);</span>
  <span class="p">}</span>
  
  <span class="n">delay</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The pins are set to inputs with a pullup. The loop logic simply checks if the pins are reading <code class="language-plaintext highlighter-rouge">LOW</code> indicating that the pedal has been pressed down causing the blocker to interfere with the emitter / receiver setup. Once brought <code class="language-plaintext highlighter-rouge">LOW</code> the logic emulates the pressing of the <code class="language-plaintext highlighter-rouge">awd</code> keys (used with <code class="language-plaintext highlighter-rouge">awsd</code> control schemes to move forward and turn or strafe).</p>

<h2 id="testing">Testing</h2>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/seeed/foot-pedal-enhanced/foot-pedal-low-resolution.gif" alt="Foot pedal playing game" style="padding: 20px; background-color: #FFF;" /></p>

<p>With the circuit in place I opted to test the functionality with the game <a href="https://www.gog.com/en/game/rebel_galaxy">Rebel Galaxy</a>. In this game the <code class="language-plaintext highlighter-rouge">a</code> and <code class="language-plaintext highlighter-rouge">d</code> correspond with turning while the <code class="language-plaintext highlighter-rouge">w</code> is used for the booster throttle.</p>

<h2 id="concluding-thoughts">Concluding thoughts</h2>

<p>This was a neat project that puzzled me for a bit. I definitely felt accomplished figuring out the unique round robin power used here with the initial circuit.</p>

<p>I’d like to design my own foot pedal in the future using a mechanical switch instead of the line break used here. Not sure if I’d reuse the enclosure here or 3D print my own but worth a revisit some day. For now this seems great and I’ll be using this for some projects I have planned.</p>]]></content><author><name>Cosmic Bee</name></author><category term="mini-projects" /><category term="Project" /><category term="Electronic Reuse" /><category term="Seeed Studio" /><summary type="html"><![CDATA[Enhancing a foot pedal for microcontroller use]]></summary></entry><entry><title type="html">Level Up Board: Xiao Level Shifting</title><link href="https://www.cranberrygrape.com/mini-projects/level-up-board/" rel="alternate" type="text/html" title="Level Up Board: Xiao Level Shifting" /><published>2024-07-12T05:30:00-04:00</published><updated>2024-07-12T05:30:00-04:00</updated><id>https://www.cranberrygrape.com/mini-projects/level-up-board</id><content type="html" xml:base="https://www.cranberrygrape.com/mini-projects/level-up-board/"><![CDATA[<p>In this project I’ll discuss my Level Up Board for level shifting Xiao form factor projects. This project is a board I designed to replace my earlier <a href="https://www.cranberrygrape.com/mini-projects/grove-level-shifter/">inline DIY level shifter</a> I’ve used for various projects.</p>

<p>Link to board project: <a href="https://www.pcbway.com/project/shareproject/Level_Up_Board_6eb22313.html">pcbway</a></p>

<!-- Courtesy of embedresponsively.com -->

<div class="responsive-video-container">
    <iframe src="https://www.youtube-nocookie.com/embed/0NuqCE4w80U" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
  </div>

<h2 id="project-sponsorship">Project Sponsorship</h2>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/pcb/level-up-xiao-1.0/pcbway.webp" alt="PCBWay Sponsor" style="padding: 20px; background-color: #FFF;" /></p>

<p>At this time I want to thank PCBWay for sponsoring this project by providing the PCBs. I’m grateful they put faith in my project and gave me this opportunity. Their <a href="https://www.pcbway.com/">website</a> can be accessed for more details about their product line. I like how easy it was to go from my gerber to the shipped product and the boards I received seem of high quality. Their <a href="https://www.pcbway.com/capabilities.html">capabilities page</a> has a lot information about what they can help you with. PCBWay really does make it easy to get started, build professional PCBs for your project, and see your ideas come to fruition. Love it.</p>

<h2 id="why-make-this-board">Why make this board?</h2>

<p>I have three uses for this board:</p>
<ul>
  <li>If I want to use UART serial communication with a 5V board I need to level shift for the signals and power to avoid hurting the Xiao with its 3V3 pins</li>
  <li>There are some 5V only i2c devices such as the Seeed Vision AI sensor v2 (you can attach the Xiao directly to the board to get around this but my projects usually use multiple sensors so I prefer attaching it via a cable)</li>
  <li>There are some actuators that require 5V like the Seeed Water Atomization module</li>
</ul>

<p>In the past I took care of this by <a href="https://www.cranberrygrape.com/mini-projects/grove-level-shifter/">assembling an inline DIY level shifter</a>. It works well but I prefer simplicity with enclosures and it makes things a bit more difficult as each DIY board has slightly different dimensions.</p>

<h2 id="schematic">Schematic</h2>

<p>So first I want to describe the logic here and give an example circuit to play around with it. This <a href="https://www.falstad.com/circuit/circuitjs.html?ctz=CQAgjCAMB0l3BWcMBMcUHYMGZIA4UA2ATmIxAUgoqoQFMBaMMAKADdxCrttDOrMKKMIAs3aNmFUYCdiB58F83iEFSQY6tKjRZAc2WKVS3niksDYLof7zCZ6SwBmqzWAx8UmtdiFhdOpAoLABOrlTufFYRHsJgcKG2aiheqhhCEfCQiSgInuk2apnwLAAyqqnJqbgi6k4AhgA2AM508hJQLACyqnlpQrmKkLUCuiwA7raRSQXZk2IxfAv9nT0ihLVq67U1wihj5SLE+UJHQyPCDS1tSNlhZysIsUXIJfdwK9srxQn3eIsaD7TYrODT-cCxETgnx+AIwIIWQHcFTLUzmAxPIx8TF2BydDg4pQ4l5UTS3KRjDiolEfEmA9qSbQyOTLLhmZYIEQXUniRkU2QE2JsiixTnc+nYDpMsYYoX4EV8DZMxGs+WszSOFxQ0lQsG0LnyWG3WDBP469kfLl4+J3JEUA1fMVxLKJB5OnFOn7ZQ7He21N2xCIgK6tBmrDQbP0R2o40ayeaW3Ucg1zWxW2ye7q9bEGwYKvYHCox3OpWPgYNNUOSxk5PpOokp16-DMl4sXG3NrzmtO6kEuLsaXUD+tGwLBSzWYXREBK9E2YVKWeODiL+UN8WaavqZkcadT6ye0S87djIA">falstad circuit</a> is the one I put together to verify my assumptions prior to ordering the PCB. With this setup you can see four different circuits. The top two are 3V3 to 5V which is a fairly normal setup for level shifting while the bottom two are 3V3 to 3V3 so demonstrating the behavior when not level shifting at all. The left two circuits are setup to allow toggling of the logic level on the LV side while the right two allow toggling on the HV side. In this way both 3V3 and 5V can be tested as the HV to confirm they will continue working.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/pcb/level-up-xiao-1.0/falstad-circuit.webp" alt="Falstad Circuit" style="padding: 20px; background-color: #FFF;" /></p>

<p>For how it works I’ve included my text from my DIY level shifting entry below.</p>

<p>I believe this works by (my understanding came from this <a href="https://www.reddit.com/r/AskElectronics/comments/npwl0t/comment/h0c2sfj/">great reddit answer</a>):</p>
<ul>
  <li>When the LV side is LOW it causes a Vgs of 3V3 which is more than the 1V5 needed to fully activate the mosfet. Once activated it causes current to flow and pulls the HV side down as well. When it’s high the Vgs is 0 so current doesn’t conduct and with the pull up resistors both sides are kept at their respected voltage.</li>
  <li>When the HV side is brought LOW it causes the diode to become reverse biased (from what I’ve gathered) which in turn allows the body diode to allow current through it. This in turn causes the source voltage to drop in turn causing the Vgs to climb until the mosfet is fully activated and the LV side is also brought fully to LOW.</li>
</ul>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/pcb/level-up-xiao-1.0/schematic.webp" alt="Level Up Schematic" style="padding: 20px; background-color: #FFF;" /></p>

<p>With that out of the way let’s look at the schematic. You can see it matches the general form of the above circuit. I’ve added some capacitors, headers for the jumper, and the Grove ports themselves. It’s a fairly simple setup. The jumper is setup as the HV value for each of the signal lines allowing customization for both ports.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/pcb/level-up-xiao-1.0/level-up-front.webp" alt="Level Up - Front" style="padding: 20px; background-color: #FFF;" /></p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/pcb/level-up-xiao-1.0/level-up-back.webp" alt="Level Up - Back" style="padding: 20px; background-color: #FFF;" /></p>

<p>The PCB itself has no components on the backside.</p>

<h2 id="assembly">Assembly</h2>

<p>Generally though the PCB has a silkscreen marking for each component listing the uF or ohms needed so I didn’t refer to the schematic while assembling. You should assemble the board with whatever you’re comfortable with if copying my steps here whether that’s individually soldering the components or using paste and hot air like I did.</p>

<h2 id="testing">Testing</h2>

<p>You can refer to the video for more information but I preformed the following two tests prior to using the circuit.</p>

<ul>
  <li>Continuity testing the various component points (confirming the pins from the Grove connector properly were soldered and checking for shorts)</li>
  <li>Using a test with all 4 signal pins set to <code class="language-plaintext highlighter-rouge">HIGH</code> allowing me to use my multimeter to check the voltage levels</li>
</ul>

<h2 id="test-code">Test Code</h2>

<h3 id="initial-test-circuit">Initial Test Circuit</h3>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
  <span class="n">pinMode</span><span class="p">(</span><span class="n">D4</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
  <span class="n">pinMode</span><span class="p">(</span><span class="n">D5</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
  <span class="n">pinMode</span><span class="p">(</span><span class="n">D6</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
  <span class="n">pinMode</span><span class="p">(</span><span class="n">D7</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>

  <span class="n">digitalWrite</span><span class="p">(</span><span class="n">D4</span><span class="p">,</span> <span class="n">HIGH</span><span class="p">);</span>
  <span class="n">digitalWrite</span><span class="p">(</span><span class="n">D5</span><span class="p">,</span> <span class="n">HIGH</span><span class="p">);</span>
  <span class="n">digitalWrite</span><span class="p">(</span><span class="n">D6</span><span class="p">,</span> <span class="n">HIGH</span><span class="p">);</span>
  <span class="n">digitalWrite</span><span class="p">(</span><span class="n">D7</span><span class="p">,</span> <span class="n">HIGH</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">loop</span><span class="p">()</span> <span class="p">{</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This circuit is the one I used for testing. The goal here was to simply set each of the various pins to a <code class="language-plaintext highlighter-rouge">HIGH</code> state such that I could test them once shifter.</p>

<h3 id="water-atomization-circuit">Water Atomization Circuit</h3>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/pcb/level-up-xiao-1.0/level-up-atomization.gif" alt="Level Up Water Atomization" style="padding: 20px; background-color: #FFF;" /></p>

<p>Additional Project items (affiliate links):</p>
<ul>
  <li><a href="https://www.seeedstudio.com/Grove-Water-Atomization-v1-0.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Grove Water Atomization 1.0</a></li>
</ul>

<p>Code:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
  <span class="n">pinMode</span><span class="p">(</span><span class="n">D7</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
  <span class="n">digitalWrite</span><span class="p">(</span><span class="n">D7</span><span class="p">,</span> <span class="n">HIGH</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">loop</span><span class="p">()</span> <span class="p">{</span>
<span class="p">}</span>
</code></pre></div></div>

<p>For this example I simply set the RX pin (D7) to <code class="language-plaintext highlighter-rouge">HIGH</code> such that the enable pin is activated and the logic can run. There’s nothing special about this set of code it just keeps the <code class="language-plaintext highlighter-rouge">EN</code> pin <code class="language-plaintext highlighter-rouge">HIGH</code> allowing it to function. When Grove modules only use with pin they use the outer pin and have the inner signal pin set to a not connected (<code class="language-plaintext highlighter-rouge">NC</code>) state.</p>

<h3 id="i2c-test-circuit">i2c Test Circuit</h3>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/pcb/level-up-xiao-1.0/level-up-i2c.webp" alt="Level Up i2c" style="padding: 20px; background-color: #FFF;" /></p>

<p>The i2c test circuit used here is a <a href="https://github.com/Seeed-Studio/Seeed_Arduino_SSCMA/blob/main/examples/inference/inference.ino">built in example</a> for SSCMA for inferencing over i2c.</p>

<p>Additional Project items (affiliate links):</p>
<ul>
  <li><a href="https://www.seeedstudio.com/Grove-Vision-AI-Module-V2-p-5851.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Grove Vision AI V2 Module</a></li>
</ul>

<p>Code:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;Seeed_Arduino_SSCMA.h&gt;</span><span class="cp">
</span>
<span class="n">SSCMA</span> <span class="n">AI</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">AI</span><span class="p">.</span><span class="n">begin</span><span class="p">();</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">9600</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">loop</span><span class="p">()</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">AI</span><span class="p">.</span><span class="n">invoke</span><span class="p">())</span>
    <span class="p">{</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"invoke success"</span><span class="p">);</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"perf: prepocess="</span><span class="p">);</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">perf</span><span class="p">().</span><span class="n">prepocess</span><span class="p">);</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", inference="</span><span class="p">);</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">perf</span><span class="p">().</span><span class="n">inference</span><span class="p">);</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", postpocess="</span><span class="p">);</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">perf</span><span class="p">().</span><span class="n">postprocess</span><span class="p">);</span>

        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">AI</span><span class="p">.</span><span class="n">boxes</span><span class="p">().</span><span class="n">size</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Box["</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"] target="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">boxes</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">target</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", score="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">boxes</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">score</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", x="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">boxes</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">x</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", y="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">boxes</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">y</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", w="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">boxes</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">w</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", h="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">boxes</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">h</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">AI</span><span class="p">.</span><span class="n">classes</span><span class="p">().</span><span class="n">size</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Class["</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"] target="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">classes</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">target</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", score="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">classes</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">score</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">AI</span><span class="p">.</span><span class="n">points</span><span class="p">().</span><span class="n">size</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Point["</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"]: target="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">points</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">target</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", score="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">points</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">score</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", x="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">points</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">x</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", y="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">points</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">y</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">AI</span><span class="p">.</span><span class="n">keypoints</span><span class="p">().</span><span class="n">size</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"keypoint["</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"] target="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">keypoints</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">box</span><span class="p">.</span><span class="n">target</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", score="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">keypoints</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">box</span><span class="p">.</span><span class="n">score</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", box:[x="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">keypoints</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">box</span><span class="p">.</span><span class="n">x</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", y="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">keypoints</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">box</span><span class="p">.</span><span class="n">y</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", w="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">keypoints</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">box</span><span class="p">.</span><span class="n">w</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", h="</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">keypoints</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">box</span><span class="p">.</span><span class="n">h</span><span class="p">);</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"], points:["</span><span class="p">);</span>
            <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">AI</span><span class="p">.</span><span class="n">keypoints</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">points</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"["</span><span class="p">);</span>
                <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">keypoints</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">points</span><span class="p">[</span><span class="n">j</span><span class="p">].</span><span class="n">x</span><span class="p">);</span>
                <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">","</span><span class="p">);</span>
                <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">keypoints</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">points</span><span class="p">[</span><span class="n">j</span><span class="p">].</span><span class="n">y</span><span class="p">);</span>
                <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"],"</span><span class="p">);</span>
            <span class="p">}</span>
            <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"]"</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I didn’t change anything with this circuit just reused the existing one to demonstrate the level shifting here.</p>

<h3 id="serial-test-circuit">Serial Test Circuit</h3>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/pcb/level-up-xiao-1.0/level-up-serial.gif" alt="Level Up Water Serial" style="padding: 20px; background-color: #FFF;" /></p>

<p>For this example project I setup a serial test circuit by utilizing an Arduino Uno, Base Shield V2, a Grove Buzzer, a Grove buzzer, and my Level Up board.</p>

<p>Given the Uno board only allows for one serial connection I opted for Software Serial utilizing the <a href="https://github.com/SlashDevin/NeoSWSerial">NeoSWSerial</a> library with a slower baud rate.</p>

<p>When the button on the Xiao side (with my level up board) is pressed it sends a message across the serial connection. On the Uno side the logic listens for commands and when it receives the associated message it triggers the buzzer.</p>

<p>Additional Project items (affiliate links):</p>
<ul>
  <li><a href="https://www.seeedstudio.com/Grove-Button-p-766.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Grove Button</a></li>
  <li><a href="https://www.seeedstudio.com/Grove-Buzzer.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Grove Buzzer</a></li>
  <li><a href="https://www.seeedstudio.com/Base-Shield-V2.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Grove Base Shield V2</a></li>
  <li><a href="https://www.seeedstudio.com/Arduino-Uno-Rev3-p-2995.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Arduino Uno Rev3</a> - although I suggest the <a href="https://www.seeedstudio.com/Arduino-Uno-Rev4-Minima-p-5716.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Uno Rev4 board</a></li>
</ul>

<h4 id="arduino-uno">Arduino Uno</h4>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;NeoSWSerial.h&gt;</span><span class="cp">
</span>
<span class="c1">// Define the software serial pins</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">rxPin</span> <span class="o">=</span> <span class="mi">6</span><span class="p">;</span> <span class="c1">// RX pin</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">txPin</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span> <span class="c1">// TX pin</span>

<span class="n">NeoSWSerial</span> <span class="nf">mySerial</span><span class="p">(</span><span class="n">rxPin</span><span class="p">,</span> <span class="n">txPin</span><span class="p">);</span>

<span class="k">const</span> <span class="kt">int</span> <span class="n">buzzerPin</span> <span class="o">=</span> <span class="n">A0</span><span class="p">;</span>
<span class="k">const</span> <span class="n">String</span> <span class="n">triggerMessage</span> <span class="o">=</span> <span class="s">"BUZZER_ON"</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
  <span class="n">pinMode</span><span class="p">(</span><span class="n">buzzerPin</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
  <span class="n">digitalWrite</span><span class="p">(</span><span class="n">buzzerPin</span><span class="p">,</span> <span class="n">LOW</span><span class="p">);</span> 

  <span class="n">mySerial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">9600</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">loop</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">mySerial</span><span class="p">.</span><span class="n">available</span><span class="p">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    
    <span class="n">String</span> <span class="n">message</span> <span class="o">=</span> <span class="n">mySerial</span><span class="p">.</span><span class="n">readStringUntil</span><span class="p">(</span><span class="sc">'\n'</span><span class="p">);</span>
    <span class="n">message</span><span class="p">.</span><span class="n">trim</span><span class="p">();</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">message</span> <span class="o">==</span> <span class="n">triggerMessage</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">digitalWrite</span><span class="p">(</span><span class="n">buzzerPin</span><span class="p">,</span> <span class="n">HIGH</span><span class="p">);</span>
      <span class="n">delay</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>
      <span class="n">digitalWrite</span><span class="p">(</span><span class="n">buzzerPin</span><span class="p">,</span> <span class="n">LOW</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The Uno logic here is fairly simple it sets up the buzzer as an output on the <code class="language-plaintext highlighter-rouge">A0</code> pin (the same pin that has the buzzer connected over a Grove cable), it sets up a serial connection using the pins <code class="language-plaintext highlighter-rouge">D5</code> and <code class="language-plaintext highlighter-rouge">D6</code>, and then during the loop it looks for newlines indicating a received message, compares it to the trigger for the buzzer, and if found turns it on for a second.</p>

<h4 id="xiao-esp32s3">Xiao ESP32S3</h4>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="kt">int</span> <span class="n">buttonPin</span> <span class="o">=</span> <span class="n">D5</span><span class="p">;</span>
<span class="k">const</span> <span class="n">String</span> <span class="n">triggerMessage</span> <span class="o">=</span> <span class="s">"BUZZER_ON"</span><span class="p">;</span>
<span class="kt">bool</span> <span class="n">buttonState</span> <span class="o">=</span> <span class="n">LOW</span><span class="p">;</span>
<span class="kt">bool</span> <span class="n">lastButtonState</span> <span class="o">=</span> <span class="n">LOW</span><span class="p">;</span>
<span class="n">HardwareSerial</span> <span class="nf">SerialOut</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>

<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
  <span class="n">pinMode</span><span class="p">(</span><span class="n">buttonPin</span><span class="p">,</span> <span class="n">INPUT</span><span class="p">);</span>
  <span class="n">SerialOut</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">9600</span><span class="p">,</span> <span class="n">SERIAL_8N1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">loop</span><span class="p">()</span> <span class="p">{</span>
  <span class="n">buttonState</span> <span class="o">=</span> <span class="n">digitalRead</span><span class="p">(</span><span class="n">buttonPin</span><span class="p">);</span>

  <span class="k">if</span> <span class="p">(</span><span class="n">buttonState</span> <span class="o">==</span> <span class="n">HIGH</span> <span class="o">&amp;&amp;</span> <span class="n">lastButtonState</span> <span class="o">==</span> <span class="n">LOW</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">SerialOut</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">triggerMessage</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="n">lastButtonState</span> <span class="o">=</span> <span class="n">buttonState</span><span class="p">;</span>
  <span class="n">delay</span><span class="p">(</span><span class="mi">50</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>On the Xiao side the code is fairly simple. It sets up <code class="language-plaintext highlighter-rouge">D5</code> as a button (here I’m using the i2c pins for the button), it then starts a hardware serial connection using the lower baud rate we set for the Uno, and it then just listens for button events and triggers the message based on that.</p>

<h2 id="closing-thoughts">Closing Thoughts</h2>

<p>This board works great for its intended purpose. It wasn’t hard to put together as it uses just a few components, its compact so it can be used with various projects, and it level shifts well.</p>

<p>I’ll be using the Seeed Grove Vision AI Module V2 for more projects in the future so this board will come in handy for those. I have a few more projects on the way in the future so will have more articles once those are ready!</p>]]></content><author><name>Cosmic Bee</name></author><category term="mini-projects" /><category term="Project" /><category term="PCB" /><category term="PCBWay" /><category term="Grove" /><category term="Seeed Studio" /><summary type="html"><![CDATA[Xiao form factor level shifter board]]></summary></entry><entry><title type="html">Unihiker Remote</title><link href="https://www.cranberrygrape.com/mini-projects/unihiker-remote/" rel="alternate" type="text/html" title="Unihiker Remote" /><published>2024-07-08T10:36:00-04:00</published><updated>2024-07-08T10:36:00-04:00</updated><id>https://www.cranberrygrape.com/mini-projects/unihiker-remote</id><content type="html" xml:base="https://www.cranberrygrape.com/mini-projects/unihiker-remote/"><![CDATA[<p>For this project I’m utilizing my DFRobot <a href="https://www.dfrobot.com/product-2691.html?tracking=Abxl41KH0gYXMiwPMWR8KfrR2xHcVGPklsPZMCdpxU6kcAylDPqgIkd9tpqCto1b">Unihiker (affiliate link)</a> to control my <a href="https://www.adeept.com/adeept-4wd-omni-directional-mecanum-wheels-robotic-car-kit-for-esp32-s3-banana-pi-picow-s3-diy-stem-remote-controlled-educational-robot-kit_p0406_s0086.html">Adeept 4WD Omni-directional Mecanum Wheels Robotic Car Kit for ESP32-S3</a>.</p>

<!-- Courtesy of embedresponsively.com -->

<div class="responsive-video-container">
    <iframe src="https://www.youtube-nocookie.com/embed/umzO1WO6qNo" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
  </div>

<p>The associated code for this project can be found <a href="https://github.com/Cosmic-Bee/unihiker-remote">here</a>.</p>

<h2 id="prerequisites">Prerequisites</h2>

<p>For more information about the robot car kit you can check my <a href="https://www.cranberrygrape.com/mini-projects/adeept-omni-car/">article on the lessons from the Adeept tutorials with the car</a>.</p>

<h2 id="background">Background</h2>

<p>There’s an <a href="https://community.dfrobot.com/contest-1614.html">ongoing contest</a> from DFRobot around utilizing the Unihiker for various project types. I have a Unihiker and have used it for multiple (<a href="https://www.cranberrygrape.com/mini-projects/dfrobot-unihiker-scale/">1</a>, <a href="https://www.cranberrygrape.com/mini-projects/dfrobot-unihiker-humidifier-control-unit/">2</a>) projects previously. The Unihiker device is fairly easy to work with: there are various example projects, the pingpong libraries makes interfacing with various sensors a simple task, and it has a large screen and touchscreen.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/dfrobot/unihiker/remote/robot-remote.gif" alt="Robot Remote Demonstration" style="padding: 20px; background-color: #FFF;" /></p>

<h2 id="setup">Setup</h2>

<h3 id="banana-pi-picow-s3-circuitpython">Banana Pi PicoW S3 CircuitPython</h3>

<p>The first step is to get your Banana Pi PicoW S3 setup to have the CircuitPython code. The majority of the libraries used for this project come as part of the omnidirectional car kit’s logic but a CircuitPython bundled library is needed as well.</p>

<p>If you reference the <a href="https://github.com/Cosmic-Bee/unihiker-remote/blob/main/code.py">code</a> only the code.py file from the repository will be used for car. Depending upon the version of CircuitPython you used you may need either the 8.x or 9.x bundle. I used the <a href="https://circuitpython.org/libraries">9.x bundle</a> as I flashed that version of CircuitPython to the board.</p>

<ul>
  <li>Copy <code class="language-plaintext highlighter-rouge">adafruit_ht16k33</code> to the drive’s lib folder</li>
  <li>From Adeept’s code bundle for the car copy over the <code class="language-plaintext highlighter-rouge">avoid_obstacles.py</code> and <code class="language-plaintext highlighter-rouge">BPI_PicoW_S3_Car.py</code>. I placed them at the root folder of the project</li>
  <li>Finally copy the <code class="language-plaintext highlighter-rouge">code.py</code> from the repository above to the drive</li>
</ul>

<p>With those in place the setup process for the car is complete and we can move onto the Unihiker setup.</p>

<h4 id="code-breakdown">Code Breakdown</h4>

<p>I’m going to go into the code I added / adjusted here. So I took the original car control logic and modified it for commands I wanted to handle. I won’t go into every part so you should reference the code in the repository for actually copying it over but this may prove helpful if you want to understand the different elements of it.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Connect WIFI
</span><span class="n">ssid</span><span class="p">,</span> <span class="n">pw</span> <span class="o">=</span> <span class="p">(</span><span class="s">'BPI-PicoW-S3'</span><span class="p">,</span> <span class="s">'12345678'</span><span class="p">)</span>

<span class="n">wifi</span><span class="p">.</span><span class="n">radio</span><span class="p">.</span><span class="n">start_ap</span><span class="p">(</span><span class="n">ssid</span><span class="o">=</span><span class="n">ssid</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="n">pw</span><span class="p">)</span>
<span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">'WiFi AP mode Started! SSID is </span><span class="si">{</span><span class="n">ssid</span><span class="si">}</span><span class="s">, IP=</span><span class="si">{</span><span class="n">wifi</span><span class="p">.</span><span class="n">radio</span><span class="p">.</span><span class="n">ipv4_address_ap</span><span class="si">}</span><span class="s">'</span><span class="p">)</span>
</code></pre></div></div>

<p>So this logic sets up an access point. This access point can be connected to from the Unihiker to then control the car. Having it act as an access point (vs connecting to an existing network) allows one to take the car anywhere. I found the car to use the address <code class="language-plaintext highlighter-rouge">192.168.4.1</code> for the access point it created (I used CircuitPython serial to read the output).</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">servo</span> <span class="o">=</span> <span class="n">Servo</span><span class="p">()</span>
<span class="n">servo</span><span class="p">.</span><span class="n">set_angle</span><span class="p">(</span><span class="n">board</span><span class="p">.</span><span class="n">GP7</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span>
</code></pre></div></div>

<p>I noticed during initialization for the servo head moves which can cause it to not point forward when the car begins movement. In order to address this I set the angle of the servo to 0 after initialization. In this way when the obstacle avoidance logic is running the car is always reading the forward direction. You’ll notice this is using the pinout from the motor shield / banana pi PicoW S3 as it’s using CircuitPython. If you were using arduino you’d need to use the ESP32S3 related GPIO numbers but in this case it’s straightforward given that mapping.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span> <span class="n">pool</span><span class="p">.</span><span class="n">socket</span><span class="p">(</span><span class="n">pool</span><span class="p">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">pool</span><span class="p">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span> <span class="k">as</span> <span class="n">server_socket</span><span class="p">:</span>
    <span class="n">server_socket</span><span class="p">.</span><span class="n">bind</span><span class="p">((</span><span class="s">'0.0.0.0'</span><span class="p">,</span> <span class="mi">8080</span><span class="p">))</span>
    <span class="n">server_socket</span><span class="p">.</span><span class="n">listen</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>

    <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
        <span class="n">server_socket</span><span class="p">.</span><span class="n">settimeout</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">connection</span><span class="p">,</span> <span class="n">address</span> <span class="o">=</span> <span class="n">server_socket</span><span class="p">.</span><span class="n">accept</span><span class="p">()</span>
            <span class="k">print</span><span class="p">(</span><span class="s">"Connected by"</span><span class="p">,</span> <span class="n">address</span><span class="p">)</span>
            <span class="n">connections</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">connection</span><span class="p">)</span>
        <span class="k">except</span> <span class="nb">OSError</span><span class="p">:</span>
            <span class="k">pass</span>
</code></pre></div></div>

<p>The logic then creates a socket and once connected proceeds.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">connection</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span><span class="n">connections</span><span class="p">):</span>
  <span class="n">buf</span> <span class="o">=</span> <span class="nb">bytearray</span><span class="p">(</span><span class="n">BUFF_SIZE</span><span class="p">)</span>
  <span class="k">try</span><span class="p">:</span>
      <span class="n">size</span> <span class="o">=</span> <span class="n">connection</span><span class="p">.</span><span class="n">recv_into</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span>
  <span class="k">except</span> <span class="nb">OSError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
      <span class="k">if</span> <span class="n">e</span><span class="p">.</span><span class="n">errno</span> <span class="o">==</span> <span class="n">errno</span><span class="p">.</span><span class="n">EAGAIN</span><span class="p">:</span>
          <span class="k">continue</span>
      <span class="k">else</span><span class="p">:</span>
          <span class="k">raise</span>

  <span class="k">if</span> <span class="ow">not</span> <span class="n">size</span><span class="p">:</span>
      <span class="n">connection</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
      <span class="n">connections</span><span class="p">.</span><span class="n">remove</span><span class="p">(</span><span class="n">connection</span><span class="p">)</span>
      <span class="k">break</span>

  <span class="n">data</span> <span class="o">=</span> <span class="n">buf</span><span class="p">[:</span><span class="n">size</span><span class="p">].</span><span class="n">decode</span><span class="p">()</span>
  <span class="k">print</span><span class="p">(</span><span class="s">"Received data:"</span><span class="p">,</span> <span class="nb">repr</span><span class="p">(</span><span class="n">data</span><span class="p">))</span>
</code></pre></div></div>

<p>Then for each connection it confirms if the connection has been closed and if so removes it else it decodes and processes the data.</p>

<p>Given the data in this case is just a command the logic is pretty simple from here out.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">data</span> <span class="o">==</span> <span class="s">"obstacle_avoidance"</span><span class="p">:</span>
  <span class="k">try</span><span class="p">:</span>
      <span class="n">avoid_obstacles_mark</span> <span class="o">=</span> <span class="mi">0</span>
      <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
          <span class="n">avoid_obstacles</span><span class="p">.</span><span class="n">test</span><span class="p">()</span>

          <span class="n">buf</span> <span class="o">=</span> <span class="nb">bytearray</span><span class="p">(</span><span class="n">BUFF_SIZE</span><span class="p">)</span>
          <span class="k">try</span><span class="p">:</span>
              <span class="n">size</span> <span class="o">=</span> <span class="n">connection</span><span class="p">.</span><span class="n">recv_into</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span>
          <span class="k">except</span> <span class="nb">OSError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
              <span class="k">if</span> <span class="n">e</span><span class="p">.</span><span class="n">errno</span> <span class="o">==</span> <span class="n">errno</span><span class="p">.</span><span class="n">EAGAIN</span><span class="p">:</span>
                  <span class="k">continue</span>
              <span class="k">else</span><span class="p">:</span>
                  <span class="k">raise</span>

          <span class="k">if</span> <span class="ow">not</span> <span class="n">size</span><span class="p">:</span>
              <span class="k">break</span>

          <span class="n">data</span> <span class="o">=</span> <span class="n">buf</span><span class="p">[:</span><span class="n">size</span><span class="p">].</span><span class="n">decode</span><span class="p">()</span>
          <span class="k">print</span><span class="p">(</span><span class="s">"Received data:"</span><span class="p">,</span> <span class="nb">repr</span><span class="p">(</span><span class="n">data</span><span class="p">))</span>
  <span class="k">except</span> <span class="nb">KeyboardInterrupt</span><span class="p">:</span>
      <span class="n">motor</span><span class="p">.</span><span class="n">motor_stop</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
</code></pre></div></div>

<p>I persisted the existing obstacle avoidance code here. It triggers the logic in that associated library provided by Adeept which prevents the car from running into obstacles by using its sensor to detect range.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="n">direction_commands</span> <span class="o">=</span> <span class="p">[</span>
      <span class="s">"forward"</span><span class="p">,</span>
      <span class="s">"backward"</span><span class="p">,</span>
      <span class="s">"left"</span><span class="p">,</span>
      <span class="s">"right"</span><span class="p">,</span>
      <span class="s">"left_backward"</span><span class="p">,</span>
      <span class="s">"right_forward"</span><span class="p">,</span>
      <span class="s">"right_backward"</span><span class="p">,</span>
      <span class="s">"left_forward"</span><span class="p">,</span>
      <span class="s">"turn_left"</span><span class="p">,</span>
      <span class="s">"turn_right"</span>
  <span class="p">]</span>

  <span class="k">if</span> <span class="n">data</span> <span class="ow">in</span> <span class="n">direction_commands</span><span class="p">:</span>
      <span class="n">motor</span><span class="p">.</span><span class="n">move</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">speed</span><span class="p">)</span>
      <span class="k">if</span> <span class="s">"turn_"</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
          <span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>
          <span class="n">motor</span><span class="p">.</span><span class="n">motor_stop</span><span class="p">()</span>

      <span class="n">lcd_print</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">"_"</span><span class="p">,</span> <span class="s">" "</span><span class="p">)</span>
  <span class="k">else</span><span class="p">:</span>
      <span class="n">lcd_print</span> <span class="o">=</span> <span class="s">"stop"</span>
      <span class="n">motor</span><span class="p">.</span><span class="n">motor_stop</span><span class="p">()</span>

<span class="k">if</span> <span class="n">info</span> <span class="o">!=</span> <span class="n">lcd_print</span><span class="p">:</span>
  <span class="n">lcd_putstr</span> <span class="o">=</span> <span class="n">LCD1602</span><span class="p">()</span>
  <span class="n">lcd_putstr</span><span class="p">.</span><span class="n">lcd</span><span class="p">.</span><span class="n">clear</span><span class="p">()</span>
  <span class="n">lcd_putstr</span><span class="p">.</span><span class="n">lcd</span><span class="p">.</span><span class="k">print</span><span class="p">(</span><span class="n">lcd_print</span><span class="p">)</span>
  <span class="n">info</span> <span class="o">=</span> <span class="n">lcd_print</span>
  <span class="n">lcd_putstr</span><span class="p">.</span><span class="n">lcd</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
</code></pre></div></div>

<p>In the original code for the WiFi control the logic did not support all directions and was geared towards the associated app. I adjusted these to remove the newline and added support for each of the directions. In the event it’s a turn the logic automatically stops the motors after half a second. I did this to help prevent the car from turning too heavily prior to a user pressing stop. I would have preferred having the Unihiker buttons act on touch but I didn’t see touch events just click in the API documentation so opted for this which works well.</p>

<p>That’s about it for the car – most of the primary logic here is handled by Adeept’s libraries so it’s just a matter of starting the access point, listening for connections and creating a socket, and then just processing the commands.</p>

<h3 id="unihiker-setup">Unihiker Setup</h3>

<p>For the Unihiker the setup is also fairly straightforward. First you’ll want to <a href="https://www.unihiker.com/wiki/get-started#4.2.%20SSH">connect your Unihiker over SSH</a> I found Visual Studio code to be a great IDE for this as they mention in their documentation.</p>

<p>With that setup you should be able to see the associated folders, are able to drag and drop code to the device, and can use the terminal via the connection.</p>

<h4 id="connecting-to-the-access-point">Connecting to the Access Point</h4>

<p>The first step you’ll want to take is to connect to the associated access point. To do such you’ll want to hit the address <a href="http://10.1.2.3">http://10.1.2.3</a> and go to the “Network Settings” tab.</p>

<p>Here you’ll see the following screen:
<img src="https://www.cranberrygrape.com/assets/images/projects/dfrobot/unihiker/remote/unihiker-network-settings.webp" alt="Unihiker Network Settings" style="padding: 20px; background-color: #FFF;" /></p>

<p>It took me a few times (both scanning and then connecting) before I saw the Access Point in the list. Once I did connect though I’ve never had to return to this page. As soon as the Banana Pi PicoW S3 is on the Unihiker attempts to connect and make a socket connection which in turn just works every time.</p>

<p>The values seen in this screenshot represent the ones we setup in the code prior. If you changed the WiFi SSID or password there you’ll need to use those here as one would expect.</p>

<h4 id="getting-the-remote-ready">Getting the Remote Ready</h4>

<p>The next step is to upload the images and code. With the SSH connection to the Unihiker ready you need only drag and drop the associated images folder from the <a href="https://github.com/Cosmic-Bee/unihiker-remote">code repository</a> to get them on the device. These images include ones for each button, a background with the title, and a splash screen.</p>

<p>Like the images the <code class="language-plaintext highlighter-rouge">unihiker_remote.py</code> can be dragged over. Once on the device you can get to the code (assuming it was placed in the root) by long pressing the side button to open the menu, selecting “2 - Run Programs”, selecting the “root” folder, and then selecting the program from within there.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/dfrobot/unihiker/remote/unihiker-control.gif" alt="Unihiker Control" style="padding: 20px; background-color: #FFF;" /></p>

<h4 id="code-breakdown-1">Code Breakdown</h4>

<p>The <a href="https://github.com/Cosmic-Bee/unihiker-remote/blob/main/unihiker_remote.py">code for the Unihiker</a> here isn’t too complex. Like earlier I’ll go into some detail about various elements but the code itself should be referenced for use on the device.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">server_ip</span> <span class="o">=</span> <span class="s">'192.168.4.1'</span>
<span class="n">server_port</span> <span class="o">=</span> <span class="mi">8080</span>
</code></pre></div></div>

<p>As mentioned earlier the access point’s address for me was this one so I setup the Unihiker remote to connect to this address.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">send_command</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">command</span><span class="p">):</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">s</span><span class="p">.</span><span class="n">sendall</span><span class="p">(</span><span class="n">command</span><span class="p">.</span><span class="n">encode</span><span class="p">())</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Sent: </span><span class="si">{</span><span class="n">command</span><span class="p">.</span><span class="n">strip</span><span class="p">()</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
    <span class="k">except</span> <span class="p">(</span><span class="nb">BrokenPipeError</span><span class="p">,</span> <span class="nb">ConnectionResetError</span><span class="p">,</span> <span class="nb">OSError</span><span class="p">):</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"Connection lost. Reconnecting..."</span><span class="p">)</span>
        <span class="n">s</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
        <span class="n">s</span> <span class="o">=</span> <span class="n">connect_to_server</span><span class="p">()</span>
        <span class="n">s</span><span class="p">.</span><span class="n">sendall</span><span class="p">(</span><span class="n">command</span><span class="p">.</span><span class="n">encode</span><span class="p">())</span>
    <span class="k">return</span> <span class="n">s</span>
</code></pre></div></div>

<p>I setup a <code class="language-plaintext highlighter-rouge">send_command</code> method to send commands to the device.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">connect_to_server</span><span class="p">():</span>
    <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">s</span> <span class="o">=</span> <span class="n">socket</span><span class="p">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="p">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="p">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span>
            <span class="n">s</span><span class="p">.</span><span class="n">connect</span><span class="p">((</span><span class="n">server_ip</span><span class="p">,</span> <span class="n">server_port</span><span class="p">))</span>
            <span class="k">print</span><span class="p">(</span><span class="s">"Connected to server"</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">s</span>
        <span class="k">except</span> <span class="p">(</span><span class="nb">ConnectionRefusedError</span><span class="p">,</span> <span class="nb">OSError</span><span class="p">)</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
            <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="nb">OSError</span><span class="p">)</span> <span class="ow">and</span> <span class="n">e</span><span class="p">.</span><span class="n">errno</span> <span class="o">==</span> <span class="mi">101</span><span class="p">:</span>
                <span class="k">print</span><span class="p">(</span><span class="s">"Network is unreachable. Retrying in 5 seconds..."</span><span class="p">)</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="k">print</span><span class="p">(</span><span class="s">"Connection failed. Retrying in 5 seconds..."</span><span class="p">)</span>
            <span class="n">s</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
            <span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
</code></pre></div></div>

<p>Likewise I setup a method called <code class="language-plaintext highlighter-rouge">connect_to_server</code> that attempts to connect to the server but in the event of failure it retries 5 seconds later.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">initialize_gui</span><span class="p">():</span>
    <span class="n">gui</span> <span class="o">=</span> <span class="n">GUI</span><span class="p">()</span>
    <span class="n">gui</span><span class="p">.</span><span class="n">draw_image</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">w</span><span class="o">=</span><span class="mi">320</span><span class="p">,</span> <span class="n">h</span><span class="o">=</span><span class="mi">320</span><span class="p">,</span> <span class="n">image</span><span class="o">=</span><span class="s">"images/remote.png"</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">gui</span>
</code></pre></div></div>

<p>I’ve encapsulated some logic here to initialize the GUI setting up the initial splash image and returning the GUI object.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">draw_buttons</span><span class="p">(</span><span class="n">gui</span><span class="p">):</span>
    <span class="n">commands</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s">'turn_left'</span><span class="p">:</span> <span class="s">'images/arrows-turn-left.png'</span><span class="p">,</span>
        <span class="s">'turn_right'</span><span class="p">:</span> <span class="s">'images/arrows-turn-right.png'</span><span class="p">,</span>
        <span class="s">'left_forward'</span><span class="p">:</span> <span class="s">'images/arrows-top-left.png'</span><span class="p">,</span>
        <span class="s">'forward'</span><span class="p">:</span> <span class="s">'images/arrows-top.png'</span><span class="p">,</span>
        <span class="s">'right_forward'</span><span class="p">:</span> <span class="s">'images/arrows-top-right.png'</span><span class="p">,</span>
        <span class="s">'left'</span><span class="p">:</span> <span class="s">'images/arrows-left.png'</span><span class="p">,</span>
        <span class="s">'stop'</span><span class="p">:</span> <span class="s">'images/stop.png'</span><span class="p">,</span>
        <span class="s">'right'</span><span class="p">:</span> <span class="s">'images/arrows-right.png'</span><span class="p">,</span>
        <span class="s">'left_backward'</span><span class="p">:</span> <span class="s">'images/arrows-bottom-left.png'</span><span class="p">,</span>
        <span class="s">'backward'</span><span class="p">:</span> <span class="s">'images/arrows-bottom.png'</span><span class="p">,</span>
        <span class="s">'right_backward'</span><span class="p">:</span> <span class="s">'images/arrows-bottom-right.png'</span><span class="p">,</span>
        <span class="s">'obstacle_avoidance'</span><span class="p">:</span> <span class="s">'images/obstacle-avoid.png'</span>
    <span class="p">}</span>

    <span class="n">button_positions</span> <span class="o">=</span> <span class="p">[</span>
        <span class="p">(</span><span class="s">'turn_left'</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">80</span><span class="p">),</span> <span class="p">(</span><span class="s">'obstacle_avoidance'</span><span class="p">,</span> <span class="mi">90</span><span class="p">,</span> <span class="mi">80</span><span class="p">),</span> <span class="p">(</span><span class="s">'turn_right'</span><span class="p">,</span> <span class="mi">150</span><span class="p">,</span> <span class="mi">80</span><span class="p">),</span>
        <span class="p">(</span><span class="s">'left_forward'</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">140</span><span class="p">),</span> <span class="p">(</span><span class="s">'forward'</span><span class="p">,</span> <span class="mi">90</span><span class="p">,</span> <span class="mi">140</span><span class="p">),</span> <span class="p">(</span><span class="s">'right_forward'</span><span class="p">,</span> <span class="mi">150</span><span class="p">,</span> <span class="mi">140</span><span class="p">),</span>
        <span class="p">(</span><span class="s">'left'</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">200</span><span class="p">),</span> <span class="p">(</span><span class="s">'stop'</span><span class="p">,</span> <span class="mi">90</span><span class="p">,</span> <span class="mi">200</span><span class="p">),</span> <span class="p">(</span><span class="s">'right'</span><span class="p">,</span> <span class="mi">150</span><span class="p">,</span> <span class="mi">200</span><span class="p">),</span>
        <span class="p">(</span><span class="s">'left_backward'</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">260</span><span class="p">),</span> <span class="p">(</span><span class="s">'backward'</span><span class="p">,</span> <span class="mi">90</span><span class="p">,</span> <span class="mi">260</span><span class="p">),</span> <span class="p">(</span><span class="s">'right_backward'</span><span class="p">,</span> <span class="mi">150</span><span class="p">,</span> <span class="mi">260</span><span class="p">)</span>
    <span class="p">]</span>

    <span class="k">for</span> <span class="n">command</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="ow">in</span> <span class="n">button_positions</span><span class="p">:</span>
        <span class="n">gui</span><span class="p">.</span><span class="n">draw_image</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="n">y</span><span class="p">,</span> <span class="n">w</span><span class="o">=</span><span class="mi">60</span><span class="p">,</span> <span class="n">h</span><span class="o">=</span><span class="mi">60</span><span class="p">,</span> <span class="n">image</span><span class="o">=</span><span class="n">commands</span><span class="p">[</span><span class="n">command</span><span class="p">],</span> <span class="n">onclick</span><span class="o">=</span><span class="k">lambda</span> <span class="n">cmd</span><span class="o">=</span><span class="n">command</span><span class="p">:</span> <span class="n">button_click</span><span class="p">(</span><span class="n">cmd</span><span class="p">))</span>
</code></pre></div></div>

<p>I’ve setup these two mappings. In retrospect I suppose I could have just put the images in the tuple for the button positions but I didn’t at the time. The logic loops over the button positions, uses the <code class="language-plaintext highlighter-rouge">draw_image</code> method on the <code class="language-plaintext highlighter-rouge">gui</code> to then draw them to the page. It uses the commands mapping to figure out the correct image to use. You can see in the onclick lambda it sets the variable <code class="language-plaintext highlighter-rouge">cmd</code> to the value of <code class="language-plaintext highlighter-rouge">command</code> which it then in the lambda uses.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">button_click</span><span class="p">(</span><span class="n">command</span><span class="p">):</span>
    <span class="k">global</span> <span class="n">connection_socket</span>
    <span class="n">connection_socket</span> <span class="o">=</span> <span class="n">send_command</span><span class="p">(</span><span class="n">connection_socket</span><span class="p">,</span> <span class="n">command</span><span class="p">)</span>
</code></pre></div></div>
<p>When the button is clicked it then runs this code sending that command to the socket.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Board</span><span class="p">(</span><span class="s">"UNIHIKER"</span><span class="p">).</span><span class="n">begin</span><span class="p">()</span>
<span class="n">gui</span> <span class="o">=</span> <span class="n">initialize_gui</span><span class="p">()</span>

<span class="n">connection_socket</span> <span class="o">=</span> <span class="n">connect_to_server</span><span class="p">()</span>

<span class="n">gui</span><span class="p">.</span><span class="n">draw_image</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">w</span><span class="o">=</span><span class="mi">320</span><span class="p">,</span> <span class="n">h</span><span class="o">=</span><span class="mi">320</span><span class="p">,</span> <span class="n">image</span><span class="o">=</span><span class="s">"images/background.png"</span><span class="p">)</span>
<span class="n">draw_buttons</span><span class="p">(</span><span class="n">gui</span><span class="p">)</span>

<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
    <span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>

<p>The main logic is seen here – it initializes the Unihiker board, the GUI (and splash as mentioned above), attempts to connect to server, once that completes it adds the background, and then draws the buttons. Given the rest of the logic is handled in callbacks the loop logic simply sleeps each iteration to keep the program alive.</p>

<h2 id="next-steps">Next Steps?</h2>

<p>I could imagine further augmenting this control to display sensor data and display it on the remote as you control it. It would also be really cool to use an onboard Xiao ESP32S3 Sense to get a low resolution image to display to allow for remote control.</p>

<h2 id="image-creation-process">Image Creation Process</h2>

<p>To help aid me with the remote control design I utilized ChatGTP’s image generation features. The nice thing about relying on a tool like this is that it can create lots of content with a similar theme allowing your images to work well together as you design an interface.</p>

<p>To generate the images I started with a simple prompt:</p>

<blockquote>
  <p>I’m designing a gamepad and need a bunch of arrow buttons to see which I’d prefer – give up and up right for each design and make sure the design can be flipped or rotated without issue for those two (omnidirectional)</p>
</blockquote>

<p>This led to it responding with ASCII art so I responded:</p>

<blockquote>
  <p>Image generation</p>
</blockquote>

<p>Which started generating images.</p>

<p>I’ve included screenshots of the exchange here as a gallery.</p>

<figure class="third full">
  
    
      <a href="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-1.webp" title="ChatGPT Image Generation Gallery 1">
          <img src="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-1.webp" alt="ChatGPT Image Generation Gallery 1" />
      </a>
    
  
    
      <a href="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-2.webp" title="ChatGPT Image Generation Gallery 2">
          <img src="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-2.webp" alt="ChatGPT Image Generation Gallery 2" />
      </a>
    
  
    
      <a href="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-3.webp" title="ChatGPT Image Generation Gallery 3">
          <img src="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-3.webp" alt="ChatGPT Image Generation Gallery 3" />
      </a>
    
  
    
      <a href="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-4.webp" title="ChatGPT Image Generation Gallery 4">
          <img src="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-4.webp" alt="ChatGPT Image Generation Gallery 4" />
      </a>
    
  
    
      <a href="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-5.webp" title="ChatGPT Image Generation Gallery 5">
          <img src="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-5.webp" alt="ChatGPT Image Generation Gallery 5" />
      </a>
    
  
    
      <a href="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-6.webp" title="ChatGPT Image Generation Gallery 6">
          <img src="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-6.webp" alt="ChatGPT Image Generation Gallery 6" />
      </a>
    
  
    
      <a href="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-7.webp" title="ChatGPT Image Generation Gallery 7">
          <img src="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-7.webp" alt="ChatGPT Image Generation Gallery 7" />
      </a>
    
  
    
      <a href="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-8.webp" title="ChatGPT Image Generation Gallery 8">
          <img src="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-8.webp" alt="ChatGPT Image Generation Gallery 8" />
      </a>
    
  
    
      <a href="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-9.webp" title="ChatGPT Image Generation Gallery 9">
          <img src="/assets/images/projects/dfrobot/unihiker/remote/chatgpt-9.webp" alt="ChatGPT Image Generation Gallery 9" />
      </a>
    
  
  
    <figcaption>ChatGPT Generation
</figcaption>
  
</figure>

<p>After this I took the images, cut out the components and exported them for as individual images. For the arrows I flipped them around as needed horizontally and vertically to have one for each direction. I also turned them 45 degrees such that they pointed toward each corner and side.</p>

<figure class="third full">
  
    
      <a href="/assets/images/projects/dfrobot/unihiker/remote/gimp-arrows.webp" title="Adjusting the arrow images">
          <img src="/assets/images/projects/dfrobot/unihiker/remote/gimp-arrows.webp" alt="Adjusting the arrow images" />
      </a>
    
  
    
      <a href="/assets/images/projects/dfrobot/unihiker/remote/gimp-splash.webp" title="Adjusting the splash page">
          <img src="/assets/images/projects/dfrobot/unihiker/remote/gimp-splash.webp" alt="Adjusting the splash page" />
      </a>
    
  
    
      <a href="/assets/images/projects/dfrobot/unihiker/remote/gimp-background.webp" title="Adjusting the background text and image">
          <img src="/assets/images/projects/dfrobot/unihiker/remote/gimp-background.webp" alt="Adjusting the background text and image" />
      </a>
    
  
  
    <figcaption>Gimp Generation
</figcaption>
  
</figure>

<p>After exporting them I took the images and used them for the project.</p>]]></content><author><name>Cosmic Bee</name></author><category term="mini-projects" /><category term="Project" /><category term="DFRobot" /><category term="Unihiker" /><category term="Adeept" /><category term="Robotics" /><category term="Banana Pi PicoW S3" /><category term="Robot Car" /><summary type="html"><![CDATA[DFRobot Unihiker Remote controlling Omnidirectional Car]]></summary></entry><entry><title type="html">ROCM Jupyter Notebook Over SSH</title><link href="https://www.cranberrygrape.com/machine-learning/jupyter-notebook-over-ssh/" rel="alternate" type="text/html" title="ROCM Jupyter Notebook Over SSH" /><published>2024-07-07T12:36:00-04:00</published><updated>2024-07-07T12:36:00-04:00</updated><id>https://www.cranberrygrape.com/machine-learning/jupyter-notebook-over-ssh</id><content type="html" xml:base="https://www.cranberrygrape.com/machine-learning/jupyter-notebook-over-ssh/"><![CDATA[<p>Recently I setup another machine learning machine but this time AMD-based with a W7900 GPU (48GB). I wanted to test the capability of the GPU relative to my other machine learning machine which uses a 12GB NVidia card. Part of my testing involved the use of TensorFlow as I have experience shrinking models and wanted to see if this would significantly speed up the process there.</p>

<p>I didn’t want to have to work at my machine learning machine though as it’s not the more comfortable positioning for long sessions so I opted to setup SSH tunneling for the notebook. This is a quick guide for that process on the AMD ROCM stack.</p>

<p>The first step is to get the W7900 configured with ROCM and the associated libraries. For this machine I set things up with Ubuntu 22.04 LTS, configured SSH via key, and had went through the <a href="https://rocm.docs.amd.com/projects/install-on-linux/en/latest/how-to/native-install/ubuntu.html">setup process for ROCM on linux</a> and the associated <a href="https://rocm.docs.amd.com/projects/install-on-linux/en/latest/how-to/native-install/post-install.html">post installation instructions</a>.</p>

<p>For TensorFlow itself I opted for an older version. I have been using EfficientNetV0 and its more complicated / larger versions as part of my model shrinking and have run into issues with newer versions around saving related to <a href="https://discuss.tensorflow.org/t/using-efficientnetb0-and-save-model-will-result-unable-to-serialize-2-0896919-2-1128857-2-1081853-to-json-unrecognized-type-class-tensorflow-python-framework-ops-eagertensor/12518/23">this bug</a>.</p>

<p>In my case I first created a conda environment for machine-learning. I suppose I could have just installed the dependencies with pip but I’ve found conda to be helpful in terms of isolating my environments for various machine learning needs. It’s also nice to be able to start over more easily if the environment gets into a bad state.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget https://repo.anaconda.com/miniconda/Miniconda3-py38_23.3.1-0-Linux-x86_64.sh
<span class="nb">sha256sum </span>Miniconda3-py38_23.3.1-0-Linux-x86_64.sh
</code></pre></div></div>

<p>After confirming the sha256 signature, install:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bash Miniconda3-py38_23.3.1-0-Linux-x86_64.sh
</code></pre></div></div>

<p>I then installed TensorFlow and several other dependencies (note I’m using 2.9.2 per that above issue):</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>conda create <span class="nt">-n</span> machine-learning
activate machine-learning
conda <span class="nb">install </span>pandas scikit-learn notebook matplotlib
pip <span class="nb">install </span>tensorflow-rocm<span class="o">==</span>2.9.2
</code></pre></div></div>

<p>Next I start a new SSH session with a tunnel setup for port 8888:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh <span class="nt">-L</span> 8888:localhost:8888 user@192.168.1.100
</code></pre></div></div>

<p>As long as that SSH session is left open I can use the associated notebook URL as if it were running on my laptop.</p>

<p>As such on subsequent SSH sessions I can run:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>conda activate machine-learning
jupyter notebook <span class="nt">--no-browser</span> <span class="nt">--port</span><span class="o">=</span>8888 <span class="nt">--NotebookApp</span>.allow_origin<span class="o">=</span><span class="s1">'https://colab.research.google.com'</span>
</code></pre></div></div>

<p>Next you’ll want to take the generated URL, such as the example below, and enter it into the local runtime selection inside of Google colab.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://localhost:8888/tree?token<span class="o">=</span>94cb1ff48933128cf12ac7fb4745edb26a39c6798fdb923f
</code></pre></div></div>

<p><img src="https://www.cranberrygrape.com/assets/images/machine-learning/remote-jupyter/colab-select.webp" alt="Select connect to local runtime" style="padding: 20px; background-color: #FFF;" /></p>

<p><img src="https://www.cranberrygrape.com/assets/images/machine-learning/remote-jupyter/colab-form.webp" alt="Fill in link to runtime" style="padding: 20px; background-color: #FFF;" /></p>

<p>With that setup you can now use your machine to setup the notebook and begin running machine learning experiments on it.</p>

<p>You’ll know it’s working if the logs are spammed with the message:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2024-07-07 08:49:17.214980: I tensorflow/core/common_runtime/gpu_fusion_pass.cc:507] ROCm Fusion is enabled.
</code></pre></div></div>

<p>I did notice one negative of this approach is that my laptop becomes a bit slower in terms of network traffic while this is ongoing. I think given this a hybrid setup works well where I use the SSH tunnel to configure the project, confirm I have the general model setup correct, and then switch over to the machine itself to run the notebook for the training aspect one I can set it and leave it alone for a long period.</p>]]></content><author><name>Cosmic Bee</name></author><category term="machine-learning" /><category term="Project" /><category term="machine learning" /><category term="keras" /><category term="AMD" /><category term="ROCM" /><summary type="html"><![CDATA[Guide to running jupyter notebook over a remote session]]></summary></entry><entry><title type="html">3D Printed Animal Tracks</title><link href="https://www.cranberrygrape.com/3d-printing/animal-tags/" rel="alternate" type="text/html" title="3D Printed Animal Tracks" /><published>2024-06-30T22:36:00-04:00</published><updated>2024-06-30T22:36:00-04:00</updated><id>https://www.cranberrygrape.com/3d-printing/animal-tags</id><content type="html" xml:base="https://www.cranberrygrape.com/3d-printing/animal-tags/"><![CDATA[<p>This guide explains the process to create the animal tags I <a href="https://www.printables.com/model/926922">added on Printables</a>. The idea behind these tags is that one can utilize them in the wilds to compare to tracks they find.</p>

<!-- Courtesy of embedresponsively.com -->

<div class="responsive-video-container">
    <iframe src="https://www.youtube-nocookie.com/embed/dzBPHl5JeFw" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
  </div>

<h2 id="steps-to-create">Steps to Create</h2>

<h3 id="image-aqusition-cleanup-and-svg-preparation">Image Aqusition, Cleanup, and SVG Preparation</h3>

<p>In the case of animal prints government websites are often a great source of information as there are several that compile track data for use of residents. Massachusetts, for example, <a href="https://www.mass.gov/news/get-to-know-animal-tracks">provides an animal tracks information page</a>. This was the one I primarily used for the project. For the below example and in my video <a href="https://fw.ky.gov/More/Documents/trackidguide[1].pdf">I used one from Kentuky</a>.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/3d-print/animal-tags/field-guide.webp" alt="Field Guide" style="padding: 20px; background-color: #FFF;" /></p>

<p>Taking a single track (the Elk) from the field guide:</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/3d-print/animal-tags/precleaned-up.webp" alt="Precleaned up Elk Track" style="padding: 20px; background-color: #FFF;" /></p>

<p>After cleaning in <a href="https://www.gimp.org/">GIMP</a> (a free image editor) removing the marks outside of the track:</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/3d-print/animal-tags/cleaned-up.webp" alt="Cleaned up Elk Track" style="padding: 20px; background-color: #FFF;" /></p>

<p>With the cleaned up image we can take it to <a href="https://ezgif.com/png-to-svg">EZGif’s PNG to SVG tool</a> to convert it:</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/3d-print/animal-tags/ezgif-png-to-svg.webp" alt="EZGif PNG to SVG" style="padding: 20px; background-color: #FFF;" /></p>

<p>After selecting options to better convert it:</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/3d-print/animal-tags/ezgif-conversion.webp" alt="EZGif Configuration" style="padding: 20px; background-color: #FFF;" /></p>

<p>After downloading the SVG we’ll switch to Fusion 360.</p>

<h3 id="fusion-360">Fusion 360</h3>

<p>With Fusion 360 there is a <a href="https://www.autodesk.com/products/fusion-360/personal">personal edition</a> which gives you a chance to learn the tooling if the following applies to you:</p>

<blockquote>
  <p>Autodesk Fusion for personal use is a limited, free version that includes basic functionality for qualifying users who generate less than $1,000 USD in annual revenue and use for home-based, non-commercial projects only.</p>
</blockquote>

<p>For this part of the project we’ll utilize the fusion 360 tooling to imprint the animal track into the 3D model and also to emboss text into the tag.</p>

<p>For the purposes of this guide the starting point is the blank animal tag:</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/3d-print/animal-tags/fusion-blank.webp" alt="Fusion 360 Blank Tag" style="padding: 20px; background-color: #FFF;" /></p>

<p>Once inside of the tool, in the Solid menu there’s the option to create a sketch. Once in the sketch view you can select the surface of the print and add sketch related elements.</p>

<p>From the insert menu there’s the option to “Insert SVG”:</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/3d-print/animal-tags/fusion-insert-menu.webp" alt="Insert SVG in Fusion 360" style="padding: 20px; background-color: #FFF;" /></p>

<p>With the SVG inserted you can scale and position it as needed until it’s in the proper location for extrusion. Once there click the button to finish the sketch you can then select the surfaces to extrude.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/3d-print/animal-tags/fusion-sketch-select.webp" alt="Fusion 360 Sketch Selection" style="padding: 20px; background-color: #FFF;" /></p>

<p>With a suitable distance set for extrusion you can cut into the surface:</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/3d-print/animal-tags/fusion-extrusion-cut.webp" alt="Fusion 360 Cut Surface" style="padding: 20px; background-color: #FFF;" /></p>

<p>The next step, if trying to emulate my design, is to create a sketch again and use the create text option to type out additional information for printing:</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/3d-print/animal-tags/fusion-add-text.webp" alt="Fusion 360 Add Text" style="padding: 20px; background-color: #FFF;" /></p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/3d-print/animal-tags/fusion-text.webp" alt="Fusion 360 Text" style="padding: 20px; background-color: #FFF;" /></p>

<p>For my own settings I used a 0.75mm extrusion for the text:</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/3d-print/animal-tags/fusion-extrude-text-join.webp" alt="Fusion 360 Extrude Text" style="padding: 20px; background-color: #FFF;" /></p>

<p>Finally with Fusion 360 you’ll need to export the model for printing:</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/3d-print/animal-tags/fusion-3d-print.webp" alt="Fusion 360 Print" style="padding: 20px; background-color: #FFF;" /></p>

<h3 id="prusaslicer">Prusaslicer</h3>

<p>With the model exported for printing it can imported into <a href="https://help.prusa3d.com/article/download-prusaslicer_2220">Prusaslicer</a> for printing (or any other similar software).</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/3d-print/animal-tags/prusa-import.webp" alt="Prusaslicer Import" style="padding: 20px; background-color: #FFF;" /></p>

<p>If you want a similar effect with the color change I did you can add a color change to the print. With my i3 MK3S the firmware pauses at this point and begins beeping alerting me to perform the change.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/3d-print/animal-tags/prusa-color-change.webp" alt="Prusaslicer Color Change" style="padding: 20px; background-color: #FFF;" /></p>

<p>Once that is complete you can print the model. I think they turned out well.</p>

<figure class="third full">
  
    
      <a href="/assets/images/projects/3d-print/animal-tags/makes-gallery-1.webp" title="Animal Tag Make Gallery 1">
          <img src="/assets/images/projects/3d-print/animal-tags/makes-gallery-1.webp" alt="Animal Tag Make Gallery 1" />
      </a>
    
  
    
      <a href="/assets/images/projects/3d-print/animal-tags/makes-gallery-2.webp" title="Animal Tag Make Gallery 2">
          <img src="/assets/images/projects/3d-print/animal-tags/makes-gallery-2.webp" alt="Animal Tag Make Gallery 2" />
      </a>
    
  
    
      <a href="/assets/images/projects/3d-print/animal-tags/makes-gallery-3.webp" title="Animal Tag Make Gallery 3">
          <img src="/assets/images/projects/3d-print/animal-tags/makes-gallery-3.webp" alt="Animal Tag Make Gallery 3" />
      </a>
    
  
  
    <figcaption>Printed Animal Tags
</figcaption>
  
</figure>

<h2 id="closing-thoughts">Closing Thoughts</h2>

<p>This approach works well for black and white images. I think having 3D printed objects also helps people visualize how the tracks look and it gives a great way to compare while out in the wild.</p>]]></content><author><name>Cosmic Bee</name></author><category term="3d-printing" /><category term="Project" /><category term="3D Printing" /><summary type="html"><![CDATA[Approach to 3D Printed Animal Track Tags]]></summary></entry><entry><title type="html">Grove Vision V2 Powered Pool Alert</title><link href="https://www.cranberrygrape.com/mini-projects/pool-alerter/" rel="alternate" type="text/html" title="Grove Vision V2 Powered Pool Alert" /><published>2024-06-29T00:36:00-04:00</published><updated>2024-06-29T00:36:00-04:00</updated><id>https://www.cranberrygrape.com/mini-projects/pool-alerter</id><content type="html" xml:base="https://www.cranberrygrape.com/mini-projects/pool-alerter/"><![CDATA[<p>This project utilizes the Seeed Vision AI V2 sensor in an enclosed manner with use of a solar panel to ensure the device stays powered. For this project I used the pretrained person sensor model with the goal of detecting my kids if they enter the pool area.</p>

<!-- Courtesy of embedresponsively.com -->

<div class="responsive-video-container">
    <iframe src="https://www.youtube-nocookie.com/embed/Tcoqa2HVCks" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
  </div>

<h2 id="model-setup">Model Setup</h2>

<p>The first step is to get your vision AI sensor configured for the model. After some testing I found the Swift YOLO model effective for person detection.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/seeed/pool-alerter/sensecraft-ai.webp" alt="Vision AI V2 Sensor Setup" style="padding: 20px; background-color: #FFF;" /></p>

<p>The process to get setup is fairly straightforward:</p>
<ul>
  <li>Using Chrome or Microsoft Edge Visit the <a href="https://seeed-studio.github.io/SenseCraft-Web-Toolkit">SenseCraft Web Toolkit</a></li>
  <li>Connect to your device via the “Connect” button</li>
  <li>A web serial popup will appear allowing you to connect to your device</li>
  <li>Select a model from the available and send to the device</li>
</ul>

<p>In the case of a self trained model there are additional steps required as you need to train it using the SSCMA library and associated notebooks (at the time of this writing). For this project though we’re using a built in model so with this step we’re done.</p>

<h2 id="telegram-setup">Telegram Setup</h2>

<p>For the telegram setup you can refer to the <a href="https://wiki.seeedstudio.com/grove_vision_ai_v2_telegram/">Seeed Wiki article</a> on the topic. I’ll go over the general steps here.</p>

<ul>
  <li>Contact the <a href="https://telegram.me/BotFather">BotFather</a> sending the message <code class="language-plaintext highlighter-rouge">/newbot</code> to initiate the bot creation process</li>
  <li>Respond to the messages from the BotFather giving your bot a name and username</li>
  <li>After this the bot will respond with a token you can use for interfacing with the API</li>
  <li>Adding this bot to a group will associate a chat id with your user and the bot (until this step is taken the response will not contain a chat id)</li>
  <li>Hitting the endpoint <code class="language-plaintext highlighter-rouge">https://api.telegram.org/bot{Token}/getUpdates</code> after replacing the <code class="language-plaintext highlighter-rouge">{Token}</code> with your token will return a payload that includes your chat id</li>
  <li>After you’ve received this chat id you can subsequently send messages to the bot via the <code class="language-plaintext highlighter-rouge">/sendMessage</code> endpoint</li>
</ul>

<p>With cURL you can send a message directly to the bot via:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl https://api.telegram.org/bot<span class="o">{</span>Token<span class="o">}</span>/sendMessage <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> POST <span class="nt">--data</span> <span class="s1">'{"chat_id": 7216726720, "text":"Test Message"}'</span>
</code></pre></div></div>

<h2 id="components--circuit-setup">Components / Circuit Setup</h2>

<p>I’ve used the following components with this setup (affiliate links):</p>

<ul>
  <li><a href="https://www.cranberrygrape.com/mini-projects/grove-level-shifter/">DIY Level Shifter</a></li>
  <li><a href="https://www.seeedstudio.com/Grove-Vision-AI-Module-V2-p-5851.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Grove Vision AI V2 Sensor</a></li>
  <li><a href="https://www.seeedstudio.com/Seeed-XIAO-ESP32C3-p-5431.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Xiao ESP32C</a></li>
  <li><a href="https://www.seeedstudio.com/OV5647-69-1-FOV-Camera-module-for-Raspberry-Pi-3B-4B-p-5484.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">FOV Camera for RP</a></li>
  <li><a href="https://amzn.to/45HUdFP">90 Degree USB Connector</a></li>
  <li><a href="https://amzn.to/3RMGAzo">Enclosure</a></li>
  <li><a href="https://www.seeedstudio.com/Grove-Shield-for-Seeeduino-XIAO-p-4621.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Grove Shield for Xiao</a></li>
  <li><a href="https://www.seeedstudio.com/Grove-LED-Bar.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Grove LED Bar</a></li>
  <li><a href="https://www.seeedstudio.com/1W-Solar-Panel-80X100.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Solar Panel</a></li>
  <li><a href="https://www.seeedstudio.com/Grove-Buzzer.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Buzzer</a></li>
  <li><a href="https://www.seeedstudio.com/LiPo-Rider-Pro.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Lipo Rider Pro</a></li>
</ul>

<p>It’s a mostly no solder setup (aside from the DIY Level Shifter). I’ll walk through the connections here to help make the setup easier to follow. I suggest VHB tape from 3M for mounting as it holds well.</p>

<ul>
  <li>First take the Lipo Rider Pro and position it such that there’s enough room for the 90 degree connector, connect the 90 degree connector</li>
  <li>Attach the solar panel via its connector</li>
  <li>Attach a lithium ion battery to its connector</li>
  <li>Attach the Xiao ESP32C3 to the Grove Shield and break off the extra HY 2.0 4P connectors (for this enclosure I could not fit the full shield)</li>
  <li>With the Xiao ESP32C3 now connected attach it to the 90 degree connector via the USB-C connection</li>
  <li>Connect the buzzer to the A1 A0 grove connector via a Grove cable (A1 is not used in this case as the Buzzer only uses the 4th pin)</li>
  <li>Connect the LED bar to the A2 A1 grove connector via a Grove cable (both pins are used here)</li>
  <li>You will need to level shift for the Grove Vision AI V2 (it seems to work with 3V3 but the wiki states it only takes 5V so likely not a good idea – the Grove connection leads to a 3V3 regulator and expects 5V from the schematic)</li>
  <li>For the level shifting connect SDA and SCL to the LV#s side of a level shifter, connect the LV to 3V3, GND to GND. On the HV side connect the HV to the 5V / VBUS from the Grove Shield extra pins, then connect the corresponding SDA and SCL wires leading to the Grove Vision AI V2 along with the GND wire. To make this process simpler I setup a DIY inline level shifter circuit I described <a href="https://www.cranberrygrape.com/mini-projects/grove-level-shifter/">in another article</a>. I also designed a Xiao Level Shifting base board that’s on the way – hoping it simplifies future projects.</li>
  <li>With the connections in place use the VHB tape to prevent movement in the enclosure for most of the parts and electrical tape to hold the LED bar, solar panel, and camera to the roof.</li>
</ul>

<p>So yeah it sounds mighty complicated but it really isn’t it’s just of Grove cables connecting various sensors to the device. I really don’t like soldering components in general (outside of individual pieces) as it limits future project reuse so I love situations like this where you can rely on the existing expansion boards to piece together a full device.</p>

<p>Given this project is relying on the Grove cables I’m not going to include a Fritzing image of the circuit. The level shifter circuit used is included in my other article which delves into how it works and provides a link to Falstad’s circuit tester where you can try it in action (really appreciate that site).</p>

<h2 id="code">Code</h2>

<p>For the code you’ll first want to setup Arduino for use with the Espressif boards. To do this you’ll need to:</p>

<ul>
  <li>Press <code class="language-plaintext highlighter-rouge">File</code> from the menu bar and press <code class="language-plaintext highlighter-rouge">Preferences</code> from the list</li>
  <li>Near the bottom of the screen you’ll see an area for additional board management URLs to be added. Here you’ll want to add <code class="language-plaintext highlighter-rouge">https://espressif.github.io/arduino-esp32/package_esp32_index.json</code> to the comma separated list (it may be blank if this is the first non-Arduino board you’re configuring)</li>
  <li>After installing go to the boards submenu from the left-hand side bar selection menu</li>
  <li>Type in “Espressif” in the search bar</li>
  <li>You’ll see a <code class="language-plaintext highlighter-rouge">esp32</code> board option, install this to setup the associated esp-tool tooling and related boards</li>
  <li>In the code screen pull down the board select and type in “Xiao ESP32C3” it should return the associated board</li>
  <li>For the libraries you’ll need to add the <a href="https://github.com/Seeed-Studio/Grove_LED_Bar/tree/1.0.1">1.0.1 version of the LED Bar from GitHub</a> as it hasn’t been published at this time</li>
  <li>In addition the SSCMA library should be installed</li>
</ul>

<p>The entire code for this example can be found with <a href="https://gist.github.com/Timo614/8ec3dec5ac49887a698191177b410417">my gist</a>. I’ll go over some of the associated code but it’s pretty simple.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Threshold for detection</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">threshold</span> <span class="o">=</span> <span class="mi">70</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">dataPin</span> <span class="o">=</span> <span class="n">D2</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">clockPin</span> <span class="o">=</span> <span class="n">D1</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">buzzerPin</span> <span class="o">=</span> <span class="n">D0</span><span class="p">;</span>
</code></pre></div></div>

<p>Here I configured the associated pins for the LED bar, the buzzer’s pin, and the threshold needed to trigger the telegram alert.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">SSCMA</span> <span class="n">AI</span><span class="p">;</span>
<span class="n">Grove_LED_Bar</span> <span class="nf">bar</span><span class="p">(</span><span class="n">dataPin</span><span class="p">,</span> <span class="n">clockPin</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">LED_BAR_10</span><span class="p">);</span>
</code></pre></div></div>

<p>Creating the associated objects for interfacing and controlling the LED bar.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">AI</span><span class="p">.</span><span class="n">begin</span><span class="p">();</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">9600</span><span class="p">);</span>
    <span class="n">WiFi</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">password</span><span class="p">);</span> 
    <span class="k">while</span> <span class="p">(</span><span class="n">WiFi</span><span class="p">.</span><span class="n">status</span><span class="p">()</span> <span class="o">!=</span> <span class="n">WL_CONNECTED</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">delay</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>
      <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"Connecting to WiFi..."</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"Connected"</span><span class="p">);</span>

    <span class="n">bar</span><span class="p">.</span><span class="n">begin</span><span class="p">();</span>
    <span class="n">pinMode</span><span class="p">(</span><span class="n">buzzerPin</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>  <span class="c1">// Buzzer on D0</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The setup logic connects to the WiFi, retries until it connects, and then associates the buzzer pin as an output.</p>

<p>Each iteration of the loop it resets the count of people detected and the highest confidence found.</p>

<p>After that:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">AI</span><span class="p">.</span><span class="n">invoke</span><span class="p">())</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">AI</span><span class="p">.</span><span class="n">boxes</span><span class="p">().</span><span class="n">size</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">boxes</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">score</span> <span class="o">&gt;</span> <span class="n">threshold</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">peopleDetected</span><span class="o">++</span><span class="p">;</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">AI</span><span class="p">.</span><span class="n">boxes</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">score</span> <span class="o">&gt;</span> <span class="n">highestConfidence</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">highestConfidence</span> <span class="o">=</span> <span class="n">AI</span><span class="p">.</span><span class="n">boxes</span><span class="p">()[</span><span class="n">i</span><span class="p">].</span><span class="n">score</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>It invokes the model and if that was successful then it loops over the boxes returned counting those that had a score higher than the threshold set and also what the highest confidence seen was.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="c1">// Update LED bar</span>
    <span class="kt">int</span> <span class="n">ledsToLight</span> <span class="o">=</span> <span class="n">map</span><span class="p">(</span><span class="n">highestConfidence</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
    <span class="n">bar</span><span class="p">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">ledsToLight</span><span class="p">);</span>
</code></pre></div></div>

<p>Here it uses this highest confidence to light up the LED bar. As I set the alerting confidence to 70% it will show 7 to 10 (likely never 10 though) lights depending upon the score. One could modify the above loop to move the confidence logic outside of the threshold check to view it from 0 to 10 but for my purposes I only wanted it showing when it alerted.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">if</span> <span class="p">(</span><span class="n">peopleDetected</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"People detected"</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">WiFi</span><span class="p">.</span><span class="n">status</span><span class="p">()</span> <span class="o">==</span> <span class="n">WL_CONNECTED</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">String</span> <span class="n">message</span> <span class="o">=</span> <span class="s">"People Detected: "</span> <span class="o">+</span> <span class="n">String</span><span class="p">(</span><span class="n">peopleDetected</span><span class="p">)</span> <span class="o">+</span> <span class="s">"\Confidence: "</span> <span class="o">+</span> <span class="n">String</span><span class="p">(</span><span class="n">highestConfidence</span><span class="p">);</span>
            <span class="n">sendMessage</span><span class="p">(</span><span class="n">message</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="c1">// Trigger buzzer</span>
        <span class="n">digitalWrite</span><span class="p">(</span><span class="n">buzzerPin</span><span class="p">,</span> <span class="n">HIGH</span><span class="p">);</span>
        <span class="n">delay</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>  <span class="c1">// Buzzer delay</span>
        <span class="n">digitalWrite</span><span class="p">(</span><span class="n">buzzerPin</span><span class="p">,</span> <span class="n">LOW</span><span class="p">);</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>If people were detected in this iteration of the loop it then sends a message to telegram to trigger the alert.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">sendMessage</span><span class="p">(</span><span class="n">String</span> <span class="n">text</span><span class="p">)</span> <span class="p">{</span>
  <span class="n">String</span> <span class="n">url</span> <span class="o">=</span> <span class="s">"https://api.telegram.org/bot"</span> <span class="o">+</span> <span class="n">botToken</span> <span class="o">+</span> <span class="s">"/sendMessage"</span><span class="p">;</span>
  <span class="n">String</span> <span class="n">payload</span> <span class="o">=</span> <span class="s">"{</span><span class="se">\"</span><span class="s">chat_id</span><span class="se">\"</span><span class="s">:</span><span class="se">\"</span><span class="s">"</span> <span class="o">+</span> <span class="n">chatId</span> <span class="o">+</span> <span class="s">"</span><span class="se">\"</span><span class="s">,</span><span class="se">\"</span><span class="s">text</span><span class="se">\"</span><span class="s">:</span><span class="se">\"</span><span class="s">"</span> <span class="o">+</span> <span class="n">text</span> <span class="o">+</span> <span class="s">"</span><span class="se">\"</span><span class="s">}"</span><span class="p">;</span>
  
  <span class="n">HTTPClient</span> <span class="n">http</span><span class="p">;</span>
  <span class="n">http</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="n">url</span><span class="p">);</span>
  <span class="n">http</span><span class="p">.</span><span class="n">addHeader</span><span class="p">(</span><span class="s">"Content-Type"</span><span class="p">,</span> <span class="s">"application/json"</span><span class="p">);</span>
  
  <span class="kt">int</span> <span class="n">statusCode</span> <span class="o">=</span> <span class="n">http</span><span class="p">.</span><span class="n">POST</span><span class="p">(</span><span class="n">payload</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">statusCode</span> <span class="o">==</span> <span class="mi">200</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"Message sent successfully!"</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"Failed to send message."</span><span class="p">);</span>
  <span class="p">}</span>
  <span class="n">http</span><span class="p">.</span><span class="n">end</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Here the logic puts everything together from the BotFather. Using the token it provided earlier you can hit the <code class="language-plaintext highlighter-rouge">/sendMessage</code> API with the payload containing the chatId and associated text for the message. This sends the message to the end user via that chat.</p>

<h2 id="final-thoughts">Final Thoughts</h2>

<p>In general I found this approach to work well; the components fit within the case, I’m able to get 5V from the lipo rider pro which I need for the Vision AI V2 sensor, and the telegram approach is effective for community.</p>]]></content><author><name>Cosmic Bee</name></author><category term="mini-projects" /><category term="Project" /><category term="PCB" /><category term="Grove" /><category term="Seeed Studio" /><summary type="html"><![CDATA[Mobile pool alert system relying on Grove Vision AI V2 Sensor]]></summary></entry><entry><title type="html">DIY Grove Level Shifter</title><link href="https://www.cranberrygrape.com/mini-projects/grove-level-shifter/" rel="alternate" type="text/html" title="DIY Grove Level Shifter" /><published>2024-06-28T06:41:00-04:00</published><updated>2024-06-28T06:41:00-04:00</updated><id>https://www.cranberrygrape.com/mini-projects/grove-level-shifter</id><content type="html" xml:base="https://www.cranberrygrape.com/mini-projects/grove-level-shifter/"><![CDATA[<p>This is a simple DIY project involving a bidirectional level shifter. These are fairly cheap boards that use n-channel mosfets (BSS138) to shift a signal up and down.</p>

<!-- Courtesy of embedresponsively.com -->

<div class="responsive-video-container">
    <iframe src="https://www.youtube-nocookie.com/embed/43s97BrVkDk" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
  </div>

<p>You can <a href="https://www.falstad.com/circuit/circuitjs.html?ctz=CQAgjCAMB0l3BWcMBMcUHYMGZIA4UA2ATmIxAUgoqoQFMBaMMAKADdxCrttDOrMKKMIAs3aNmFUYCdiB58F83iEFSQY6tKjRZAc2WKVS3niksDYLof7zCZ6SwBmqzWAx8UmtdiFhdOpAoLABOrlTufFYRHsJgcKG2aiheqhhCEfCQiSgInuk2apnwLAAyqqnJqbgi6k4AhgA2AM508hJQLACyqnlpQrmKkLUCuiwA7raRSQXZk2IxfAv9nT0ihLVq67U1wihj5SLE+UJHQyPCDS1tSNlhZysIsUXIJfdwK9srxQn3eIsaD7TYrODT-cCxETgnx+AIwIIWQHcFTLUzmAxPIx8TF2BydDg4pQ4l5UTS3KRjDiolEfEmA9qSbQyFhAA">test the general idea behind the circuit here (falstad)</a>. The two circuits shown are identical in that link aside from a change to the input and output to allow for both directions to be tested.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/seeed/grove-level-shifter/level-shift-circuit.webp" alt="Level Shift Circuit" style="padding: 20px; background-color: #FFF;" /></p>

<p>I believe this works by (my understanding came from this <a href="https://www.reddit.com/r/AskElectronics/comments/npwl0t/comment/h0c2sfj/">great reddit answer</a>):</p>
<ul>
  <li>When the LV side is LOW it causes a Vgs of 3V3 which is more than the 1V5 needed to fully activate the mosfet. Once activated it causes current to flow and pulls the HV side down as well. When it’s high the Vgs is 0 so current doesn’t conduct and with the pull up resistors both sides are kept at their respected voltage.</li>
  <li>When the HV side is brought LOW it causes the diode to become reverse biased (from what I’ve gathered) which in turn allows the body diode to allow current through it. This in turn causes the source voltage to drop in turn causing the Vgs to climb until the mosfet is fully activated and the LV side is also brought fully to LOW.</li>
</ul>

<h2 id="purpose">Purpose</h2>

<p>A level shifter allows your microcontroller which tend to use 3V3 to interface with actuators and sensors that require a higher voltage. Sometimes a sensor may work with the lower voltage but just isn’t as accurate other times it may not run at all at the voltage level if it’s below a certain point. For example the <a href="https://www.seeedstudio.com/Grove-Water-Atomization-v1-0.html">Seeed Water Atomization module</a> operates at 5V putting this module out of reach for most microcontrollers without a level shifter in the mix.</p>

<h2 id="using-a-bidirectional-level-shifter">Using a Bidirectional Level Shifter</h2>

<p>Bidirectional level shifters are fairly easy to use but quickly make a project a mess given the need for several wires. At their simplest setup you place wires for each signal line you want to shift to a LV# pin on the shifter. Their corresponding HV# pin is then connected to whatever device you wanted to utilize. The LV pin is associated with the lower voltage while the HV pin is associated with the higher voltage. In addition a common ground must be used between the two voltage sources.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/seeed/grove-level-shifter/level-shift-fritzing-schematic.webp" alt="Level Shift Fritzing Schematic" style="padding: 20px; background-color: #FFF;" /></p>

<p>Often times MCUs will provide a pin for the VBUS line which comes from the USB. One could use this as the HV side and have the LV side be the 3V3 from the microcontroller to level shift up to that level. If you need a lot of current a separate 5V voltage source would be ideal over using this though as your microcontroller will also require current to operate and USB 2.0 only allows for up to 500mA. USB PD allows for up to 3A@5V so there are a lot more possibilities when you bring something like a CH224K from WCH into the mix to negotiate for USB PD.</p>

<h2 id="grove-level-shifting-board">Grove Level Shifting Board</h2>

<p>For this project I put together a little Grove level shifting board that allows for no solder level shifting. It’s just a circuit that uses the Adafruit Bidirectional Level shifter, 2 <a href="https://www.lcsc.com/products/Wire-To-Board-Connector_11068.html">HY 2.0 4P connectors</a>, a pin for the HV level, and then some wires to get it all in place.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/seeed/grove-level-shifter/level-shift-fritzing.webp" alt="Level Shift Fritzing" style="padding: 20px; background-color: #FFF;" /></p>

<p>For this setup it’s really basic you just want to make sure that you have the <a href="https://www.lcsc.com/products/Wire-To-Board-Connector_11068.html">HY 2.0 4P</a> connectors setup to the correct LV# and HV# pins on each side. The rest is really just routing the wires correctly per the above.</p>

<p>The end result is you can connect a Grove cable on one end with 3V3 and 3V3 signals and on the other end get 5V and 5V signals out another Grove cable with the help of one dupont cable to the 5V HV. This in turn ends up giving you a much smaller setup in terms of cables for shifting.</p>

<h2 id="testing-code-used-in-the-video">Testing Code Used in the Video</h2>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Initialize the Grove LED Button (D6, D7 signals)</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">ledPin</span> <span class="o">=</span> <span class="n">D7</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">buttonPin</span> <span class="o">=</span> <span class="n">D6</span><span class="p">;</span>
<span class="c1">// Atomization module is on D0</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">enablePin</span> <span class="o">=</span> <span class="n">D0</span><span class="p">;</span>

<span class="c1">// Initial state in the off position</span>
<span class="kt">bool</span> <span class="n">enabled</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="kt">bool</span> <span class="n">buttonState</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="kt">bool</span> <span class="n">lastButtonState</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// The three pins are setup with the button as an input and others as output</span>
  <span class="n">pinMode</span><span class="p">(</span><span class="n">ledPin</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
  <span class="n">pinMode</span><span class="p">(</span><span class="n">enablePin</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
  <span class="n">pinMode</span><span class="p">(</span><span class="n">buttonPin</span><span class="p">,</span> <span class="n">INPUT</span><span class="p">);</span>
  
  <span class="c1">// LED and atomization enable are pulled low to set initial state</span>
  <span class="n">digitalWrite</span><span class="p">(</span><span class="n">ledPin</span><span class="p">,</span> <span class="n">LOW</span><span class="p">);</span>
  <span class="n">digitalWrite</span><span class="p">(</span><span class="n">enablePin</span><span class="p">,</span> <span class="n">LOW</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">loop</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// Check if button has been driven low</span>
  <span class="n">buttonState</span> <span class="o">=</span> <span class="n">digitalRead</span><span class="p">(</span><span class="n">buttonPin</span><span class="p">)</span> <span class="o">==</span> <span class="n">LOW</span><span class="p">;</span>
  
  <span class="c1">// If the button has changed its state</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">buttonState</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">lastButtonState</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Toggle </span>
    <span class="n">enabled</span> <span class="o">=</span> <span class="o">!</span><span class="n">enabled</span><span class="p">;</span>
    
    <span class="n">digitalWrite</span><span class="p">(</span><span class="n">ledPin</span><span class="p">,</span> <span class="n">enabled</span> <span class="o">?</span> <span class="n">HIGH</span> <span class="o">:</span> <span class="n">LOW</span><span class="p">);</span>
    <span class="n">digitalWrite</span><span class="p">(</span><span class="n">enablePin</span><span class="p">,</span> <span class="n">enabled</span> <span class="o">?</span> <span class="n">HIGH</span> <span class="o">:</span> <span class="n">LOW</span><span class="p">);</span>
  <span class="p">}</span>
  
  <span class="n">lastButtonState</span> <span class="o">=</span> <span class="n">buttonState</span><span class="p">;</span>
  
  <span class="n">delay</span><span class="p">(</span><span class="mi">50</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="my-level-up-xiao-board">My Level Up Xiao Board</h2>

<p>My little level up Xiao board is coming soon!</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/seeed/grove-level-shifter/level-up-board.webp" alt="Level Up Board" style="padding: 20px; background-color: #FFF;" /></p>

<p>I’ve ordered it from <a href="https://pcbway.com/g/9mk3Jz">PCBWay</a> (thanks Serene!) and it should be coming in the next week or so for testing.</p>

<p>I’ll go into more detail once the board arrives but I wanted to take the elements of the level shifter and make it a permanent feature of my helper board.</p>

<p>I’m a big fan of <a href="https://www.seeedstudio.com/Grove-Shield-for-Seeeduino-XIAO-p-4621.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Seeed Studio Grove Base for XIAO (affiliate link)</a>. The board makes it very easy to get up and running with a project with little to no soldering. It has pins for an extra row of the main headers allowing for dupont wires to connect to any of those, comes with a battery bonding pad and charging circuit allowing for use with the Xiao without onboard battery ICs (RP2040 and SAMD21), and has an optional flash bonding pad allowing for expandability. In addition to those features it has a tons of HY 2.0 4P connectors allowing you to connect Grove modules easily. Given that I wanted to create a board that used the elements I relied on the most with it.</p>

<p>For the first iteration of my board I decided:</p>
<ul>
  <li>Xiao pinout allowing for use with the <a href="https://www.seeedstudio.com/xiao-series-page?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Seeed’s Xiao line of boards (affiliate link)</a> and Adafruit’s QT Py</li>
  <li>I wanted at least two Grove inputs (HY 2.0 4P) with one as I2C and the other UART</li>
  <li>I wanted the ability to optionally set either to 5V or have it still default to 3V3</li>
  <li>Given the I2C bus is easily expanded with a <a href="https://www.seeedstudio.com/Grove-I2C-Hub-6-Port-p-4349.html?sensecap_affiliate=vkN9MXE&amp;referring_service=link">Grove I2C Hub (affiliate link)</a> I didn’t want to overload the board with I2C ports</li>
  <li>Allow for expandability via the matching pins to the Xiao pinout</li>
</ul>]]></content><author><name>Cosmic Bee</name></author><category term="mini-projects" /><category term="Project" /><category term="PCB" /><category term="Grove" /><category term="Seeed Studio" /><summary type="html"><![CDATA[Project Showing No Solder Solution to Grove to Grove Level Shifting]]></summary></entry><entry><title type="html">Robot Car With Vision Sensor V2 Face Control</title><link href="https://www.cranberrygrape.com/mini-projects/face-controlled-robot/" rel="alternate" type="text/html" title="Robot Car With Vision Sensor V2 Face Control" /><published>2024-06-24T13:41:00-04:00</published><updated>2024-06-24T13:41:00-04:00</updated><id>https://www.cranberrygrape.com/mini-projects/face-controlled-robot</id><content type="html" xml:base="https://www.cranberrygrape.com/mini-projects/face-controlled-robot/"><![CDATA[<p>For this project I’m utilizing <a href="https://www.seeedstudio.com/Grove-Vision-AI-Module-V2-p-5851.html">Seeed Vision Module V2</a> as the control mechanism for the <a href="https://www.adeept.com/adeept-4wd-omni-directional-mecanum-wheels-robotic-car-kit-for-esp32-s3-banana-pi-picow-s3-diy-stem-remote-controlled-educational-robot-kit_p0406_s0086.html">Adeept Omni-directional Mecanum Wheels car kit</a>. I’ve created a <a href="https://www.printables.com/model/922657-seeed-vision-sensor-v2-mount-for-adeept-omni-direc">Printables model</a> that can be downloaded and printed to attach the sensor to the device.</p>

<!-- Courtesy of embedresponsively.com -->

<div class="responsive-video-container">
    <iframe src="https://www.youtube-nocookie.com/embed/AwGuaZD-V2Y" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
  </div>

<h2 id="prerequisites">Prerequisites</h2>

<p>For more information about the robot car kit you can check my <a href="https://www.cranberrygrape.com/mini-projects/adeept-omni-car/">article on the lessons from the Adeept tutorials with the car</a>.</p>

<h2 id="background">Background</h2>

<p>For this project I’ve created Arduino libraries to control the car and its features. I did this such that I could use the SSCMA library from Seeed Studio. I contemplated porting it to CircuitPython but in the end decided this would be a good learning opportunity.</p>

<h2 id="code">Code</h2>

<p>The code can be found <a href="https://github.com/Timo614/robot-car-picow-s3/">here</a>. There are three directories that are self contained with the associated Arduino code. I included my test projects along with the final car control logic in that repository.</p>

<p>This code is written in C++ for Arduino. You’ll need the Espressif core board package installed with Arduino. Banana Pi does not include a board specifically for this in the board package so you need to use the “ESP32S3 Dev Module” instead. This also means you’ll need to use the GPIO pins from the ESP32S3 directly versus the silkscreen pinout from the Banana Pi PicoW S3.</p>

<table>
  <thead>
    <tr>
      <th>Silkscreen GPIO</th>
      <th>ESP32S3 GPIO</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0 / TX</td>
      <td>43</td>
    </tr>
    <tr>
      <td>1 / RX</td>
      <td>44</td>
    </tr>
    <tr>
      <td>2</td>
      <td>47</td>
    </tr>
    <tr>
      <td>3</td>
      <td>17</td>
    </tr>
    <tr>
      <td>4</td>
      <td>15</td>
    </tr>
    <tr>
      <td>5</td>
      <td>13</td>
    </tr>
    <tr>
      <td>6</td>
      <td>12</td>
    </tr>
    <tr>
      <td>7</td>
      <td>14</td>
    </tr>
    <tr>
      <td>8</td>
      <td>18</td>
    </tr>
    <tr>
      <td>9</td>
      <td>16</td>
    </tr>
    <tr>
      <td>10</td>
      <td>21</td>
    </tr>
    <tr>
      <td>11</td>
      <td>38</td>
    </tr>
    <tr>
      <td>12</td>
      <td>39</td>
    </tr>
    <tr>
      <td>13</td>
      <td>40</td>
    </tr>
    <tr>
      <td>14</td>
      <td>41</td>
    </tr>
    <tr>
      <td>15</td>
      <td>42</td>
    </tr>
    <tr>
      <td>16</td>
      <td>1</td>
    </tr>
    <tr>
      <td>17</td>
      <td>2</td>
    </tr>
    <tr>
      <td>18</td>
      <td>3</td>
    </tr>
    <tr>
      <td>19</td>
      <td>4</td>
    </tr>
    <tr>
      <td>20 / SDA</td>
      <td>5</td>
    </tr>
    <tr>
      <td>21 / SCL</td>
      <td>6</td>
    </tr>
    <tr>
      <td>22</td>
      <td>7</td>
    </tr>
    <tr>
      <td>26</td>
      <td>8</td>
    </tr>
    <tr>
      <td>27</td>
      <td>9</td>
    </tr>
    <tr>
      <td>28</td>
      <td>10</td>
    </tr>
    <tr>
      <td>run</td>
      <td>rst</td>
    </tr>
  </tbody>
</table>

<p>For getting Arduino configured <a href="https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html#installing">you can see the guide from Espressif</a>. The short of it is you need to add the board support package located at <code class="language-plaintext highlighter-rouge">https://espressif.github.io/arduino-esp32/package_esp32_index.json</code> and then in the boards tab type “Espressif” to find and install.</p>

<p>For the configuration options in Arduino I relied on the <a href="https://github.com/BPI-STEAM/BPI-Leaf-S3-Doc/tree/main/Example/Arduino#board-selection-and-configuration">Banana Pi Leaf S3 related documentation</a> which worked in this case as well.</p>

<p><img src="https://www.cranberrygrape.com/assets/images/projects/seeed/face-recognition/picow-s3-board-configuration.webp" alt="PicoW S3 Board Configuration" style="padding: 20px; background-color: #FFF;" /></p>

<h3 id="motorcontroltest">MotorControlTest</h3>

<p>Initially I was thinking of using the ultrasonic sensor to detect the range of the person in front of the vehicle to aid with the distance tracking. As such I added support for it in this example but I didn’t end up using it in the final code.</p>

<p>I’ll hit some of the elements here to give some information:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define SDA_PIN 5
#define SCL_PIN 6
#define NUM_LEDS 4
#define DATA_PIN 38
#define BUZZER_PIN 8
</span>
<span class="c1">// Define trigger pin as 17 and echo pin as 47</span>
<span class="n">Ultrasonic</span> <span class="nf">ultrasonic</span><span class="p">(</span><span class="mi">17</span><span class="p">,</span> <span class="mi">47</span><span class="p">);</span> 
<span class="n">Adafruit_NeoPixel</span> <span class="n">strip</span> <span class="o">=</span> <span class="n">Adafruit_NeoPixel</span><span class="p">(</span><span class="n">NUM_LEDS</span><span class="p">,</span> <span class="n">DATA_PIN</span><span class="p">,</span> <span class="n">NEO_GRB</span> <span class="o">+</span> <span class="n">NEO_KHZ800</span><span class="p">);</span>

<span class="n">ServoControl</span> <span class="nf">servoControl</span><span class="p">(</span><span class="mi">14</span><span class="p">);</span> 
<span class="n">MotorControl</span> <span class="n">motorControl</span><span class="p">;</span>
<span class="n">I2C_LCD</span> <span class="nf">lcd</span><span class="p">(</span><span class="mh">0x27</span><span class="p">);</span>
</code></pre></div></div>

<p>The logic initializes and configures the different elements using their GPIO pins (versus the silkscreen).</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">playTone</span><span class="p">(</span><span class="kt">int</span> <span class="n">pin</span><span class="p">,</span> <span class="kt">int</span> <span class="n">frequency</span><span class="p">,</span> <span class="kt">int</span> <span class="n">duration</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">period</span> <span class="o">=</span> <span class="mi">1000000L</span> <span class="o">/</span> <span class="n">frequency</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">pulse</span> <span class="o">=</span> <span class="n">period</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">long</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">duration</span> <span class="o">*</span> <span class="mi">1000L</span><span class="p">;</span> <span class="n">i</span> <span class="o">+=</span> <span class="n">period</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">digitalWrite</span><span class="p">(</span><span class="n">pin</span><span class="p">,</span> <span class="n">HIGH</span><span class="p">);</span>
        <span class="n">delayMicroseconds</span><span class="p">(</span><span class="n">pulse</span><span class="p">);</span>
        <span class="n">digitalWrite</span><span class="p">(</span><span class="n">pin</span><span class="p">,</span> <span class="n">LOW</span><span class="p">);</span>
        <span class="n">delayMicroseconds</span><span class="p">(</span><span class="n">pulse</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">playAlertSound</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">frequencies</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">370</span><span class="p">,</span> <span class="mi">415</span><span class="p">,</span> <span class="mi">330</span><span class="p">,</span> <span class="mi">233</span><span class="p">,</span> <span class="mi">311</span><span class="p">};</span>
    <span class="kt">int</span> <span class="n">durations</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">50</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">300</span><span class="p">};</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">5</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">playTone</span><span class="p">(</span><span class="n">BUZZER_PIN</span><span class="p">,</span> <span class="n">frequencies</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">durations</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
    <span class="p">}</span>
    <span class="n">digitalWrite</span><span class="p">(</span><span class="n">BUZZER_PIN</span><span class="p">,</span> <span class="n">LOW</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>There’s logic which attempts to play a sound on alert here. I tried to make it sound similar to the Metal Gear Solid alert noise but it was hard with a buzzer.</p>

<p>Inside the setup method:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">delay</span><span class="p">(</span><span class="mi">5000</span><span class="p">);</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">9600</span><span class="p">);</span>

    <span class="n">pinMode</span><span class="p">(</span><span class="n">BUZZER_PIN</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>

    <span class="n">strip</span><span class="p">.</span><span class="n">begin</span><span class="p">();</span>
    <span class="n">strip</span><span class="p">.</span><span class="n">show</span><span class="p">();</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">NUM_LEDS</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">strip</span><span class="p">.</span><span class="n">setPixelColor</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">strip</span><span class="p">.</span><span class="n">Color</span><span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">));</span>
    <span class="p">}</span>
    <span class="n">strip</span><span class="p">.</span><span class="n">show</span><span class="p">();</span>

    <span class="n">Wire</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="n">SDA_PIN</span><span class="p">,</span> <span class="n">SCL_PIN</span><span class="p">);</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>

    <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Starting..."</span><span class="p">);</span>

    <span class="c1">// Testing alert sound</span>
    <span class="n">playAlertSound</span><span class="p">();</span>
</code></pre></div></div>

<p>The logic is pretty straightforward first initializing the LED strip to red, it configures i2c, and the LCD with an initial message.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">delay</span><span class="p">(</span><span class="mi">500</span><span class="p">);</span>
    <span class="c1">// Test servo: sweep from -90 to 90 degrees and back</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">angle</span> <span class="o">=</span> <span class="o">-</span><span class="mi">90</span><span class="p">;</span> <span class="n">angle</span> <span class="o">&lt;=</span> <span class="mi">90</span><span class="p">;</span> <span class="n">angle</span> <span class="o">+=</span> <span class="mi">10</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Setting servo to "</span><span class="p">);</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">angle</span><span class="p">);</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">" degrees"</span><span class="p">);</span>
        <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
        <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Servo: "</span><span class="p">);</span>
        <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">angle</span><span class="p">);</span>
        <span class="n">servoControl</span><span class="p">.</span><span class="n">setAngle</span><span class="p">(</span><span class="n">angle</span><span class="p">);</span>
        <span class="n">delay</span><span class="p">(</span><span class="mi">500</span><span class="p">);</span>
        <span class="n">lcd</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span>
    <span class="p">}</span>
    
    <span class="n">servoControl</span><span class="p">.</span><span class="n">setAngle</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
</code></pre></div></div>
<p>The servo does a sweep from side. YOu can see it does it in 10 degree increments.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">NUM_LEDS</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">strip</span><span class="p">.</span><span class="n">setPixelColor</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">strip</span><span class="p">.</span><span class="n">Color</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">));</span>
    <span class="p">}</span>
    <span class="n">strip</span><span class="p">.</span><span class="n">show</span><span class="p">();</span>
</code></pre></div></div>
<p>Given the LEDs are RGB this sets them all to green. The array is 0-indexed and as such it updates all four LEDs by looping over them.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="c1">// Test each motor in each direction for one second</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"Testing forward"</span><span class="p">);</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Forward "</span><span class="p">);</span>
    <span class="n">motorControl</span><span class="p">.</span><span class="n">moveCar</span><span class="p">(</span><span class="n">MOVE</span><span class="p">,</span> <span class="n">FORWARD</span><span class="p">,</span> <span class="mi">50</span><span class="p">);</span>
    <span class="n">delay</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>
    <span class="n">motorControl</span><span class="p">.</span><span class="n">stopAllMotors</span><span class="p">();</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span>
<span class="p">...</span>
</code></pre></div></div>
<p>Each of the directions is tested here. It’s useful to compare the direction of the wheels to mecanum car directional graphics while testing.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">NUM_LEDS</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">strip</span><span class="p">.</span><span class="n">setPixelColor</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">strip</span><span class="p">.</span><span class="n">Color</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">));</span>
    <span class="p">}</span>
    <span class="n">strip</span><span class="p">.</span><span class="n">show</span><span class="p">();</span>
</code></pre></div></div>
<p>Finally the color is set to blue.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// Get distance from ultrasonic sensor</span>
    <span class="kt">float</span> <span class="n">distance</span> <span class="o">=</span> <span class="n">ultrasonic</span><span class="p">.</span><span class="n">getDistance</span><span class="p">();</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Distance: "</span><span class="p">);</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">distance</span><span class="p">);</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">" cm"</span><span class="p">);</span>

    <span class="c1">// Display distance on LCD</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Distance: "</span><span class="p">);</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">distance</span><span class="p">);</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">" cm"</span><span class="p">);</span>

    <span class="n">delay</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The loop is straightforward and just gets the distance to the item detected by the ultrasonic distance sensor and displays it on the LCD.</p>

<p>The motors code can be inspected to see how all that works. In short it’s using the <a href="https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/mcpwm.html">mcpwm library</a> for controlling the motors here.</p>

<h3 id="visionsensortest">VisionSensorTest</h3>

<p>The vision sensor test is in this directory. For the most part the initialization is similar to the last test. The big difference is the presence of the SSCMA library for interfacing with the Seeed vision sensor v2 module.</p>

<p>As we’re already using the i2c bus for the LCD we don’t need to configure anything else there. I’m going to focus on the loop in particular for this example.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">static</span> <span class="kt">bool</span> <span class="n">personDetected</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
    <span class="k">static</span> <span class="kt">bool</span> <span class="n">alertGiven</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
    <span class="k">static</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">lastDetectionTime</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">Infer</span><span class="p">.</span><span class="n">invoke</span><span class="p">())</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">Infer</span><span class="p">.</span><span class="n">boxes</span><span class="p">().</span><span class="n">size</span><span class="p">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">personDetected</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
            <span class="n">lastDetectionTime</span> <span class="o">=</span> <span class="n">millis</span><span class="p">();</span>

            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">alertGiven</span><span class="p">)</span> <span class="p">{</span>
                <span class="c1">// Red when person is detected</span>
                <span class="n">setLEDColor</span><span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> 
                <span class="n">playAlertSound</span><span class="p">();</span>
                <span class="n">alertGiven</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="kt">int</span> <span class="n">personX</span> <span class="o">=</span> <span class="n">Infer</span><span class="p">.</span><span class="n">boxes</span><span class="p">()[</span><span class="mi">0</span><span class="p">].</span><span class="n">x</span><span class="p">;</span>
            <span class="kt">int</span> <span class="n">personWidth</span> <span class="o">=</span> <span class="n">Infer</span><span class="p">.</span><span class="n">boxes</span><span class="p">()[</span><span class="mi">0</span><span class="p">].</span><span class="n">w</span><span class="p">;</span>

            <span class="n">lcd</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span>
            <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
            <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Width: "</span><span class="p">);</span>
            <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">personWidth</span><span class="p">);</span>
            <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
            <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"X: "</span><span class="p">);</span>
            <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">personX</span><span class="p">);</span>

        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">personDetected</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">millis</span><span class="p">()</span> <span class="o">-</span> <span class="n">lastDetectionTime</span> <span class="o">&gt;</span> <span class="mi">10000</span><span class="p">))</span> <span class="p">{</span>
                <span class="n">setLEDColor</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">);</span> 
                <span class="n">personDetected</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
                <span class="n">alertGiven</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The logic is setup to invoke the model. After it invokes it gets back the bounding boxes and takes the first returned result, grabs the x value and the width of the box, and then outputs it on the LCD. If this was a new detection it sets the LEDs red and plays the alert noise. If a face has not been detected for 10 seconds it sets the LEDs to blue and returns back to “scanning mode.”</p>

<h3 id="motorcontrol">MotorControl</h3>

<p>This is the main project and brings those elements together. I won’t repeat the above here but will go into the control code:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">personX</span> <span class="o">=</span> <span class="n">Infer</span><span class="p">.</span><span class="n">boxes</span><span class="p">()[</span><span class="mi">0</span><span class="p">].</span><span class="n">x</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">personWidth</span> <span class="o">=</span> <span class="n">Infer</span><span class="p">.</span><span class="n">boxes</span><span class="p">()[</span><span class="mi">0</span><span class="p">].</span><span class="n">w</span><span class="p">;</span>

<span class="kt">bool</span> <span class="n">moveLeft</span> <span class="o">=</span> <span class="n">personX</span> <span class="o">&lt;</span> <span class="mi">80</span><span class="p">;</span>
<span class="kt">bool</span> <span class="n">moveRight</span> <span class="o">=</span> <span class="n">personX</span> <span class="o">&gt;</span> <span class="mi">140</span><span class="p">;</span>
<span class="kt">bool</span> <span class="n">moveForward</span> <span class="o">=</span> <span class="n">personWidth</span> <span class="o">&lt;</span> <span class="mi">45</span><span class="p">;</span>
<span class="kt">bool</span> <span class="n">moveBackward</span> <span class="o">=</span> <span class="n">personWidth</span> <span class="o">&gt;</span> <span class="mi">50</span><span class="p">;</span>
<span class="kt">bool</span> <span class="n">turnLeft</span> <span class="o">=</span> <span class="n">personX</span> <span class="o">&lt;</span> <span class="mi">40</span><span class="p">;</span>
<span class="kt">bool</span> <span class="n">turnRight</span> <span class="o">=</span> <span class="n">personX</span> <span class="o">&gt;</span> <span class="mi">180</span><span class="p">;</span>
</code></pre></div></div>

<p>The servo control logic shown in the <a href="https://github.com/Seeed-Studio/Seeed_Arduino_SSCMA/blob/main/examples/fan_tacking/fan_tracking.ino">SSCMA examples</a> was a great starting point to learn how to use the API response here. I did some experiments with my face at various distances to determine the size of the box when I was close and further away. The <code class="language-plaintext highlighter-rouge">x</code> value here is located at the center of the bounding box and the <code class="language-plaintext highlighter-rouge">w</code> value is the width of the box.</p>

<p>I then used the above variables to control the car’s actual movement based on their state.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">turnLeft</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"Turn Left"</span><span class="p">);</span>
    <span class="n">motorControl</span><span class="p">.</span><span class="n">moveCar</span><span class="p">(</span><span class="n">MOVE</span><span class="p">,</span> <span class="n">TURN_LEFT</span><span class="p">,</span> <span class="mi">50</span><span class="p">);</span>
    <span class="n">delay</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>
    <span class="n">motorControl</span><span class="p">.</span><span class="n">stopAllMotors</span><span class="p">();</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Turn Left"</span><span class="p">);</span>            
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">turnRight</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"Turn right"</span><span class="p">);</span>
    <span class="n">motorControl</span><span class="p">.</span><span class="n">moveCar</span><span class="p">(</span><span class="n">MOVE</span><span class="p">,</span> <span class="n">TURN_RIGHT</span><span class="p">,</span> <span class="mi">50</span><span class="p">);</span>
    <span class="n">delay</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>
    <span class="n">motorControl</span><span class="p">.</span><span class="n">stopAllMotors</span><span class="p">();</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Turn Right"</span><span class="p">);</span>      
</code></pre></div></div>

<p>Turning moves fairly quickly (in retrospect perhaps I should have used a smaller value for the speed) which can be a problem when using the face sensing so I opted to only turn for 100ms at a time.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">moveForward</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Action: Moving"</span><span class="p">);</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">moveLeft</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"Moving left forward"</span><span class="p">);</span>
        <span class="n">motorControl</span><span class="p">.</span><span class="n">moveCar</span><span class="p">(</span><span class="n">MOVE</span><span class="p">,</span> <span class="n">LEFT_FORWARD</span><span class="p">,</span> <span class="mi">50</span><span class="p">);</span>
        <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Left Forward"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">moveRight</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"Moving right forward"</span><span class="p">);</span>
        <span class="n">motorControl</span><span class="p">.</span><span class="n">moveCar</span><span class="p">(</span><span class="n">MOVE</span><span class="p">,</span> <span class="n">RIGHT_FORWARD</span><span class="p">,</span> <span class="mi">50</span><span class="p">);</span>
        <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Right Forward"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"Moving forward"</span><span class="p">);</span>
        <span class="n">motorControl</span><span class="p">.</span><span class="n">moveCar</span><span class="p">(</span><span class="n">MOVE</span><span class="p">,</span> <span class="n">FORWARD</span><span class="p">,</span> <span class="mi">50</span><span class="p">);</span>
        <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Forward"</span><span class="p">);</span>
    <span class="p">}</span>
</code></pre></div></div>
<p>For the forward and backward (not shown here) movement the logic determines if it should also move left or right and if so moves in that direction.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">moveLeft</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Action: Moving"</span><span class="p">);</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"Moving left"</span><span class="p">);</span>
    <span class="n">motorControl</span><span class="p">.</span><span class="n">moveCar</span><span class="p">(</span><span class="n">MOVE</span><span class="p">,</span> <span class="n">LEFT</span><span class="p">,</span> <span class="mi">50</span><span class="p">);</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Left"</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">moveRight</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Action: Moving"</span><span class="p">);</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"Moving right"</span><span class="p">);</span>
    <span class="n">motorControl</span><span class="p">.</span><span class="n">moveCar</span><span class="p">(</span><span class="n">MOVE</span><span class="p">,</span> <span class="n">RIGHT</span><span class="p">,</span> <span class="mi">50</span><span class="p">);</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Right"</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"Stopping all motors"</span><span class="p">);</span>
    <span class="n">motorControl</span><span class="p">.</span><span class="n">stopAllMotors</span><span class="p">();</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Action: Stopped"</span><span class="p">);</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>If there’s no movement forward or backwards it strafes in that direction and if there’s no movement at all it stops the motors.</p>

<h2 id="summary">Summary</h2>

<p>It was a fun project to work on that I learned quite a bit with. Up until this moment I haven’t spent much time with robotic programming. I’ll definitely use what I’ve gained here for projects in the future.</p>

<p>The Seeed Vision Sensor V2 has also been great. I’m a fan of how easy it is to program and train models for. I have a couple other projects I’ll be posting in the next few days.</p>]]></content><author><name>Cosmic Bee</name></author><category term="mini-projects" /><category term="Project" /><category term="Adeept" /><category term="Robotics" /><category term="Banana Pi PicoW S3" /><category term="Robot Car" /><category term="Vision Sensor V2" /><category term="Seeed Studio" /><summary type="html"><![CDATA[Adeept Omni-Directional Mecanum Car Kit x Seeed Vision Module V2]]></summary></entry></feed>