<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/css/rss.xsl" type="text/xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>xythobuz.de Blog</title>
<link>https://www.xythobuz.de</link>
<atom:link href="https://www.xythobuz.de/rss.xml" rel="self" type="application/rss+xml" />
<description>xythobuz Electronics &amp; Software Projects</description>
<language>en-us</language>
<pubDate>Fri, 31 Jul 2026 20:41:51 -0000</pubDate>
<lastBuildDate>Fri, 31 Jul 2026 20:41:51 -0000</lastBuildDate>
<docs>http://blogs.law.harvard.edu/tech/rss</docs>
<generator>Poole</generator>
<ttl>720</ttl>

<item>
    <title>Glove80 USB Dongle</title>
    <link>https://www.xythobuz.de/2026_07_29_glove80_dongle.html</link>
    <description>&lt;p&gt;&lt;span class=&quot;listdesc&quot;&gt;&lt;a href=&quot;input_devices.html&quot;&gt;...back to Input Devices overview&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;For about three years, since September 2023, my daily-driver keyboard has been the &lt;a href=&quot;https://www.moergo.com/pages/glove80&quot;&gt;Glove80&lt;/a&gt;.
It has always worked great, with the only downside being the battery life of the left half, which is much shorter due to it being the BLE central, the right half only operating as BLE peripheral.&lt;/p&gt;
&lt;p&gt;The manufacturer often &lt;a href=&quot;https://old.reddit.com/r/ErgoMechKeyboards/comments/1eqhx2x/moergo_glove80_battery_life/lhzqq7z/&quot;&gt;mentioned&lt;/a&gt; it is possible to use a dongle to avoid that problem.
But many people &lt;a href=&quot;https://old.reddit.com/r/ErgoMechKeyboards/comments/1h3te7j/help_setting_up_zmk_dongle_for_glove80_using/&quot;&gt;struggle&lt;/a&gt; to get that working.&lt;/p&gt;
&lt;p&gt;So here is how I did it and how you can replicate it.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/glove80_dongle_4.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/glove80_dongle_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/glove80_dongle_4_small.jpg&quot; alt=&quot;nRF52840 Dongle in 3D printed case&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/glove80_dongle_1.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/glove80_dongle_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/glove80_dongle_1_small.jpg&quot; alt=&quot;Glove80, Trackball and Space Mouse on my desktop&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;Quick Start&lt;/h2&gt;
&lt;p&gt;You need to buy an &lt;a href=&quot;https://www.nordicsemi.com/Products/Development-hardware/nRF52840-Dongle&quot;&gt;nRF52840 Dongle&lt;/a&gt;, aka. PCA10059, from Nordic.
You can &lt;a href=&quot;https://www.nordicsemi.com/About-us/BuyOnline?search_token=nRF52840%20Dongle&quot;&gt;check their website&lt;/a&gt; for distributors.
Other dongles or microcontroller boards can work as well, but then you need to make your own config.&lt;/p&gt;
&lt;p&gt;Download the latest pre-built binaries from my GitHub releases.&lt;/p&gt;
&lt;div class=&quot;releasecard&quot;&gt;
Release builds for glove80-dongle are &lt;a href=&quot;https://github.com/xythobuz/glove80-dongle/releases&quot;&gt;available on GitHub&lt;/a&gt;.&lt;br&gt;

Latest release of &lt;a href=&quot;https://github.com/xythobuz/glove80-dongle&quot;&gt;glove80-dongle&lt;/a&gt;: &lt;a href=&quot;https://github.com/xythobuz/glove80-dongle/releases/tag/custom-v3&quot;&gt;Custom ZMK with USB HID multi-battery reporting&lt;/a&gt; (2026-07-24 13:34:13)&lt;br&gt;

&lt;span style=&quot;font-size: x-small&quot;&gt;(at the time of this writing)&lt;/span&gt;
&lt;br&gt;
&lt;br&gt;Release Assets:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xythobuz/glove80-dongle/releases/download/custom-v3/dongle_fw.zip&quot;&gt;dongle_fw.zip&lt;/a&gt; (290 KiB)
&lt;li&gt;&lt;a href=&quot;https://github.com/xythobuz/glove80-dongle/releases/download/custom-v3/glove80_custom-glove80_lh-zmk.uf2&quot;&gt;glove80_custom-glove80_lh-zmk.uf2&lt;/a&gt; (390 KiB)
&lt;li&gt;&lt;a href=&quot;https://github.com/xythobuz/glove80-dongle/releases/download/custom-v3/glove80_custom-glove80_rh-zmk.uf2&quot;&gt;glove80_custom-glove80_rh-zmk.uf2&lt;/a&gt; (390 KiB)
&lt;li&gt;&lt;a href=&quot;https://github.com/xythobuz/glove80-dongle/releases/download/custom-v3/glove80_custom.settings_reset-glove80_lh-zmk.uf2&quot;&gt;glove80_custom.settings_reset-glove80_lh-zmk.uf2&lt;/a&gt; (127 KiB)
&lt;li&gt;&lt;a href=&quot;https://github.com/xythobuz/glove80-dongle/releases/download/custom-v3/glove80_custom.settings_reset-glove80_rh-zmk.uf2&quot;&gt;glove80_custom.settings_reset-glove80_rh-zmk.uf2&lt;/a&gt; (127 KiB)
&lt;li&gt;&lt;a href=&quot;https://github.com/xythobuz/glove80-dongle/releases/download/custom-v3/glove80_dongle-nrf52840dongle__zmk-zmk.bin&quot;&gt;glove80_dongle-nrf52840dongle__zmk-zmk.bin&lt;/a&gt; (290 KiB)
&lt;li&gt;&lt;a href=&quot;https://github.com/xythobuz/glove80-dongle/releases/download/custom-v3/glove80_dongle.settings_reset-nrf52840dongle__zmk-zmk.bin&quot;&gt;glove80_dongle.settings_reset-nrf52840dongle__zmk-zmk.bin&lt;/a&gt; (103 KiB)
&lt;/ul&gt;&lt;/div&gt;

&lt;p&gt;You need &lt;code&gt;dongle_fw.zip&lt;/code&gt; to flash to the PCA10059 board.
The &lt;code&gt;glove80_custom-glove80_X-zmk.uf2&lt;/code&gt; files need to be flashed to the left and right half of the keyboard.
When you switch from a config without a dongle to one with, or vice-versa, you need to flash the &lt;code&gt;settings_reset&lt;/code&gt; firmwares in between.&lt;/p&gt;
&lt;p&gt;To &lt;a href=&quot;https://docs.moergo.com/glove80-user-guide/customizing-key-layout/#putting-glove80-into-bootloader-for-firmware-loading&quot;&gt;enter the Glove80 bootloader&lt;/a&gt; of the left half, turn it on while holding the &lt;code&gt;MAGIC&lt;/code&gt; and &lt;code&gt;E&lt;/code&gt; keys.
For the right half it's &lt;code&gt;PgDn&lt;/code&gt; and &lt;code&gt;I&lt;/code&gt;.
You can then simply drag-n-drop the corresponding &lt;code&gt;.uf2&lt;/code&gt; file to the removable drive that appeared.&lt;/p&gt;
&lt;p&gt;If you haven't already, prepare &lt;a href=&quot;https://www.nordicsemi.com/Products/Development-tools/nRF-Util&quot;&gt;nRF Util&lt;/a&gt;.
In this example I install to &lt;code&gt;~/bin&lt;/code&gt;, which is in my &lt;code&gt;$PATH&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget -O nrfutil https://files.nordicsemi.com/ui/api/v1/download?repoKey=swtools&amp;amp;path=external/nrfutil/executables/x86_64-unknown-linux-gnu/nrfutil&amp;amp;isNativeBrowsing=false
chmod a+x nrfutil
mv nrfutil ~/bin
nrfutil install nrf5sdk-tools
nrfutil install device
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To &lt;a href=&quot;https://academy.nordicsemi.com/flash-instructions-for-nrf52840-dongle/&quot;&gt;enter the dongle bootloader&lt;/a&gt;, stick it into a USB port and press the RESET button.
Then flash the &lt;code&gt;dongle_fw.zip&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nrfutil device program --firmware dongle_fw.zip --traits nordicDfu
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you ever need to reset the settings of the dongle, convert the &lt;code&gt;.bin&lt;/code&gt; to a &lt;code&gt;.zip&lt;/code&gt; first.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nrfutil pkg generate --hw-version 52 --sd-req=0x00  --application glove80_dongle.settings_reset-nrf52840dongle__zmk-zmk.bin --application-version 1 dongle_reset.zip
nrfutil device program --firmware dongle_reset.zip --traits nordicDfu
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As mentioned above, when doing this initially you need to reset the settings of the two previously paired halves first, to clear the cached bluetooth pairing keys.
Then plug in the dongle with its new firmware and turn on the two keyboards with their new firmware.
They will automatically pair and everything should &lt;em&gt;just work&lt;/em&gt;™, as usual with ZMK.&lt;/p&gt;
&lt;h2&gt;Software Implementation&lt;/h2&gt;
&lt;p&gt;As a first step I've taken a look at the ZMK documentation.
They have a great &lt;a href=&quot;https://zmk.dev/docs/hardware-integration/dongle&quot;&gt;guide for configuring dongles&lt;/a&gt;.
I also searched GitHub and found &lt;a href=&quot;https://github.com/stammy/zmk-dongle-config&quot;&gt;&quot;zmk-dongle-config&quot; by stammy&lt;/a&gt;, which also seems to be based on the Glove80.&lt;/p&gt;
&lt;p&gt;The repo stops halfway short, when building I got a firmware build that properly paired the two peripherals to the central.
But it didn't know how to interpret the keypresses, as the layouts were missing.
So finishing according to the documentation, I had a working keyboard.&lt;/p&gt;
&lt;p&gt;At this point I need to mention the differences between &lt;a href=&quot;https://github.com/zmkfirmware/zmk&quot;&gt;upstream ZMK&lt;/a&gt; and the &lt;a href=&quot;https://github.com/moergo-sc/zmk&quot;&gt;MoErgo fork&lt;/a&gt;.
The stock Glove80 firmware uses the RGB per-key backlight to indicate status information like the battery level and BLE profiles.
This is not supported in the upstream ZMK firmware.
Even following the ZMK guide I was not able to get the MoErgo fork to build for a dongle.&lt;/p&gt;
&lt;p&gt;But even though I was not expecting the RGB &lt;em&gt;indicators&lt;/em&gt; to work on my upstream ZMK build, at least the RGB backlight animations should have worked, but didn't.
An &lt;a href=&quot;https://github.com/zmkfirmware/zmk/issues/3017&quot;&gt;issue by Samsuper12&lt;/a&gt; brought me on the right track for that.
The dongle device tree also needed RGB backlight configured.
So I copied the corresponding nodes from the Glove80 device tree, and then RGB backlight was working as well.&lt;/p&gt;
&lt;p&gt;This only left the problem of not seeing the battery level of the keyboard now.
ZMK supports reporting battery levels, but only via BLE.
Fortunately there is a &lt;a href=&quot;https://github.com/zmkfirmware/zmk/pull/2938&quot;&gt;pullrequest&lt;/a&gt; that implements battery level reporting via USB HID, and there even is &lt;a href=&quot;https://github.com/zampierilucas/zmk/tree/feat/individual-hid-battery-reporting&quot;&gt;a branch by &quot;zampierilucas&quot;&lt;/a&gt; that implements multiple battery levels for Linux &amp;gt;= 7.2.
I simply rebased that on the current ZMK upstream master, which worked without any conflicts.&lt;/p&gt;
&lt;h2&gt;Hardware&lt;/h2&gt;
&lt;p&gt;The dongle is plugged into an ugly hack consisting of two &lt;a href=&quot;https://de.aliexpress.com/item/1005006008569954.html&quot;&gt;portable USB hubs&lt;/a&gt;, hidden behind my display, connected to my PC using a USB extension.
This has been holding my webcam, &lt;a href=&quot;auto_brightness.html&quot;&gt;AutoBrightness&lt;/a&gt; and a Logitech dongle.
And now this keyboard dongle as well.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/glove80_dongle_2.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/glove80_dongle_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/glove80_dongle_2_small.jpg&quot; alt=&quot;USB Hub hidden behind displays (1)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/glove80_dongle_3.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/glove80_dongle_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/glove80_dongle_3_small.jpg&quot; alt=&quot;USB Hub hidden behind displays (2)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The 3D printed case is &lt;a href=&quot;https://www.printables.com/model/1281920-nrf52840-dongle-case-with-button-accessible&quot;&gt;&quot;nRF52840 Dongle case with Button accessible&quot; by janxgeist&lt;/a&gt;.
Though I actually didn't end up using it.&lt;/p&gt;
&lt;h2&gt;Logging Battery Levels&lt;/h2&gt;
&lt;p&gt;Based on the idea from &lt;a href=&quot;https://nessy.info/post/2016-04-11-telegraf-laptop-battery-plugin/&quot;&gt;this blog post&lt;/a&gt; I wrote a shell script that reads available battery states from &lt;code&gt;/proc&lt;/code&gt; and outputs it for Influx in line-protocol format.&lt;/p&gt;
&lt;pre class=&quot;sh_sh&quot;&gt;
#!/bin/bash

for d in /sys/class/power_supply/*/ ; do
        # ZMK etc.
        if [ -f &quot;$d/capacity&quot; ] &amp;&amp; [ -f &quot;$d/status&quot; ] &amp;&amp; [ -f &quot;$d/model_name&quot; ] &amp;&amp; [ -f &quot;$d/type&quot; ] ; then
                CAPACITY=`cat &quot;$d/capacity&quot;`
                STATUS=`cat &quot;$d/status&quot;`
                MODEL=`cat &quot;$d/model_name&quot; | sed 's/ /\\\\&amp;/g' | sed 's/,/\\\\&amp;/g' | sed 's/=/\\\\&amp;/g'`
                TYPE=`cat &quot;$d/type&quot; | sed 's/ /\\\\&amp;/g' | sed 's/,/\\\\&amp;/g' | sed 's/=/\\\\&amp;/g'`
                DIR=`basename &quot;$d&quot; | sed 's/ /\\\\&amp;/g' | sed 's/,/\\\\&amp;/g' | sed 's/=/\\\\&amp;/g'`
                echo &quot;battery,name=${MODEL},type=${TYPE},protocol=HID,path=${DIR} capacity=${CAPACITY}i,status=\&quot;${STATUS}\&quot;&quot;
        fi

        # Logitech HID++
        if [ -f &quot;$d/capacity_level&quot; ] &amp;&amp; [ -f &quot;$d/status&quot; ] &amp;&amp; [ -f &quot;$d/manufacturer&quot; ] &amp;&amp; [ -f &quot;$d/model_name&quot; ] &amp;&amp; [ -f &quot;$d/type&quot; ] &amp;&amp; [ -f &quot;$d/serial_number&quot; ] ; then
                LEVEL=`cat &quot;$d/capacity_level&quot;`
                STATUS=`cat &quot;$d/status&quot;`
                MANUFACTURER=`cat &quot;$d/manufacturer&quot; | sed 's/ /\\\\&amp;/g' | sed 's/,/\\\\&amp;/g' | sed 's/=/\\\\&amp;/g'`
                MODEL=`cat &quot;$d/model_name&quot; | sed 's/ /\\\\&amp;/g' | sed 's/,/\\\\&amp;/g' | sed 's/=/\\\\&amp;/g'`
                TYPE=`cat &quot;$d/type&quot; | sed 's/ /\\\\&amp;/g' | sed 's/,/\\\\&amp;/g' | sed 's/=/\\\\&amp;/g'`
                SERIAL=`cat &quot;$d/serial_number&quot; | sed 's/ /\\\\&amp;/g' | sed 's/,/\\\\&amp;/g' | sed 's/=/\\\\&amp;/g'`
                DIR=`basename &quot;$d&quot; | sed 's/ /\\\\&amp;/g' | sed 's/,/\\\\&amp;/g' | sed 's/=/\\\\&amp;/g'`
                echo &quot;battery,name=${MANUFACTURER}\ ${MODEL},type=${TYPE},protocol=HID++,path=${DIR},serial=${SERIAL} level=\&quot;${LEVEL}\&quot;,status=\&quot;${STATUS}\&quot;&quot;
        fi
done
&lt;/pre&gt;

&lt;p&gt;Here is some example output.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; $ batteries.sh
battery,name=ZMK\ Project\ G80\ Dongle,type=Battery,protocol=HID,path=hid-XXX-battery-5 capacity=91i,status=&quot;Discharging&quot;
battery,name=ZMK\ Project\ G80\ Dongle,type=Battery,protocol=HID,path=hid-XXX-battery-6 capacity=75i,status=&quot;Discharging&quot;
battery,name=Logitech\ MX\ Ergo\ Multi-Device\ Trackball\ ,type=Battery,protocol=HID++,path=hidpp_battery_0,serial=XXX level=&quot;Normal&quot;,status=&quot;Discharging&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I stored this in &lt;code&gt;/usr/local/bin/batteries.sh&lt;/code&gt; and set the executable-bit.&lt;/p&gt;
&lt;p&gt;Using a small telegraf config snippet my desktop PC will call this shell script and write the output to my Influx DB.&lt;/p&gt;
&lt;pre class=&quot;sh_yaml&quot;&gt;
[[inputs.exec]]
  commands = [ &quot;/usr/local/bin/batteries.sh&quot; ]
  data_format = &quot;influx&quot;
  interval = &quot;60s&quot;
&lt;/pre&gt;

&lt;p&gt;This is then visualized, &lt;a href=&quot;influxdb.html&quot;&gt;as usual&lt;/a&gt;, in my Grafana dashboard.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/glove80_dongle_5.png&quot; data-lg-size=&quot;1600-720&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/glove80_dongle_5.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/glove80_dongle_5_small.png&quot; alt=&quot;Grafana screenshot showing battery levels&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;In 5 days since installing the script both peripheral batteries have gone down by around 2%.&lt;/p&gt;
&lt;p&gt;So it looks like the promised reduction in energy usage was true.
Previously, the right half (peripheral) had a runtime of a couple of months.
The left halt (central) had a much shorter battery life, of a couple of weeks maybe.
It should be mentioned, I never turn the keyboards or my desktop PC off.&lt;/p&gt;
&lt;p&gt;Now the left half seems to have similar battery life as the right half, as they both act as peripheral.
The &quot;large&quot; energy consumption is in the central, which is now powered via USB from the desktop.&lt;/p&gt;</description>
    <pubDate>Wed, 29 Jul 2026 12:00:00 -0000</pubDate>
    <atom:updated>2026-07-29T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/2026_07_29_glove80_dongle.html</guid>
</item>

<item>
    <title>Logitech MX Ergo switch replacement</title>
    <link>https://www.xythobuz.de/2026_07_27_mx_ergo_switch.html</link>
    <description>&lt;p&gt;After using a Logitech M570 trackball for a while I switched to their MX Ergo model while the pandemic was going on.
I've been pretty happy with it, but after around three or four years, it had now developed the &quot;doubleclick issue&quot;.
Apparently it's a common problem with these devices.
In my case, the switch in the left mouse button started to register false clicks when holding it down.
This caused drag-and-drop or selecting text to become pretty much impossible.&lt;/p&gt;
&lt;p&gt;To disassemble the device you can follow this &lt;a href=&quot;https://www.ifixit.com/Guide/Logitech+MX+Ergo+Disassembly/133042&quot;&gt;great iFixit guide&lt;/a&gt;, but it's pretty easy and well constructed, so you'll find your way around.&lt;/p&gt;
&lt;p&gt;The most important part is unscrewing all the torx screws in the bottom, then sliding a prying tool under the right button, and unclip the two shells all the way around, until you arrive at the left button or they pop open.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/mx_ergo_1.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/mx_ergo_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/mx_ergo_1_small.jpg&quot; alt=&quot;Disassembled MX Ergo&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/mx_ergo_2.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/mx_ergo_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/mx_ergo_2_small.jpg&quot; alt=&quot;Main PCB, full of cat hair&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I was very surprised about the ridiculous amounts of cat hair that had built up.
Even though the encoder slots in the mouse wheel were pretty much invisible, it didn't seem to interfere with anything.
Of course I gave it a thorough clean.&lt;/p&gt;
&lt;p&gt;From research I assumed that the low-force variant, &lt;code&gt;OMRON D2F-01F&lt;/code&gt; would be the correct replacement.
But turns out, the original ones are &lt;code&gt;OMRON D2FC-F-7N (10M)&lt;/code&gt;.
So if you care to have an exact replacement, make sure to get these.
Some people also recommend &lt;code&gt;Kailh GM8.0&lt;/code&gt; as a replacement.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/mx_ergo_3.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/mx_ergo_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/mx_ergo_3_small.jpg&quot; alt=&quot;Main PCB with switch replaced, top side&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/mx_ergo_4.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/mx_ergo_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/mx_ergo_4_small.jpg&quot; alt=&quot;Main PCB with switch replaced, bottom side&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Even though I've got the &quot;wrong&quot; type, &lt;code&gt;D2F-01F&lt;/code&gt;, I only replaced the left button.
It works properly now, but you can hear and feel the difference between the left and right side.
I don't really care, to be honest, so I'll probably just leave it like that.&lt;/p&gt;
&lt;p&gt;Unsoldering the original switch is a bit tricky.
I used solder wick to get rid of most of the tin, then heated each of the three switch pins, one after the other, while pressing down on them to get the switch to move a bit.
Then I could get a small screwdriver between the switch and PCB, to pry a bit more, while still heating the pins.
When there was enough space, I simply cut off the pins near the switch housing.
Then the remaining pins can be easily unsoldered.
The large ground pad is a bit tricky, due to high thermal mass.
Make sure not to rip off the pad with too much force or heat.&lt;/p&gt;
&lt;p&gt;I also noticed that the switches themselves can be disassembled, they have small plastic latches on two sides.
So if you really want to, you could also try to clean the contact surfaces, instead of replacing the whole switch.&lt;/p&gt;</description>
    <pubDate>Mon, 27 Jul 2026 12:00:00 -0000</pubDate>
    <atom:updated>2026-07-27T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/2026_07_27_mx_ergo_switch.html</guid>
</item>

<item>
    <title>Dry Filament Storage</title>
    <link>https://www.xythobuz.de/2026_07_18_filament_storage.html</link>
    <description>&lt;p&gt;For years the filament roll for my &lt;a href=&quot;i3-am8.html&quot;&gt;3D printer&lt;/a&gt; has just been hanging from the front of my &lt;a href=&quot;ikea-lack.html&quot;&gt;Lack tower&lt;/a&gt;.
As I also often have long stretches of time where I don't use much filament, it tends to draw moisture from the air.
This reduces print quality, so it's recommended to keep the filament dry.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/filament_storage_3.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/filament_storage_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/filament_storage_3_small.jpg&quot; alt=&quot;Ares the cat demonstrating the old setup&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;So I finally decided to upgrade my printing situation by adding an air-tight container for the filament roll, with a bowden tube running to my direct-drive extruder.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/filament_storage_1.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/filament_storage_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/filament_storage_1_small.jpg&quot; alt=&quot;Air-tight box on the lowest Lack tower level&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/filament_storage_2.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/filament_storage_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/filament_storage_2_small.jpg&quot; alt=&quot;Printer with bowden tube running to the extruder&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/filament_storage_4.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/filament_storage_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/filament_storage_4_small.jpg&quot; alt=&quot;Back of Lack tower with bowden tube&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The 3D printed coupling mount on the extruder is &lt;a href=&quot;https://www.printables.com/model/462294-sherpa-mini-ptfe-coupler-holder-reverse-bowden-pc4&quot;&gt;&quot;Sherpa Mini PTFE Coupler Holder (Reverse Bowden, PC4‑M10 coupler)&quot; by sulli18&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I really like the cheap chinese box.
The kit already comes with everything except the bowden tube.
The filament rolls smoothly on bearings and it even includes a humidity meter.&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
&lt;a href=&quot;https://de.aliexpress.com/item/1005009970131564.html&quot;&gt;3D Printer Filament Dry Box Storage Hot-Air Heating Storage Printing Material Holder Moisture-Resistant for Filaments Dryer Box&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
16.49€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
&lt;a href=&quot;https://de.aliexpress.com/item/1005010656898078.html&quot;&gt;Blue/Orange Indicating Silica Gel Desiccant Beads，Reusable Moisture Absorber for Storage，Closets，Electronics，3D Printer Filament (Blue-9oz)&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
7.09€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
&lt;a href=&quot;https://de.aliexpress.com/item/1005010436615944.html&quot;&gt;Ultra-Smooth PTFE Tube for 1.75mm filament with Cutter for Extruder,2.5mm IDx4mm OD Length Tube for Bambu Lab A1/P1P/P1S/X1C (White 4M cutter)&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
4.89€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Sum
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
28.47€
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;I simply stuck a bag of silica gel in the bottom.
Only later I found out that the blue desiccant has toxic cobalt for coloring, so I'd recommend to buy the orange variant instead.&lt;/p&gt;</description>
    <pubDate>Sat, 18 Jul 2026 12:00:00 -0000</pubDate>
    <atom:updated>2026-07-18T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/2026_07_18_filament_storage.html</guid>
</item>

<item>
    <title>E-Scooter Battery Build</title>
    <link>https://www.xythobuz.de/2026_07_04_scooter_battery.html</link>
    <description>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;For over two years now, since February 2024, I own a Xiaomi Mi 3 scooter.
Of course I flashed it with SHFW using the ST-Link method, which then allows using the normal SHFW Android app to do all updating and configuring.&lt;/p&gt;
&lt;p&gt;This worked perfectly fine for about 2500km.
Then the internal battery was noticeably degraded and I could no longer reach home from work in winter.&lt;/p&gt;
&lt;p&gt;So I got one of the last &lt;a href=&quot;https://e-scooters.embedden.com/shop/accessories/rita-adapter-for-xiaomi/&quot;&gt;Rita adapters&lt;/a&gt; before the product was discontinued, which allows connecting an external battery with differing voltage for range extension.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_16.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_16.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_16_small.jpg&quot; alt=&quot;Rita adapter (Gen 6) for Xiaomi&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_17.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_17.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_17_small.jpg&quot; alt=&quot;Rita adapter installed in my scooter&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;A colleague of mine bought a 13S6P battery on AliExpress which he didn't end up using, so I got that from him.
Unfortunately it was total crap, and died much much quicker.
After only a handful of trips, I could again not reach work anymore.&lt;/p&gt;
&lt;p&gt;Still not having learned my lesson, I got a &quot;new&quot; internal Xiaomi scooter battery from AliExpress as well, which was just as bad and died in just a couple of trips.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_18.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_18.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_18_small.jpg&quot; alt=&quot;13S battery from AliExpress on my scooter&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_19.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_19.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_19_small.jpg&quot; alt=&quot;BMS of the cheap AliExpress battery&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_20.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_20.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_20_small.jpg&quot; alt=&quot;10S battery from AliExpress&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;So finally I decided to build my own.&lt;/p&gt;
&lt;h2&gt;Build&lt;/h2&gt;
&lt;p&gt;In my scooter config I've set a maximum current of 28A, so I decided to get a DIY battery kit from AliExpress with nominal support for 30A, in a 13S6P configuration.
30A divided by 6 cells in parallel gives a discharge current of 5A per cell.&lt;/p&gt;
&lt;p&gt;Looking around online I decided to go with &lt;a href=&quot;https://www.nkon.nl/&quot;&gt;nkon.nl&lt;/a&gt; as they are a well-known large supplier of cells in Europe.
There I found the &lt;a href=&quot;https://www.nkon.nl/en/inr18650-m29-2850mah-10a.html&quot;&gt;&lt;code&gt;INR18650-M29&lt;/code&gt;&lt;/a&gt; cells from LG, which support a surge discharge current of 10A and continuous discharge of 6A, according to &lt;a href=&quot;https://akkuplus.de/mediafiles/Datenblatt/LG/LG_INR18650M29.pdf&quot;&gt;their datasheet&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_1.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_1_small.jpg&quot; alt=&quot;80x 18650 cells in their shipping carton&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_2.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_2_small.jpg&quot; alt=&quot;AliExpress battery kit parts&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;As mentioned above, I checked the internal cell resistance, but you don't necessarily have to do that, as long as you use cells from one batch.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_3.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_3_small.jpg&quot; alt=&quot;Ohm-meter at work&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_4.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_4_small.jpg&quot; alt=&quot;Checking all internal cell resistances&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Now just stick them all into the carrier and attach the paper rings to the positive poles, to protect from shorts with the negative pole around the perimeter of each cell.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_5.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_5.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_5_small.jpg&quot; alt=&quot;Half of the cells being assembeld in their carrier&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_6.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_6.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_6_small.jpg&quot; alt=&quot;All cells in their carrier&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_7.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_7.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_7_small.jpg&quot; alt=&quot;All celss in their carrier, with BMS&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_8.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_8.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_8_small.jpg&quot; alt=&quot;Attaching paper rings to + poles&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Then do the spot welding.
I recommend doing some testing first on an unused cell, which is how I arrived at the settings mentioned above.
I ended up with four welds per cell pole.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_9.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_9.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_9_small.jpg&quot; alt=&quot;Starting the spot-welding&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_10.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_10.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_10_small.jpg&quot; alt=&quot;Close-up view of some spot-welds&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_11.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_11.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_11_small.jpg&quot; alt=&quot;Covering the finished welds with paper&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_12.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_12.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_12_small.jpg&quot; alt=&quot;Wrapping the cells with tape for stability&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The whole thing was covered with more insulating paper, and some battery fibre tape to hold the cells firmly in place.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_13.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_13.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_13_small.jpg&quot; alt=&quot;BMS attached to the nickel strips&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_14.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_14.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_14_small.jpg&quot; alt=&quot;Assembled battery box, top&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_15.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_15.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_15_small.jpg&quot; alt=&quot;Assembled battery box, side&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The BMS can also be welded, just the (dis)charge leads need to be soldered.
Use thick wires for that, rated for the current.
Therefore you will also need a beefy soldering iron to get them soldered to the PCB without unnecessary heating of other components.&lt;/p&gt;
&lt;h2&gt;Tools Used&lt;/h2&gt;
&lt;p&gt;Fortunately I have some access to tools at work.&lt;/p&gt;
&lt;p&gt;So I used their &lt;a href=&quot;https://de.rs-online.com/web/p/batterie-tester/1804810&quot;&gt;RS Pro RSBM-3300 Battery Tester&lt;/a&gt; to check the  charge state and internal resistance of each cell.
This turned out to be unnecessary though, as all cells came from the same batch and had matching voltages and resistances.&lt;/p&gt;
&lt;p&gt;For spot welding I used the &lt;a href=&quot;https://www.fnirsi.com/products/swm-10&quot;&gt;FNIRSI SWM-10&lt;/a&gt;.
The settings I was happy with are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Preheat: &lt;code&gt;3ms&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Pulse: &lt;code&gt;15ms&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Interval: &lt;code&gt;10ms&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Dots: &lt;code&gt;3&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Although all around I don't like this spot welder very much.
It's made with a single very beefy lithium cell inside, that seems to just be shorted for welding, which is fine.
But the charging and safety monitoring of this cell seems problematic.
It was deep-discharged by itself after some time on the shelf, and when left to charge it stopped the charge at a cell voltage of 4.3V, which is overcharging the cell.
So be careful with that, if you own the device yourself.
Some people online suggest only using it with slow USB chargers with &lt;code&gt;500mA&lt;/code&gt; or so.&lt;/p&gt;
&lt;p&gt;It also took around three or four recharges of the spot-welder battery to finish this project.&lt;/p&gt;
&lt;h2&gt;Bill of Materials&lt;/h2&gt;
&lt;p&gt;These are the consumables I bought, with the price I paid at the time.&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;th&gt;Unit-Cost&lt;/th&gt;
&lt;th&gt;Total-Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
&lt;a href=&quot;https://www.nkon.nl/en/inr18650-m29-2850mah-10a.html&quot;&gt;INR18650-M29 2850mAh - 10A&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
78x
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
1.06€
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
82.68€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
&lt;a href=&quot;https://de.aliexpress.com/item/1005008724048062.html&quot;&gt;48V 30A 13S 6P 18650 Battery Holder E-bike Battery Case Box BMS for E-scooter Electric Bike Battery Housings with Welding Nickel&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
1x
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
24.59€
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
24.59€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
&lt;a href=&quot;https://de.aliexpress.com/item/1005010596832228.html&quot;&gt;XT60 Battery Connector Male &amp; Female Bullet Connector Plugs with Sheath Housing for RC Battery Motor FPV XT-60 Adapter Silicone (L)&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
1x
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
4.69€
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
4.69€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
&lt;a href=&quot;https://de.aliexpress.com/item/1005009381533331.html&quot;&gt;High Viscosity Stripe Fiber Tape, Non-Marking Tape, Battery Bundling, Wear-Resistant, Waterproof, and High-Temperature Resistant (50mm)&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
1x
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
3.55€
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
3.55€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
&lt;a href=&quot;https://de.aliexpress.com/item/1005010613670195.html&quot;&gt;5m 18650 Battery Electrical Insulating Adhesive Paper Thickness Battery Pack Insulator Gasket Tape Warp Electrode Insulated Pads (65mm)&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
1x
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
3.29€
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
3.29€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
&lt;a href=&quot;https://de.aliexpress.com/item/1005009644859705.html&quot;&gt;400 Pieces of Self-Adhesive Solid/Hollow 18650 Battery Insulation Pads, 18650 Battery Insulation Rings, 18650 High-Density Paper, Insulation Pad Circles, Single-Sided Adhesive Batteries (Hollow)&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
1x
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
1.59€
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
1.59€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
&lt;a href=&quot;https://de.aliexpress.com/item/1005005885492561.html&quot;&gt;ANL Fuse Holder Bolt-on Fuse Automotive Fuse Holders Fusible Link with fuse 40A 60A 80A 100A 200A 250A 300A Fuses AMP (2x 40A)&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
1x
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
4.99€
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
4.99€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Sum
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;

&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;

&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
125.38€
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;With these 2.85Ah cells, arranged as 6 in parallel, the resulting capacity of the pack is &lt;code&gt;17Ah&lt;/code&gt;.
And the 13 strings of these in series give a nominal voltage of &lt;code&gt;48V&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Fuse Update&lt;/h2&gt;
&lt;p&gt;After building the battery I noticed that UL certified batteries are required to have a fuse or some other mechanism to prevent thermal runaway events.
The BMS has short-circuit-protection, but I guess adding an actual fuse may help in some cases.
So I bought some &lt;a href=&quot;https://de.aliexpress.com/item/1005005885492561.html&quot;&gt;40A fuses and a matching fuse block&lt;/a&gt; from AliExpress.
Turns out they are way bigger than I expected.
Reading the specs before buying may be helpful, lesson learned... 😅&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_21.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_21.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_21_small.jpg&quot; alt=&quot;DC Fuse Block&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_22.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_22.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_22_small.jpg&quot; alt=&quot;DC Fuse with heat shrink tube&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;So I just skipped the fuse block and wired the fuse directly into the positive wire.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_23.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_23.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_23_small.jpg&quot; alt=&quot;Original wiring inside battery&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_24.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_24.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_24_small.jpg&quot; alt=&quot;Positive wire cut, with screw lugs&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;It's a bit tight, but with some M5x12 screws to mount the cables I was able to make it fit.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_25.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_25.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_25_small.jpg&quot; alt=&quot;Fuse connected with heat shrink tubing&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/scooter_bat_26.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/scooter_bat_26.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/scooter_bat_26_small.jpg&quot; alt=&quot;Fuse inside battery&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I also took this opportunity and replaced the original six Philips M2.5x16 screws with ones that have internal hex heads.&lt;/p&gt;</description>
    <pubDate>Sat, 04 Jul 2026 12:00:00 -0000</pubDate>
    <atom:updated>2026-07-12T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/2026_07_04_scooter_battery.html</guid>
</item>

<item>
    <title>Oscilloscope Video Display</title>
    <link>https://www.xythobuz.de/osci_video_pcb.html</link>
    <description>&lt;p&gt;After playing &lt;a href=&quot;osci_music_player.html&quot;&gt;oscilloscope music&lt;/a&gt; the next obvious question that came up was: can we make it &lt;a href=&quot;https://canitrundoom.org/&quot;&gt;run Doom&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;Turns out a couple of people already &lt;a href=&quot;https://www.youtube.com/watch?v=OU16lIx_pC8&quot;&gt;did that&lt;/a&gt;, even in &lt;a href=&quot;https://www.youtube.com/watch?v=xZaKlLyikKg&quot;&gt;different ways&lt;/a&gt;.
The last link is just running normal Doom on a modern LCD osci, which seems a bit boring.
Even more involved is modifying the game engine to output vectors, which has been done &lt;a href=&quot;https://www.mikeayles.com/#scopedoom&quot;&gt;here&lt;/a&gt; or &lt;a href=&quot;https://www.lofibucket.com/articles/oscilloscope_quake.html&quot;&gt;here, for Quake&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For this project I copied the circuit from the &lt;a href=&quot;https://www.youtube.com/watch?v=UZapLL3Qa-I&quot;&gt;XYZ converter by MrSlehofer&lt;/a&gt;.
The basic idea is to convert a CRT oscilloscope into a grayscale TV, by rastering each NTSC / PAL field line-by-line.
So the X and Y channels contain saw-tooth waveforms, with the X frequency matching the horizontal blank period, and the Y frequency matching the vertical blank period.
When Z is not connected you should therefore see something resembling a rectangular white 4:3 image.
Then Z just needs to have the amplitude of the video signal, which corresponds to the brightness at that moment in time.&lt;/p&gt;
&lt;p&gt;Just to test that process out for the first time I turned the PCB into a KiCad design that can be assembled by JLCPCB.
This worked out well.
If you want to order some as well, use the &lt;a href=&quot;https://codeberg.org/xythobuz/osci-video-pcb/src/branch/master/production_files&quot;&gt;&lt;code&gt;production_files&lt;/code&gt; from the repo&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_video_pcb_1.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_pcb_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_pcb_1_small.jpg&quot; alt=&quot;Assembled PCBs from JLCPCB&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_video_pcb_2.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_pcb_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_pcb_2_small.jpg&quot; alt=&quot;Top of assembled PCB&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_video_pcb_3.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_pcb_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_pcb_3_small.jpg&quot; alt=&quot;Bottom of assembled PCB&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Initially I had some problems getting the circuit to work at all.
The oscilloscope screen only showed some strange warbling lines or squares.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_video_pcb_4.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_pcb_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_pcb_4_small.jpg&quot; alt=&quot;First FPV camera test&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_video_pcb_5.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_pcb_5.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_pcb_5_small.jpg&quot; alt=&quot;Unsatisfying first results&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Turns out it is very susceptible to changes in voltage levels.
Depending on the source you use to feed it you may need to adjust some resistance values.
In the end I replaced two resistors with potentiometers.
This allows easy on-the-fly adjustments.
Try a 10k poti for R1, and a 100k Poti for R10.
Big thanks to &lt;a href=&quot;https://dl2ol.darc.de/&quot;&gt;Walter (DL2OL)&lt;/a&gt; from the &lt;a href=&quot;https://wiki.toolbox-bodensee.de/doku.php?id=amateurfunk:start&quot;&gt;local ham radio club&lt;/a&gt; co-located at &lt;a href=&quot;https://toolbox-bodensee.de/&quot;&gt;our makerspace&lt;/a&gt; for helping with tuning the circuit!&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_video_1.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_1_small.jpg&quot; alt=&quot;First successful test&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_video_2.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_2_small.jpg&quot; alt=&quot;FPV camera setup&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_video_3.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_3_small.jpg&quot; alt=&quot;Closeup with noticeable distortion&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The next step was to use a Raspberry Pi 1 that I still had lying around, because of the prominent analog video output connector on there.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_video_4.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_4_small.jpg&quot; alt=&quot;Test setup with switching power supply&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_video_5.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_5.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_5_small.jpg&quot; alt=&quot;Noisy desktop background&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Powering it with switching voltage regulators was not a good idea.
The voltage ripple on the supply lines is very visible on the output image.
So next I used a lab power supply for the video converter PCB, and an official Raspberry Pi USB wall wart for 5V.
I also used the opportunity to switch to a beefier Raspberry Pi 3, after I learned that it still has an AV out on the 3.5mm jack.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_video_6.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_6.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_6_small.jpg&quot; alt=&quot;Test setup with one lab power supply (12V) and a USB wall wart&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_video_7.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_7.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_7_small.jpg&quot; alt=&quot;Running doom for the first time&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_video_8.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_8.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_8_small.jpg&quot; alt=&quot;Very noisy xterm&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Replacing the USB power supply with another big lab power supply and dimming the room lights / providing some shade for the scope screen meant we could finally play for the first time.
&lt;a href=&quot;https://www.phschoen.de/&quot;&gt;Philipp&lt;/a&gt; and I tried both Doom (&lt;a href=&quot;https://www.chocolate-doom.org/wiki/index.php/Chocolate_Doom&quot;&gt;chocolate-doom&lt;/a&gt;) and &lt;a href=&quot;https://tuxracer.sourceforge.net/&quot;&gt;Tux Racer&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-video='{&quot;source&quot;: [{&quot;src&quot;:&quot;img/osci_video_3.mp4&quot;, &quot;type&quot;:&quot;video/mp4&quot;}], &quot;attributes&quot;: {&quot;preload&quot;: false, &quot;playsinline&quot;: true, &quot;controls&quot;: true}}' data-lg-size=&quot;1920-1080&quot; data-poster=&quot;img/osci_video_3_poster.png&quot; data-sub-html=&quot;Doom getting kinda playable&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_3.mp4&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_3_thumb.png&quot; alt=&quot;Doom getting kinda playable&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-video='{&quot;source&quot;: [{&quot;src&quot;:&quot;img/osci_video_4.mp4&quot;, &quot;type&quot;:&quot;video/mp4&quot;}], &quot;attributes&quot;: {&quot;preload&quot;: false, &quot;playsinline&quot;: true, &quot;controls&quot;: true}}' data-lg-size=&quot;1080-1920&quot; data-poster=&quot;img/osci_video_4_poster.png&quot; data-sub-html=&quot;Playing Tux Racer&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_4.mp4&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_4_thumb.png&quot; alt=&quot;Playing Tux Racer&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Here's another run where we actually completed the first Doom 1 Episode.
I also brought my old Tektronix scope.
All the scopes we tried don't have any gain control for their Z / brightness input, and the video PCB is also not that finely adjustable.
With the Tektronix scope the voltage ranges and therefore contrast and brightness seem to fit best.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-video='{&quot;source&quot;: [{&quot;src&quot;:&quot;img/osci_video_5.mp4&quot;, &quot;type&quot;:&quot;video/mp4&quot;}], &quot;attributes&quot;: {&quot;preload&quot;: false, &quot;playsinline&quot;: true, &quot;controls&quot;: true}}' data-lg-size=&quot;1920-1080&quot; data-poster=&quot;img/osci_video_5_poster.png&quot; data-sub-html=&quot;Doom finally running well&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_5.mp4&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_5_thumb.png&quot; alt=&quot;Doom finally running well&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_video_9.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_9.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_9_small.jpg&quot; alt=&quot;Test setup with two lab power supplies (12V and 5V)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;To have another portable setup for display in the local makerspace I planned to iterate on the case idea from my &lt;a href=&quot;osci_music_player.html&quot;&gt;Osci Music Player&lt;/a&gt;.
The housing should be a plastic kitchen ware container with all required connections routed to panel mount connectors.&lt;/p&gt;
&lt;p&gt;Everything was initially planned to be powered by a USB-C PD power supply with 12V for the converter PCB and a step-down converter to power a Raspberry Pi with 5V.
Unfortunately I think some more serious filtering of the regulator outputs would be needed to get acceptable image quality while using step-down converters.&lt;/p&gt;
&lt;p&gt;For now I'm simply hooking up some high quality lab power supplies.
Considering the device is always operated near an oscilloscope that should probably not be too restricting.&lt;/p&gt;
&lt;p&gt;Though I have to admit, I'm still not quite happy with this raster approach.
The high frequency audio interface of the Osci Music Player hardware could also be used to improve the visual quality of &lt;a href=&quot;https://www.mikeayles.com/#scopedoom&quot;&gt;Michael Ayles' ScopeDoom&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;More Videos&lt;/h2&gt;
&lt;div class=&quot;collapse&quot;&gt;Some more media I didn't use above.&lt;/div&gt;

&lt;div class=&quot;collapsecontent&quot;&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-video='{&quot;source&quot;: [{&quot;src&quot;:&quot;img/osci_video_1.mp4&quot;, &quot;type&quot;:&quot;video/mp4&quot;}], &quot;attributes&quot;: {&quot;preload&quot;: false, &quot;playsinline&quot;: true, &quot;controls&quot;: true}}' data-lg-size=&quot;1080-1920&quot; data-poster=&quot;img/osci_video_1_poster.png&quot; data-sub-html=&quot;First Doom test (1 / 2)&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_1.mp4&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_1_thumb.png&quot; alt=&quot;First Doom test (1 / 2)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-video='{&quot;source&quot;: [{&quot;src&quot;:&quot;img/osci_video_2.mp4&quot;, &quot;type&quot;:&quot;video/mp4&quot;}], &quot;attributes&quot;: {&quot;preload&quot;: false, &quot;playsinline&quot;: true, &quot;controls&quot;: true}}' data-lg-size=&quot;1920-1080&quot; data-poster=&quot;img/osci_video_2_poster.png&quot; data-sub-html=&quot;First Doom test (2 / 2)&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_video_2.mp4&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_video_2_thumb.png&quot; alt=&quot;First Doom test (2 / 2)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
    <pubDate>Mon, 22 Dec 2025 12:00:00 -0000</pubDate>
    <atom:updated>2025-12-22T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/osci_video_pcb.html</guid>
</item>

<item>
    <title>CYD MQTT Touchscreen UI</title>
    <link>https://www.xythobuz.de/cyd_ui.html</link>
    <description>&lt;p&gt;&lt;span class=&quot;listdesc&quot;&gt;&lt;a href=&quot;smarthome.html&quot;&gt;...back to Smart Home overview&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;After adding more actors to my Smart Home in the form of power sockets running &lt;a href=&quot;https://tasmota.github.io/docs/&quot;&gt;Tasmota&lt;/a&gt;, I had to improve the ways to control all of these devices.
The &lt;a href=&quot;mqtt_telegram.html&quot;&gt;Telegram Bot&lt;/a&gt; and &lt;a href=&quot;mqtt_web.html&quot;&gt;Web UI&lt;/a&gt; are a good start, but the Wife Acceptance Factor is pretty low, and they require access to a computer or smartphone.&lt;/p&gt;
&lt;p&gt;Recently I learned about the &lt;a href=&quot;https://github.com/witnessmenow/ESP32-Cheap-Yellow-Display&quot;&gt;CYD (cheap yellow display)&lt;/a&gt;, a nice ESP32 dev board with a touchscreen.
This seemed ideal to run a simple user interface to control everything via MQTT.&lt;/p&gt;
&lt;p&gt;Here are the results.
When not in use the device shows a stand-by screen with the current time and date.
After pressing the screen a menu is shown.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/cyd_3.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/cyd_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/cyd_3_small.jpg&quot; alt=&quot;Livingroom unit, on standy screen&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/cyd_4.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/cyd_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/cyd_4_small.jpg&quot; alt=&quot;Livingroom unit, on menu screen&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I mounted multiple of these units in different places.
They can be updated easily over-the-air.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/cyd_5.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/cyd_5.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/cyd_5_small.jpg&quot; alt=&quot;Workbench unit&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/cyd_6.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/cyd_6.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/cyd_6_small.jpg&quot; alt=&quot;Bathroom unit&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;To get the LDR on the device to work properly, so the screen brightness can be adjusted to the environment lighting conditions, you need to modify the hardware a bit.
I removed R19 and replaced R15 with a 100k resistor.
The top board in the following image is in the original state, the bottom board has already been modified.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/cyd_2.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/cyd_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/cyd_2_small.jpg&quot; alt=&quot;LDR resistor fix, top original, bottom modified&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The CYD code has been integrated into my &lt;a href=&quot;espenv.html&quot;&gt;ESP-Env project&lt;/a&gt; and &lt;a href=&quot;https://codeberg.org/xythobuz/esp-env&quot;&gt;repo&lt;/a&gt;.&lt;/p&gt;</description>
    <pubDate>Sun, 28 Sep 2025 12:00:00 -0000</pubDate>
    <atom:updated>2025-09-28T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/cyd_ui.html</guid>
</item>

<item>
    <title>Smart Meter Bridge</title>
    <link>https://www.xythobuz.de/lora_sml.html</link>
    <description>&lt;p&gt;&lt;span class=&quot;listdesc&quot;&gt;&lt;a href=&quot;smarthome.html&quot;&gt;...back to Smart Home overview&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;A while ago my power net company installed new smart power meters, specifically the &lt;a href=&quot;https://wiki.volkszaehler.org/hardware/channels/meters/power/edl-ehz/iskraemeco_mt681&quot;&gt;Iskra MT681&lt;/a&gt;.
It has a standardized infrared interface to get some data from it.
There are different &lt;a href=&quot;https://wiki.volkszaehler.org/hardware/controllers/ir-schreib-lesekopf-ttl-ausgang&quot;&gt;designs&lt;/a&gt; available to attach a microcontroller, both open-source and commercial.
I've gone the lazy route and bought one &lt;a href=&quot;https://www.ebay.de/itm/353940190755&quot;&gt;from eBay&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lora_sml_1.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lora_sml_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lora_sml_1_small.jpg&quot; alt=&quot;IR TTL Reader with magnetic base&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Unfortunately the meter is mounted in the basemet of my apartment building, too far for any WiFi signals.
So I decided to try out &lt;a href=&quot;https://en.wikipedia.org/wiki/LoRa&quot;&gt;LoRa&lt;/a&gt; to transmit the data, using two &lt;a href=&quot;https://heltec.org/project/wifi-lora-32-v3/&quot;&gt;Heltec LoRa32&lt;/a&gt; dev boards.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lora_sml_2.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lora_sml_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lora_sml_2_small.jpg&quot; alt=&quot;LoRa SML transmitter, outside&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lora_sml_3.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lora_sml_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lora_sml_3_small.jpg&quot; alt=&quot;LoRa SML transmitter, inside&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The interface to the meter is an infrared UART with 9600 8N1.
Over this port it sends messages in a format called &lt;a href=&quot;https://de.wikipedia.org/wiki/Smart_Message_Language&quot;&gt;SML&lt;/a&gt;.
To decode these I'm using &lt;a href=&quot;https://github.com/olliiiver/sml_parser&quot;&gt;sml_parser&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lora_sml_4.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lora_sml_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lora_sml_4_small.jpg&quot; alt=&quot;LoRa SML receiver&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;To get useful data from the meter you have to unlock it with a PIN from the net provider.
Without this PIN you only get the current energy count in &lt;code&gt;kWh&lt;/code&gt;, the same number you can see on the display.
After unlocking all data, you get the energy count in &lt;code&gt;1/10th Wh&lt;/code&gt;, so much finer resolution.
And you also get the current power in &lt;code&gt;W&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lora_sml_5.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lora_sml_5.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lora_sml_5_small.jpg&quot; alt=&quot;Testing the transmitter&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lora_sml_6.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lora_sml_6.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lora_sml_6_small.jpg&quot; alt=&quot;Readout of SML messages&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;For the LoRa PHY I'm using &lt;a href=&quot;https://github.com/jgromes/RadioLib/&quot;&gt;RadioLib&lt;/a&gt;, which already comes integrated into the &lt;a href=&quot;https://github.com/ropg/heltec_esp32_lora_v3/&quot;&gt;unofficial Heltec LoRa 32 library&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I'm doing lots of deep sleep on the transmitter side, to safe as much battery capacity as possible.
And I had to use the watchdog in the ESP32 and also do an auto-reset when no messages have been received after a while.
So somewhere the code hangs, sometimes.
But with these hacks in place it seems to work well.&lt;/p&gt;
&lt;p&gt;The code is integrated into my &lt;a href=&quot;espenv.html&quot;&gt;ESP-Env project&lt;/a&gt; and &lt;a href=&quot;https://codeberg.org/xythobuz/esp-env&quot;&gt;repo&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lora_sml_power.png&quot; data-lg-size=&quot;1600-713&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lora_sml_power.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lora_sml_power_small.png&quot; alt=&quot;Example of my power usage&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lora_sml_battery.png&quot; data-lg-size=&quot;1600-713&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lora_sml_battery.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lora_sml_battery_small.png&quot; alt=&quot;Battery usage of the LoRa transmitter&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Here you can see a normal weekend for me, going to bed at 4:30 in the morning, and waking up at around 13:00.
The spike with 1.3kW is heating water for coffee.
The other power readings come from tasmota sockets or my NAS UPS.&lt;/p&gt;
&lt;p&gt;Also the battery usage of the LoRa transmitter is very low.
&lt;s&gt;I will update when I have to recharge them for the first time.&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt; from September 2025:&lt;/p&gt;
&lt;p&gt;The device works for around two to three months on a single charge.
This is with an over five year old Panasonic NCR18650B that was previously used in an E-Cigarette mod, with nominal 3400mAh.
Here is the battery status from the previous year.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lora_sml_batt_usage.png&quot; data-lg-size=&quot;1600-719&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lora_sml_batt_usage.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lora_sml_batt_usage_small.png&quot; alt=&quot;Battery voltage over a whole year&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;As you can tell I even accidentally deep-discharged the cell last christmas, but it still runs fine.&lt;/p&gt;</description>
    <pubDate>Sat, 06 Jul 2024 12:00:00 -0000</pubDate>
    <atom:updated>2025-09-28T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/lora_sml.html</guid>
</item>

<item>
    <title>Duality</title>
    <link>https://www.xythobuz.de/duality.html</link>
    <description>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I regularly watch the livestreams of fulltime GTA:SA speedrunner &lt;a href=&quot;https://www.twitch.tv/joshimuz/&quot;&gt;Joshimuz&lt;/a&gt;.
A while ago he did a &lt;a href=&quot;https://www.youtube.com/playlist?list=PLv1eoin737hDEcLhbiLIccTPr9qQEaXU9&quot;&gt;PS2 Retro Achievements run&lt;/a&gt;.
As part of these he has to get a Top 5 score in the in-game arcade machine Duality in both the black and white highscore table.
In &lt;a href=&quot;https://youtu.be/PIAo_3YPYO8?t=14536&quot;&gt;Part 10 at 04:02:16&lt;/a&gt;, when Josh starts playing Duality, chat user &lt;code&gt;Caffie_&lt;/code&gt; mentions how this could be a Game Boy game.
So this inspired me to try to port the game to the Game Boy Color.
Here are the results.&lt;/p&gt;
&lt;p&gt;You can either &lt;a href=&quot;https://xythobuz.github.io/Duality/duality.gb&quot;&gt;download the ROM&lt;/a&gt; or try it out right here, if you have JavaScript enabled, thanks to &lt;a href=&quot;https://emulatorjs.org/&quot;&gt;EmulatorJS&lt;/a&gt;.&lt;/p&gt;
&lt;div id=&quot;duality_wrap&quot; class=&quot;border has_js&quot;&gt;
    &lt;div id=&quot;duality_game&quot;&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;On PCs use keyboard input with the keys given below. On mobile devices a touch overlay should automatically appear over the emulator.&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;th&gt;Button&lt;/th&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
D-Pad Left
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
Arrow Left
&lt;/td&gt;
&lt;td style=&quot; text-align: center;&quot;&gt;
Rotate Left
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
D-Pad Right
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
Arrow Right
&lt;/td&gt;
&lt;td style=&quot; text-align: center;&quot;&gt;
Rotate Right
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
A
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
S
&lt;/td&gt;
&lt;td style=&quot; text-align: center;&quot;&gt;
Accelerate
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
B
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
A
&lt;/td&gt;
&lt;td style=&quot; text-align: center;&quot;&gt;
Shoot
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
Start
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
Enter
&lt;/td&gt;
&lt;td style=&quot; text-align: center;&quot;&gt;
Play / Pause
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
Select
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
Space
&lt;/td&gt;
&lt;td style=&quot; text-align: center;&quot;&gt;
Config / About
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;p style=&quot;font-size: smaller; font-style: italic;&quot;&gt;
Although you can use EmulatorJS I recommend a native emulator for your target device if you experience stuttering music or bad performance with the emulator on this page.
&lt;/p&gt;

&lt;p&gt;Alternatively here's a short gameplay video and some screenshots.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-video='{&quot;source&quot;: [{&quot;src&quot;:&quot;img/duality_gameplay.webm&quot;, &quot;type&quot;:&quot;video/webm&quot;}], &quot;attributes&quot;: {&quot;preload&quot;: false, &quot;playsinline&quot;: true, &quot;controls&quot;: true}}' data-lg-size=&quot;495-450&quot; data-poster=&quot;img/duality_gameplay_poster.png&quot; data-sub-html=&quot;Gameplay screen recording&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_gameplay.webm&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_gameplay_thumb.png&quot; alt=&quot;Gameplay screen recording&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_ss_menu.png&quot; data-lg-size=&quot;960-864&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_ss_menu.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_ss_menu_small.png&quot; alt=&quot;Start screen&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_ss_shoot2.png&quot; data-lg-size=&quot;960-864&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_ss_shoot2.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_ss_shoot2_small.png&quot; alt=&quot;Gameplay&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_ss_accel.png&quot; data-lg-size=&quot;960-864&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_ss_accel.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_ss_accel_small.png&quot; alt=&quot;Accelerating&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_ss_over.png&quot; data-lg-size=&quot;960-864&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_ss_over.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_ss_over_small.png&quot; alt=&quot;Scoring name entry screen&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

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

&lt;div class=&quot;collapse&quot;&gt;Click for more screenshots.&lt;/div&gt;

&lt;div class=&quot;collapsecontent&quot;&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_ss_damage.png&quot; data-lg-size=&quot;960-864&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_ss_damage.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_ss_damage_small.png&quot; alt=&quot;Damaged from black hole&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_ss_score.png&quot; data-lg-size=&quot;960-864&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_ss_score.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_ss_score_small.png&quot; alt=&quot;Score list&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_ss_about.png&quot; data-lg-size=&quot;960-864&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_ss_about.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_ss_about_small.png&quot; alt=&quot;About screen&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_ss_conf.png&quot; data-lg-size=&quot;960-864&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_ss_conf.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_ss_conf_small.png&quot; alt=&quot;Configuration menu&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_ss_continue.png&quot; data-lg-size=&quot;960-864&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_ss_continue.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_ss_continue_small.png&quot; alt=&quot;Continue screen&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_ss_shoot.png&quot; data-lg-size=&quot;960-864&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_ss_shoot.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_ss_shoot_small.png&quot; alt=&quot;Shooting&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_ss_debug.png&quot; data-lg-size=&quot;960-864&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_ss_debug.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_ss_debug_small.png&quot; alt=&quot;Debug menu&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_ss_thanks.png&quot; data-lg-size=&quot;960-864&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_ss_thanks.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_ss_thanks_small.png&quot; alt=&quot;Acknowledgements screen&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;h3&gt;Quick Start Guide&lt;/h3&gt;
&lt;p&gt;Press &lt;code&gt;Left&lt;/code&gt; or &lt;code&gt;Right&lt;/code&gt; on the title screen to show either the black or white highscores.
Press &lt;code&gt;Select&lt;/code&gt; to show the about screen and build info.
In-game press &lt;code&gt;Start&lt;/code&gt; to pause and resume.
While paused press &lt;code&gt;Select&lt;/code&gt; to return to the menu.&lt;/p&gt;
&lt;p&gt;Collect small white spheres to get +5 white score.
Collect small black spheres to get +5 black score.
The opposite color will reduce your score when collected.
Shooting while you have a white score will reduce it by one.
Large black holes will attract you and damage your ship when touched.
Large white spheres will repel you and replenish your health when touched.
Accelerating will reduce your fuel, which will recharge when not accelerating.
You can shoot large spheres for +10 points.&lt;/p&gt;
&lt;p&gt;For a more detailed description of the original game check out the &lt;a href=&quot;https://gta.fandom.com/wiki/Duality&quot;&gt;Duality article on GTA Wiki&lt;/a&gt; 😛&lt;/p&gt;
&lt;h3&gt;Links&lt;/h3&gt;
&lt;p&gt;The code is of course &lt;a href=&quot;https://codeberg.org/xythobuz/Duality&quot;&gt;freely available&lt;/a&gt; under the GPL license.
Also check out the automatically generated &lt;a href=&quot;https://xythobuz.github.io/Duality&quot;&gt;project website&lt;/a&gt; on GitHub Pages.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://xythobuz.github.io/Duality/cartridge.png&quot;&gt;&lt;a href=&quot;https://xythobuz.github.io/Duality/cartridge.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://xythobuz.github.io/Duality/cartridge.png&quot; alt=&quot;cartridge artwork&quot; style=&quot;max-width:300px;max-height:300px;&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_cart_1.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_cart_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_cart_1_small.jpg&quot; alt=&quot;flash carts (top)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_cart_2.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_cart_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_cart_2_small.jpg&quot; alt=&quot;flash carts (side)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;Toolchain&lt;/h2&gt;
&lt;p&gt;Fortunately the Game Boy is probably one of the retro hardware platforms with the best community-created documentation.
The most important piece from this community is &lt;a href=&quot;https://gbdev.io/pandocs/&quot;&gt;Pan Docs&lt;/a&gt;, a living document that has been extended and revised over the years that basically &lt;em&gt;is&lt;/em&gt; the reference manual for the platform.&lt;/p&gt;
&lt;p&gt;I wrote the whole game in C, which is very easy thanks to the &lt;a href=&quot;https://github.com/gbdk-2020/gbdk-2020/&quot;&gt;GBDK-2020&lt;/a&gt;.
It uses SDCC as the compiler with some custom tools to link the final ROM file.
Additionally it also includes a tool that can convert graphics to the proper hardware sprite tile and map format.
The GBDK also comes with &lt;a href=&quot;https://gbdk.org/docs/api/&quot;&gt;good documentation&lt;/a&gt; and lots of &lt;a href=&quot;https://github.com/gbdk-2020/gbdk-2020/tree/develop/gbdk-lib/examples&quot;&gt;examples&lt;/a&gt; that really make it easy to get started.&lt;/p&gt;
&lt;p&gt;Over the course of the project my &lt;code&gt;Makefile&lt;/code&gt; grew relatively big and customized.
Of course I've added the usual stuff like dependency file generation and git version information.
I'm also automatically generating the graphics data with the &lt;code&gt;png2asset&lt;/code&gt; tool.
To convert sound samples I've modified the &lt;code&gt;cvtsample.py&lt;/code&gt; tool from the GBDK examples.
And to pre-calculate some speed vector tables for different angles I wrote my own &lt;code&gt;gen_angles.py&lt;/code&gt; script.&lt;/p&gt;
&lt;p&gt;To find out how to convert the graphics assets I'm encoding the mode in the filename of the input images.&lt;/p&gt;
&lt;pre class=&quot;sh_makefile&quot; data-offset=&quot;146
&quot;&gt;
$(BUILD_DIR)/$(DATA_DIR)/%.c $(BUILD_DIR)/$(DATA_DIR)/%.h: $(DATA_DIR)/%.png Makefile
        @mkdir -p $(@D)
        $(eval SPRFLAG = $(shell echo &amp;quot;$&amp;lt;&amp;quot; | sed -n 's/.*_spr\([0-9]\+\).*/\-sw \1 \-sh \1/p'))
        $(eval FNTFLAG = $(shell echo &amp;quot;$&amp;lt;&amp;quot; | sed -n 's/.*_fnt\([0-9]\+\).*/\-sw \1 \-sh \1/p'))
        $(if $(findstring _map,$&amp;lt;),                                                             \
                @echo &amp;quot;Converting map $&amp;lt;&amp;quot; &amp;amp;&amp;amp;                                                    \
                $(PNGA) $&amp;lt; -o $@ -spr8x8 -map -noflip                                           \
        ,$(if $(findstring _fnt,$&amp;lt;),                                                            \
                @echo &amp;quot;Converting font $&amp;lt;&amp;quot; &amp;amp;&amp;amp;                                                   \
                $(PNGA) $&amp;lt; -o $@ -spr8x8 $(FNTFLAG) -map -noflip                                \
        ,$(if $(findstring _spr,$&amp;lt;),                                                            \
                @echo &amp;quot;Converting 8x8 sprite $&amp;lt;&amp;quot; &amp;amp;&amp;amp;                                             \
                $(PNGA) $&amp;lt; -o $@ -spr8x8 $(SPRFLAG) -noflip                                     \
        ,$(if $(findstring pause,$&amp;lt;),                                                           \
                @echo &amp;quot;Converting 40x16 sprite $&amp;lt;&amp;quot; &amp;amp;&amp;amp;                                           \
                $(PNGA) $&amp;lt; -o $@ -spr8x8 -sw 40 -sh 16 -noflip                                  \
        ,$(if $(findstring _sgb,$&amp;lt;),                                                            \
                @echo &amp;quot;Converting sgb border $&amp;lt;&amp;quot; &amp;amp;&amp;amp;                                             \
                $(PNGA) $&amp;lt; -o $@ -map -bpp 4 -max_palettes 4 -pack_mode sgb -use_map_attributes \
        ,                                                                                       \
                @echo &amp;quot;Converting tile $&amp;lt;&amp;quot; &amp;amp;&amp;amp;                                                   \
                $(PNGA) $&amp;lt; -o $@ -spr8x8                                                        \
        )))))&lt;/pre&gt;

&lt;p class=&quot;sh_link_upstream&quot;&gt;Link to the complete file &quot;&lt;a href=&quot;https://raw.githubusercontent.com/xythobuz/Duality/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/Makefile&quot;&gt;Makefile&lt;/a&gt;&quot;
 (&lt;a href=&quot;https://codeberg.org/xythobuz/Duality/raw/commit/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/Makefile&quot;&gt;alt 1&lt;/a&gt;)
&lt;/p&gt;

&lt;p&gt;Like with the documentation, the emulation ecosystem for the Game Boy is also very healthy.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I've mostly been using &lt;a href=&quot;https://github.com/drhelius/Gearboy&quot;&gt;Gearboy&lt;/a&gt; which includes some nice visualization tools for the hardware state.&lt;/li&gt;
&lt;li&gt;To test the Super Game Boy borders I used &lt;a href=&quot;https://sameboy.github.io/&quot;&gt;SameBoy&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A very nice symbolic C debbuger integration is available in &lt;a href=&quot;https://emulicious.net/&quot;&gt;Emulicious&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/shonumi/gbe-plus&quot;&gt;GBE+&lt;/a&gt; can also emulate a Game Boy Printer.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://problemkaputt.de/gmb.htm&quot;&gt;NO$GMB&lt;/a&gt; is able to emulate multiple linked Game Boy systems.&lt;/li&gt;
&lt;li&gt;While testing debugging I also played around with &lt;a href=&quot;https://bgb.bircd.org/&quot;&gt;BGB&lt;/a&gt; for a bit.&lt;/li&gt;
&lt;li&gt;And for one especially hard debugging session I used the reverse-time-step feature of &lt;a href=&quot;https://github.com/Rodrigodd/gameroy&quot;&gt;GameRoy&lt;/a&gt; with good success (to actually see the faulty jump to the wrong bank after it happened).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Windows-only emulators all ran fine using Wine.&lt;/p&gt;
&lt;p&gt;I described the Emulicious debugger integration to Kate (or VSCode I guess) in more detail &lt;a href=&quot;https://codeberg.org/xythobuz/Duality#ide-integration&quot;&gt;in the README&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Software&lt;/h2&gt;
&lt;p&gt;The game is really made for the Game Boy Color (GBC).
On the monochrome Game Boy (DMG) and the Super Game Boy (SGB) it runs at half-speed and the black and white spheres are hard to differentiate.
Or, to put it another way, I was not able to optimize the code enough that it run's at the DMG clock speed of ~1MHz.
So I had to cheat by putting the GBC CPU into a double-clock mode.&lt;/p&gt;
&lt;pre class=&quot;sh_c&quot; data-offset=&quot;646
&quot;&gt;
    // &amp;quot;cheat&amp;quot; and enable double-speed CPU mode on GBC
    if (_cpu == CGB_TYPE) {
        cpu_fast();
    }&lt;/pre&gt;

&lt;p class=&quot;sh_link_upstream&quot;&gt;Link to the complete file &quot;&lt;a href=&quot;https://raw.githubusercontent.com/xythobuz/Duality/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/main.c&quot;&gt;main.c&lt;/a&gt;&quot;
 (&lt;a href=&quot;https://codeberg.org/xythobuz/Duality/raw/commit/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/main.c&quot;&gt;alt 1&lt;/a&gt;)
&lt;/p&gt;

&lt;p&gt;To get some randomness into the gameplay I'm initially showing a splash screen after reset, where the user has to press &lt;code&gt;Start&lt;/code&gt;.
The timing of this button-press is used to initialize a random number generator that's later used to determine spawning positions of objects in the world map.&lt;/p&gt;
&lt;pre class=&quot;sh_c&quot; data-offset=&quot;662
&quot;&gt;
    splash();

#ifdef CONSTANT_SEED
    uint16_t seed = CONSTANT_SEED;
#else
    uint16_t seed = DIV_REG;
    waitpadup();
    seed |= ((uint16_t)DIV_REG) &amp;lt;&amp;lt; 8;
#endif

    // store in an SRAM variable, so the
    // value goes over the cartridge bus.
    // gives gb-interceptor a chance to see it.
    prng_seed = seed;
    initarand(prng_seed);&lt;/pre&gt;

&lt;p class=&quot;sh_link_upstream&quot;&gt;Link to the complete file &quot;&lt;a href=&quot;https://raw.githubusercontent.com/xythobuz/Duality/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/main.c&quot;&gt;main.c&lt;/a&gt;&quot;
 (&lt;a href=&quot;https://codeberg.org/xythobuz/Duality/raw/commit/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/main.c&quot;&gt;alt 1&lt;/a&gt;)
&lt;/p&gt;

&lt;p&gt;The splash screen in &lt;code&gt;main.c&lt;/code&gt; also handles the configuration and debug menus (try the Konami code).&lt;/p&gt;
&lt;p&gt;The main game loop in &lt;code&gt;game.c&lt;/code&gt; is pretty straight-forward, like with most game engines.
It basically does the following things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;read key inputs from user&lt;/li&gt;
&lt;li&gt;modify player and world state (according to input and time)&lt;/li&gt;
&lt;li&gt;draw the output graphics&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The world state is kept track of in &lt;code&gt;obj.c&lt;/code&gt;, where each shot and colored sphere is represented as an entry in an object list.&lt;/p&gt;
&lt;p&gt;Background music is played automatically on the pulse channels (1 and 2), as well as the noise channel (4).
Only the sample channel (3) is used for sound effects like shots or explosions.
Both of these are handled by interrupts, so they should never stutter or crackle (instead gameplay slows down).
For timekeeping I'm also configuring the internal timer and handle all of these tasks in the same ISR.&lt;/p&gt;
&lt;pre class=&quot;sh_c&quot; data-offset=&quot;29
&quot;&gt;
static void timer_isr(void) NONBANKED {
    sample_isr();
    snd_play();
    count++;
}&lt;/pre&gt;

&lt;p class=&quot;sh_link_upstream&quot;&gt;Link to the complete file &quot;&lt;a href=&quot;https://raw.githubusercontent.com/xythobuz/Duality/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/timer.c&quot;&gt;timer.c&lt;/a&gt;&quot;
 (&lt;a href=&quot;https://codeberg.org/xythobuz/Duality/raw/commit/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/timer.c&quot;&gt;alt 1&lt;/a&gt;)
&lt;/p&gt;

&lt;p&gt;One pattern I've used repeatedly is storing (&lt;code&gt;const&lt;/code&gt;) lists of &quot;things&quot; in ROM to be able to use them easily in other places.
This is used for sprites, background maps, sound samples and music.
For example, this is the start of the list of sprite graphics.&lt;/p&gt;
&lt;pre class=&quot;sh_c&quot; data-offset=&quot;56
&quot;&gt;
struct sprites metasprites[SPRITE_COUNT] = {
    { // SPR_SHIP
        .ms = rockshp_spr24_metasprites,
        .ms_n = ARR_LEN(rockshp_spr24_metasprites),
        .ti = rockshp_spr24_tiles,
        .pa = rockshp_spr24_palettes,
        .pa_n = rockshp_spr24_PALETTE_COUNT,
        .pa_i = OAMF_CGB_PAL0,
        .cnt = rockshp_spr24_TILE_COUNT,
        .off = TILE_NUM_START,
        .bank = BANK(rockshp_spr24),
    },
    { // SPR_LIGHT
        .ms = light_metasprites,
        .ms_n = ARR_LEN(light_metasprites),
        .ti = light_tiles,
        .pa = light_palettes,
        .pa_n = light_PALETTE_COUNT,
        .pa_i = OAMF_CGB_PAL2,
        .cnt = light_TILE_COUNT,
        .off = TILE_NUM_START,
        .bank = BANK(light),
    },
    { // SPR_DARK&lt;/pre&gt;

&lt;p class=&quot;sh_link_upstream&quot;&gt;Link to the complete file &quot;&lt;a href=&quot;https://raw.githubusercontent.com/xythobuz/Duality/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/sprite_data.c&quot;&gt;sprite_data.c&lt;/a&gt;&quot;
 (&lt;a href=&quot;https://codeberg.org/xythobuz/Duality/raw/commit/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/sprite_data.c&quot;&gt;alt 1&lt;/a&gt;)
&lt;/p&gt;

&lt;p&gt;By then calling the sprite functions the correct data is automatically loaded into VRAM and used accordingly.
The sprite and map lists are not &lt;code&gt;const&lt;/code&gt; because the offsets are calculated dynamically when loaded.
The music and sample lists are both &lt;code&gt;const&lt;/code&gt; though.&lt;/p&gt;
&lt;pre class=&quot;sh_c&quot; data-offset=&quot;73
&quot;&gt;
void spr_init(void);
void spr_init_pal(void);
void spr_draw(enum SPRITES sprite, enum SPRITE_FLIP flip, int8_t x_off, int8_t y_off, uint8_t frame, uint8_t *hiwater);
void spr_ship(enum SPRITE_ROT rot, uint8_t moving, uint8_t *hiwater);&lt;/pre&gt;

&lt;p class=&quot;sh_link_upstream&quot;&gt;Link to the complete file &quot;&lt;a href=&quot;https://raw.githubusercontent.com/xythobuz/Duality/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/sprites.h&quot;&gt;sprites.h&lt;/a&gt;&quot;
 (&lt;a href=&quot;https://codeberg.org/xythobuz/Duality/raw/commit/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/sprites.h&quot;&gt;alt 1&lt;/a&gt;)
&lt;/p&gt;

&lt;p&gt;It's not possible to rotate sprites by arbitrary angles, only the X and Y axis can be flipped individually.
So for each desired rotation a sprite has to be prepared manually.&lt;/p&gt;
&lt;p&gt;I decided to split the circle into 16 different angles, or 22.5 degree steps.
When taking advantage of the tile flipping we can get away with five different sprite rotations, from 0 degrees to 90 degrees.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/rockshp_spr24.png&quot; data-lg-size=&quot;768-576&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/rockshp_spr24.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/rockshp_spr24_small.png&quot; alt=&quot;ship spritesheet&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;To get the proper movement and shot velocity vectors for the current angle I'm also pre-calculating these tables.&lt;/p&gt;
&lt;pre class=&quot;sh_c&quot; data-skip=1&gt;
const int8_t table_speed_move[table_speed_move_SIZE] = {
    0, 23, // 0.0
    9, 21, // 22.5
    16, 16, // 45.0
    21, 9, // 67.5
    23, 0, // 90.0
    21, -9, // 112.5
    16, -16, // 135.0
    9, -21, // 157.5
    0, -23, // 180.0
    -9, -21, // 202.5
    -16, -16, // 225.0
    -21, -9, // 247.5
    -23, 0, // 270.0
    -21, 9, // 292.5
    -16, 16, // 315.0
    -9, 21, // 337.5
};
&lt;/pre&gt;

&lt;h3&gt;Graphics&lt;/h3&gt;
&lt;p&gt;To easily visualize the data in VRAM here are some screenshots from &lt;a href=&quot;https://github.com/drhelius/Gearboy&quot;&gt;Gearboy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There's not enough space to fit all graphics data at once, so I'm loading different subsets in the menu and in-game.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_vram_3.png&quot; data-lg-size=&quot;530-368&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_vram_3.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_vram_3_small.png&quot; alt=&quot;menu sprites&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_vram_2.png&quot; data-lg-size=&quot;398-296&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_vram_2.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_vram_2_small.png&quot; alt=&quot;menu tile data&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_vram_1.png&quot; data-lg-size=&quot;396-448&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_vram_1.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_vram_1_small.png&quot; alt=&quot;menu background map&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_vram_4.png&quot; data-lg-size=&quot;489-451&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_vram_4.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_vram_4_small.png&quot; alt=&quot;menu palettes&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;The first image shows the sprite objects in OAM. These are instances of tiles from the tile data.&lt;/li&gt;
&lt;li&gt;Tile data is shown in the upper left part of the second image.
     The middle and lower left parts of the second image contain the tile data for the background maps and window.
     The right half of the second image shows a second bank only available on GBC, which I use for a smaller font.&lt;/li&gt;
&lt;li&gt;The third image shows the background map, a large map of tiles that can be scrolled easily.&lt;/li&gt;
&lt;li&gt;The fourth image shows the palettes to colorize the tiles on GBC hardware.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_vram_7.png&quot; data-lg-size=&quot;531-366&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_vram_7.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_vram_7_small.png&quot; alt=&quot;in-game sprites&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_vram_6.png&quot; data-lg-size=&quot;398-297&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_vram_6.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_vram_6_small.png&quot; alt=&quot;in-game tile data&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_vram_5.png&quot; data-lg-size=&quot;395-446&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_vram_5.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_vram_5_small.png&quot; alt=&quot;in-game background map&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_vram_8.png&quot; data-lg-size=&quot;481-450&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_vram_8.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_vram_8_small.png&quot; alt=&quot;in-game palettes&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;You can only store a maximum of 40 tile instances in &lt;a href=&quot;https://gbdev.io/pandocs/OAM.html&quot;&gt;OAM&lt;/a&gt; at once, but there are &lt;a href=&quot;https://gbdev.io/pandocs/OAM.html#object-priority-and-conflicts&quot;&gt;more complicated rules&lt;/a&gt; on when they appear.
To colorize tiles the GBC adds a palette index to the attributes.&lt;/p&gt;
&lt;p&gt;The background map works similar to the OAM in that it indexes the background tile map, but the positions are fixed and &lt;a href=&quot;https://gbdev.io/pandocs/Tile_Maps.html#bg-map-attributes-cgb-mode-only&quot;&gt;the GBC has attributes&lt;/a&gt; for the map that don't exist on DMG.&lt;/p&gt;
&lt;h3&gt;Sound&lt;/h3&gt;
&lt;p&gt;The background music is simply stored as long lists of notes.
Here are some example excerpts from the score-screen music.&lt;/p&gt;
&lt;pre class=&quot;sh_c&quot; data-offset=&quot;(26, 42)
 data-skip=1
&quot;&gt;
#include &amp;lt;assert.h&amp;gt;

#include &amp;quot;banks.h&amp;quot;
#include &amp;quot;sound.h&amp;quot;
#include &amp;quot;sound_over.h&amp;quot;

BANKREF(sound_over)

static const enum notes over_notes[] = {
    // repeat 1

    Ab4,  C5, Ab4,  F4, Ab4, Ab4, Ab4,  C5,
    Ab4,  F4, Ab4, Bb4,  C5, Bb4, Ab4,  F4,

     A4,  C5,  A4,  F4,  A4,  A4,  A4,  C5,
     A4,  F4,  A4, Bb4,  C5, Bb4,  A4,  F4,


// ...

    END
};

#define ADD_LOOP \
    Ab2, SIL, Ab2, SIL, Ab2, SIL, Ab2, SIL, \
    Ab2, SIL, Ab2, SIL, Ab2, SIL, Ab2, SIL

#define ADD_LOOP_2 \
     A2, SIL,  A2, SIL,  A2, SIL,  A2, SIL, \
     A2, SIL,  A2, SIL,  A2, SIL,  A2, SIL

#define ADD_LOOP_3 \
     E2, SIL,  E2, SIL,  E2, SIL,  E2, SIL, \
     E2, SIL,  E2, SIL,  E2, SIL,  E2, SIL

static const enum notes over_notes2[] = {
    // repeat 1

    ADD_LOOP, ADD_LOOP_2,
    ADD_LOOP, ADD_LOOP_3,
    ADD_LOOP, ADD_LOOP_2,

    Db3, Db3, Db3, Db3, Db3, Db3, Db3, Db3,
    SIL, SIL, SIL, SIL, SIL, SIL, SIL, SIL,
    SIL, SIL, SIL, SIL, SIL, SIL, SIL, SIL,
    SIL, SIL, SIL, SIL, SIL, SIL, SIL, SIL,


// ...

    END
};

#define DRUM_LOOP \
    dKi, dSI, dSI, dSI, dSI, dSI, dKi, dSI, \
    dSn, dSI, dSI, dSI, dKi, dSI, dSI, dSI  \

static const enum drums over_drums[] = {
    DRUM_LOOP,
    DRUM_LOOP,
    DRUM_LOOP,
    DRUM_LOOP,
    DRUM_LOOP,
    DRUM_LOOP,
    DRUM_LOOP,
    DRUM_LOOP,
    DRUM_LOOP,
    DRUM_LOOP,


// ...

    dEND
};

static_assert(sizeof(over_notes)  == sizeof(over_notes2), &amp;quot;music loops need to be same length&amp;quot;);
static_assert(sizeof(over_notes2) == sizeof(over_drums),  &amp;quot;music loops need to be same length&amp;quot;);
static_assert(sizeof(over_notes)  == sizeof(over_drums),  &amp;quot;music loops need to be same length&amp;quot;);

const struct music music_over = {
    .notes = over_notes,
    .notes2 = over_notes2,
    .drums = over_drums,
    .duration = 120,
    .repeat = MUSIC_NO_REPEAT,
};&lt;/pre&gt;

&lt;p class=&quot;sh_link_upstream&quot;&gt;Link to the complete file &quot;&lt;a href=&quot;https://raw.githubusercontent.com/xythobuz/Duality/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/sound_over.c&quot;&gt;sound_over.c&lt;/a&gt;&quot;
 (&lt;a href=&quot;https://codeberg.org/xythobuz/Duality/raw/commit/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/sound_over.c&quot;&gt;alt 1&lt;/a&gt;)
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;over_notes&lt;/code&gt; and &lt;code&gt;over_notes2&lt;/code&gt; are the frequencies for the two pulse channels.
&lt;code&gt;over_drums&lt;/code&gt; has the IDs of different pre-defined settings for the noise channel.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;snd_play()&lt;/code&gt; function in &lt;code&gt;sound.c&lt;/code&gt; is then walking through this list after the note duration has elapsed, filling the sound hardware registers as needed.&lt;/p&gt;
&lt;pre class=&quot;sh_c&quot; data-offset=&quot;37
&quot;&gt;
BANKREF(sound)

const uint16_t frequencies[SILENCE] = {
      44,  156,  262,  363,  457,  547,  631,  710,  786,  854,  923,  986, //  0 .. 11
    1046, 1102, 1155, 1205, 1253, 1297, 1339, 1379, 1417, 1452, 1486, 1517, // 12 .. 23
    1546, 1575, 1602, 1627, 1650, 1673, 1694, 1714, 1732, 1750, 1767, 1783, // 24 .. 35
    1798, 1812, 1825, 1837, 1849, 1860, 1871, 1881, 1890, 1899, 1907, 1915, // 36 .. 47
    1923, 1930, 1936, 1943, 1949, 1954, 1959, 1964, 1969, 1974, 1978, 1982, // 48 .. 59
    1985, 1988, 1992, 1995, 1998, 2001, 2004, 2006, 2009, 2011, 2013, 2015  // 60 .. 71
};

static volatile struct music const * music = NULL;
static volatile uint8_t bank;
static volatile uint8_t duration;
static volatile uint16_t off = 0;
static volatile uint16_t last_t = 0;

struct snds {
    uint8_t bank;
    struct music const * snd;
};

static const struct snds snds[SND_COUNT] = {
    { .bank = BANK(sound_menu), .snd = &amp;amp;music_menu }, // SND_MENU
    { .bank = BANK(sound_game), .snd = &amp;amp;music_game }, // SND_GAME
    { .bank = BANK(sound_over), .snd = &amp;amp;music_over }, // SND_GAMEOVER
};

#define CALL_FREQ_HZ 256

static void play_note(enum notes note) NONBANKED {
    if (note &amp;lt; SILENCE) {
        uint16_t freq;
        START_ROM_BANK(BANK(sound)) {
            freq = frequencies[note];
        } END_ROM_BANK

        NR11_REG = 0x80 | duration; // 50% duty, higher value is shorter time (up to 0x3F)
        NR12_REG = (conf_get()-&amp;gt;music_vol &amp;lt;&amp;lt; 4) | 0x00; // given volume, no change
        NR13_REG = freq &amp;amp; 0xFF; // given frequency
        NR14_REG = 0x80 | 0x40 | ((freq &amp;gt;&amp;gt; 8) &amp;amp; 0x07); // trigger, enable length, upper freq bits
    }
}

static void play_note2(enum notes note) NONBANKED {
    if (note &amp;lt; SILENCE) {
        uint16_t freq;
        START_ROM_BANK(BANK(sound)) {
            freq = frequencies[note];
        } END_ROM_BANK

        NR21_REG = 0x80 | duration; // 50% duty, higher value is shorter time (up to 0x3F)
        NR22_REG = (conf_get()-&amp;gt;music_vol &amp;lt;&amp;lt; 4) | 0x00; // given volume, no change
        NR23_REG = freq &amp;amp; 0xFF; // given frequency
        NR24_REG = 0x80 | 0x40 | ((freq &amp;gt;&amp;gt; 8) &amp;amp; 0x07); // trigger, enable length, upper freq bits
    }
}

static void play_drum(enum drums drum) NONBANKED {
    switch (drum) {
        case dKick:
            NR41_REG = 0x2F; // length timer, higher value is shorter time (up to 0x3F)
            NR42_REG = (conf_get()-&amp;gt;music_vol &amp;lt;&amp;lt; 4) | 0x00; // initially full volume, no volume changes over time
            NR43_REG = 0x11; // frequency distribution
            NR44_REG = 0xC0; // trigger and enable length
            break;

        case dSnare:
            NR41_REG = 0x10; // length timer, higher value is shorter time (up to 0x3F)
            NR42_REG = (conf_get()-&amp;gt;music_vol &amp;lt;&amp;lt; 4) | 0x02; // initially full volume, then fade sound out
            NR43_REG = 0x46; // frequency distribution
            NR44_REG = 0xC0; // trigger and enable length
            break;

        default:
            break;
    }
}

void snd_init(void) BANKED {
    NR52_REG = 0x80; // sound on
    NR51_REG = 0xFF; // all channels on left and right

#ifdef DEBUG
    NR50_REG = 0x33; // left and right on half volume
#else
    NR50_REG = 0x77; // left and right on full volume
#endif
}

void snd_music_off(void) BANKED {
    CRITICAL {
        music = NULL;
    }
}

void snd_note_off(void) BANKED {
    play_note(SILENCE);
    play_note2(SILENCE);
}

static uint16_t snd_duration(enum SOUNDS snd) NONBANKED {
    uint16_t r;
    START_ROM_BANK(snds[snd].bank) {
        r = snds[snd].snd-&amp;gt;duration;
    } END_ROM_BANK
    return r;
}

void snd_music(enum SOUNDS snd) BANKED {
    if (snd &amp;gt;= SND_COUNT) {
        return;
    }

    CRITICAL {
        music = snds[snd].snd;
        bank = snds[snd].bank;
        duration = 0x3F - MIN((snd_duration(snd) &amp;gt;&amp;gt; 2) + 1, 0x3F);
        off = 0;
        last_t = timer_get();
    }
}

void snd_play(void) NONBANKED {
    if (!music) {
        return;
    }

    START_ROM_BANK(bank) {
        uint16_t diff = timer_get() - last_t;
        if (diff &amp;gt;= music-&amp;gt;duration) {
            if (music-&amp;gt;notes) {
                if (music-&amp;gt;notes[off] != END) {
                    play_note(music-&amp;gt;notes[off]);
                } else {
                    if (music-&amp;gt;repeat != MUSIC_NO_REPEAT) {
                        off = music-&amp;gt;repeat;
                        snd_play();
                        goto end;
                    } else {
                        music = NULL;
                        goto end;
                    }
                }
            }

            if (music &amp;amp;&amp;amp; music-&amp;gt;notes2) {
                if (music-&amp;gt;notes2[off] != END) {
                    play_note2(music-&amp;gt;notes2[off]);
                } else {
                    if (music-&amp;gt;repeat != MUSIC_NO_REPEAT) {
                        off = music-&amp;gt;repeat;
                        snd_play();
                        goto end;
                    } else {
                        music = NULL;
                        goto end;
                    }
                }
            }

            if (music &amp;amp;&amp;amp; music-&amp;gt;drums) {
                if (music-&amp;gt;drums[off] != dEND) {
                    play_drum(music-&amp;gt;drums[off]);
                } else {
                    if (music-&amp;gt;repeat != MUSIC_NO_REPEAT) {
                        off = music-&amp;gt;repeat;
                        snd_play();
                        goto end;
                    } else {
                        music = NULL;
                        goto end;
                    }
                }
            }

            off++;
            last_t += music-&amp;gt;duration;
        }

end:
    } END_ROM_BANK
}&lt;/pre&gt;

&lt;p class=&quot;sh_link_upstream&quot;&gt;Link to the complete file &quot;&lt;a href=&quot;https://raw.githubusercontent.com/xythobuz/Duality/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/sound.c&quot;&gt;sound.c&lt;/a&gt;&quot;
 (&lt;a href=&quot;https://codeberg.org/xythobuz/Duality/raw/commit/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/sound.c&quot;&gt;alt 1&lt;/a&gt;)
&lt;/p&gt;

&lt;p&gt;For the sound effect sample player I've transcribed the assembly ISR from the GBDK examples to C, which was a fun little exercise in SM83 assembly.&lt;/p&gt;
&lt;pre class=&quot;sh_c&quot; data-offset=&quot;88
&quot;&gt;
#if 1

// TODO C version has a slight 'beep' always? and much worse at lower volumes?

void sample_isr(void) NONBANKED {
    if (play_length == 0) {
        return;
    }

    NR51_REG = 0xBB; // turn CH3 off in left and right pan
    NR30_REG = 0x00; // turn DAC off

    START_ROM_BANK(play_bank) {
        // load waveforms
        for (uint8_t i = 0; i &amp;lt; 16; i++) {
            _AUD3WAVERAM[i] = *(play_sample++);
        }
    } END_ROM_BANK

    NR30_REG = 0x80; // turn DAC on
    NR31_REG = 0xFE; // length of wave, 2nd shortest
    NR32_REG = (4 - /*conf_get()-&amp;gt;sfx_vol*/0x03) &amp;lt;&amp;lt; 5;
    NR33_REG = 0x00; // low freq bits are zero
    NR34_REG = 0xC7; // start, no loop, high freq bits are 111
    NR51_REG = 0xFF; // turn all channels on

    play_length--;
}

#else

// TODO ASM version has less beep at full volume, but also beeping at lower volumes?

void sample_isr(void) NONBANKED NAKED {
    __asm
        ld hl, #_play_length    ; something left to play?
        ld a, (hl+)
        or (hl)
        ret z

        ld hl, #_play_sample
        ld a, (hl+)
        ld h, (hl)
        ld l, a                 ; HL = current position inside the sample

                                ; load new waveform
        ld a, (#__current_bank) ; save bank and switch
        ld e, a
        ld a, (#_play_bank)
        ld (_rROMB0), a

        ldh a, (_NR51_REG)
        ld c, a
        and #0b10111011
        ldh (_NR51_REG), a

        xor a
        ldh (_NR30_REG), a

        .irp ofs,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
            ld a, (hl+)
            ldh (__AUD3WAVERAM+ofs), a
        .endm

        ld a, #0x80
        ldh (_NR30_REG), a
        ld a, #0xFE             ; length of wave
        ldh (_NR31_REG), a
        ld a, #0x20             ; volume
        ldh (_NR32_REG), a
        xor a                   ; low freq bits are zero
        ldh (_NR33_REG), a
        ld a, #0xC7             ; start; no loop; high freq bits are 111
        ldh (_NR34_REG), a

        ld a, c
        ldh (_NR51_REG), a

        ld a, e                 ; restore bank
        ld (_rROMB0), a

        ld a, l                 ; save current position
        ld (#_play_sample), a
        ld a, h
        ld (#_play_sample+1), a

        ld hl, #_play_length    ; decrement length variable
        ld a, (hl)
        sub #1
        ld (hl+), a
        ld a, (hl)
        sbc #0
        ld (hl), a
        ret
    __endasm;
}
#endif&lt;/pre&gt;

&lt;p class=&quot;sh_link_upstream&quot;&gt;Link to the complete file &quot;&lt;a href=&quot;https://raw.githubusercontent.com/xythobuz/Duality/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/sample.c&quot;&gt;sample.c&lt;/a&gt;&quot;
 (&lt;a href=&quot;https://codeberg.org/xythobuz/Duality/raw/commit/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/sample.c&quot;&gt;alt 1&lt;/a&gt;)
&lt;/p&gt;

&lt;h3&gt;Banking&lt;/h3&gt;
&lt;p&gt;Probably the most important topic in Game Boy software development is memory banking.
Take a look at the memory map of the system.&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;th&gt;Start&lt;/th&gt;
&lt;th&gt;End&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;tr style='font-size: larger; font-weight: bold; line-height: 4lh'&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0x0000
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0x3FFF
&lt;/td&gt;
&lt;td&gt;
16 KiB ROM bank 00
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style='font-size: larger; font-weight: bold; line-height: 4lh'&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0x4000
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0x7FFF
&lt;/td&gt;
&lt;td&gt;
16 KiB ROM Bank 01 – NN
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style='line-height: 2lh'&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0x8000
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0x9FFF
&lt;/td&gt;
&lt;td&gt;
8 KiB Video RAM (VRAM)
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style='font-size: larger; font-weight: bold; line-height: 2lh'&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xA000
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xBFFF
&lt;/td&gt;
&lt;td&gt;
8 KiB External RAM (SRAM)
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style='line-height: 1lh'&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xC000
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xCFFF
&lt;/td&gt;
&lt;td&gt;
4 KiB Work RAM (WRAM)
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style='line-height: 1lh'&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xD000
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xDFFF
&lt;/td&gt;
&lt;td&gt;
4 KiB Work RAM (WRAM)
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style='line-height: 2lh'&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xE000
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xFDFF
&lt;/td&gt;
&lt;td&gt;
Echo RAM (mirrors WRAM)
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style='font-size: smaller'&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xFE00
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xFE9F
&lt;/td&gt;
&lt;td&gt;
Object attribute memory (OAM)
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style='font-size: small'&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xFEA0
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xFEFF
&lt;/td&gt;
&lt;td&gt;
Not Usable
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style='font-size: smaller'&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xFF00
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xFF7F
&lt;/td&gt;
&lt;td&gt;
I/O Registers
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style='font-size: smaller'&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xFF80
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xFFFE
&lt;/td&gt;
&lt;td&gt;
High RAM (HRAM)
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style='font-size: x-small'&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xFFFF
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: center;&quot;&gt;
0xFFFF
&lt;/td&gt;
&lt;td&gt;
Interrupt Enable register (IE)
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;Only the 32KiB ROM bank areas at &lt;code&gt;0x0000 - 0x7FFF&lt;/code&gt; and the 8KiB RAM at &lt;code&gt;0xA000 - 0xBFFF&lt;/code&gt; are coming from the cartridge.
The cartridge RAM is usually used to store persistent savegames and configs on a battery backed SRAM.
The code and data live in the first 32KiB.&lt;/p&gt;
&lt;p&gt;Of course for many games these 32KiB are not enough, so they use some special hardware in the cartridge to map different memory chips to the same address range, depending on a configuration register.
This is accomplished by Nintendos &lt;a href=&quot;https://gbdev.io/pandocs/MBCs.html&quot;&gt;Memory Bank Controllers&lt;/a&gt;, most commonly (and for this game) the &lt;a href=&quot;https://gbdev.io/pandocs/MBC5.html&quot;&gt;MBC5&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Each bank has a size of 16KiB, with the first bank (0) always mapped to the first 16KiB of the address space.
The next half of the ROM area is for the switchable bank, which can be selected by writing to the MBC-internal registers.&lt;/p&gt;
&lt;p&gt;Fortunately GBDK already contains a bunch of helper functions that make this all a bit easier.
For the ROM banks we have to differentiate the following cases.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;const data in a bank&lt;/li&gt;
&lt;li&gt;functions in bank 0 (non-banked)&lt;/li&gt;
&lt;li&gt;non-static functions in a bank&lt;/li&gt;
&lt;li&gt;static functions (in a bank)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Also note that with the GBDK and SDCC every compilation unit (so each &lt;code&gt;.c&lt;/code&gt; file) can only be part of one bank.
Which unit goes into which bank can be decided automatically (autobanking).&lt;/p&gt;
&lt;p&gt;First enable autobanking by passing the proper compiler flag &lt;code&gt;-Wm-yoA&lt;/code&gt;.
Now, at the start of each &lt;code&gt;.c&lt;/code&gt; and &lt;code&gt;.h&lt;/code&gt; files, declare a reference to the bank of this compilation unit.&lt;/p&gt;
&lt;pre class=&quot;sh_c&quot; data-skip=1&gt;
/* in some_name.c */
BANKREF(some_name)

/* in some_name.h */
BANKREF_EXTERN(some_name)
&lt;/pre&gt;

&lt;p&gt;Of course each unit needs a unique &lt;code&gt;some_name&lt;/code&gt; (does not need to be the filename).&lt;/p&gt;
&lt;p&gt;Now you can declare your non-static functions with an attribute to place them in the correct bank.&lt;/p&gt;
&lt;pre class=&quot;sh_c&quot; data-skip=1&gt;
void foo(void) BANKED;
void bar(void) NONBANKED;
&lt;/pre&gt;

&lt;p&gt;When you call a &lt;code&gt;BANKED&lt;/code&gt; function a trampoline in bank 0 automatically takes care of proper bank switching for you.
Only when calling static functions not marked as &lt;code&gt;BANKED&lt;/code&gt; (case 4 from above) you need to make sure you're either already in the correct bank (by coming from a &lt;code&gt;BANKED&lt;/code&gt; function from the same compilation unit), or to switch banks manually (when coming from a &lt;code&gt;NONBANKED&lt;/code&gt; function).&lt;/p&gt;
&lt;p&gt;To easily bank-switch I made some small helper macros.&lt;/p&gt;
&lt;pre class=&quot;sh_c&quot; data-offset=&quot;25
&quot;&gt;
#define START_ROM_BANK_2(x) __xyz_previous__bank = CURRENT_BANK; SWITCH_ROM(x); do
#define START_ROM_BANK(x) uint8_t START_ROM_BANK_2(x)
#define END_ROM_BANK while (0); SWITCH_ROM(__xyz_previous__bank);&lt;/pre&gt;

&lt;p class=&quot;sh_link_upstream&quot;&gt;Link to the complete file &quot;&lt;a href=&quot;https://raw.githubusercontent.com/xythobuz/Duality/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/banks.h&quot;&gt;banks.h&lt;/a&gt;&quot;
 (&lt;a href=&quot;https://codeberg.org/xythobuz/Duality/raw/commit/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/banks.h&quot;&gt;alt 1&lt;/a&gt;)
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const&lt;/code&gt; data has the same restrictions, so when you need to read &lt;code&gt;const&lt;/code&gt; data from one bank in a function from another compilation unit you may need to add a small &lt;code&gt;NONBANKED&lt;/code&gt; helper function.&lt;/p&gt;
&lt;pre class=&quot;sh_c&quot; data-offset=&quot;209
&quot;&gt;
static uint8_t is_conf_hw(uint8_t i) NONBANKED {
    uint8_t r;
    START_ROM_BANK(BANK(main)) {
        r = (conf_entries[i].type == HW_ALL) || (conf_entries[i].type == get_hw());
    } END_ROM_BANK
    return r;
}&lt;/pre&gt;

&lt;p class=&quot;sh_link_upstream&quot;&gt;Link to the complete file &quot;&lt;a href=&quot;https://raw.githubusercontent.com/xythobuz/Duality/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/window.c&quot;&gt;window.c&lt;/a&gt;&quot;
 (&lt;a href=&quot;https://codeberg.org/xythobuz/Duality/raw/commit/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/window.c&quot;&gt;alt 1&lt;/a&gt;)
&lt;/p&gt;

&lt;p&gt;Of course you can never directly switch banks inside a function that is not &lt;code&gt;NONBANKED&lt;/code&gt; as this would replace the currently executed opcodes.&lt;/p&gt;
&lt;p&gt;Initially I started out with lots of functions marked as &lt;code&gt;NONBANKED&lt;/code&gt;, but this turned out to be unnecessary.
By properly modularizing your code and liberally using &lt;code&gt;BANKED&lt;/code&gt; you can get the compiler to do most of the work for you.&lt;/p&gt;
&lt;p&gt;It's also relatively easy to spot banking errors.
Most of the time the VRAM will quickly fill with some regular pattern, like horizontal or vertical lines.
Or a debugger shows you suddenly in the middle of nowhere.
But I also had some devious cases, where the control flow jumped into some legitimate code that caused strange effects (wrong sound effects, no objects appearing, but the game still ran).
These took me a while to figure out, until I noticed a &lt;code&gt;return&lt;/code&gt; that skipped the final &lt;code&gt;END_ROM_BANK&lt;/code&gt; of a function, thereby forgetting to switch-back the bank.&lt;/p&gt;
&lt;p&gt;RAM banks, in comparison, can be handled more easily.
I'm simply enabling and setting RAM bank 0 at the beginning, before reading the config from there, and always keep it enabled while the game is running.&lt;/p&gt;
&lt;pre class=&quot;sh_c&quot; data-offset=&quot;51
&quot;&gt;
void conf_init(void) BANKED {
    ENABLE_RAM;
    SWITCH_RAM(0);

    if (calc_crc() != mem.crc) {
        mem.config.debug_flags = 0;
        //mem.config.sfx_vol = 0x03;
        mem.config.music_vol = 0x07;

        if (_cpu == CGB_TYPE) {
            mem.config.game_bg = 0;
        } else {
            mem.config.game_bg = 1;
        }

        mem.config.dmg_bg_inv = 1;

        score_reset();

        mem.state.in_progress = 0;
    }
}&lt;/pre&gt;

&lt;p class=&quot;sh_link_upstream&quot;&gt;Link to the complete file &quot;&lt;a href=&quot;https://raw.githubusercontent.com/xythobuz/Duality/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/config.ba0.c&quot;&gt;config.ba0.c&lt;/a&gt;&quot;
 (&lt;a href=&quot;https://codeberg.org/xythobuz/Duality/raw/commit/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/config.ba0.c&quot;&gt;alt 1&lt;/a&gt;)
&lt;/p&gt;

&lt;p&gt;Similar to the &lt;code&gt;png2asset&lt;/code&gt; calls the RAM bank of a compilation unit is specified in the filename (&lt;code&gt;foo.baN.c&lt;/code&gt; where N is the RAM bank number).&lt;/p&gt;
&lt;pre class=&quot;sh_makefile&quot; data-offset=&quot;170
&quot;&gt;
$(BUILD_DIR)/%.o: %.c $(ASSETS) Makefile
        @mkdir -p $(@D)
        @echo Compiling Code $&amp;lt;
        $(eval BAFLAG = $(shell echo &amp;quot;$&amp;lt;&amp;quot; | sed -n 's/.*\.ba\([0-9]\+\).*/\-Wf-ba\1/p'))
        @$(LCC) $(LCCFLAGS) $(BAFLAG) -c -o $@ $&amp;lt;&lt;/pre&gt;

&lt;p class=&quot;sh_link_upstream&quot;&gt;Link to the complete file &quot;&lt;a href=&quot;https://raw.githubusercontent.com/xythobuz/Duality/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/Makefile&quot;&gt;Makefile&lt;/a&gt;&quot;
 (&lt;a href=&quot;https://codeberg.org/xythobuz/Duality/raw/commit/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/Makefile&quot;&gt;alt 1&lt;/a&gt;)
&lt;/p&gt;

&lt;p&gt;This is what the memory usage for my game looks like at the moment.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Bank         Range                Size     Used  Used%     Free  Free% 
--------     ----------------  -------  -------  -----  -------  -----
ROM_0        0x0000 -&amp;gt; 0x3FFF    16384    10466    64%     5918    36%
ROM_1        0x4000 -&amp;gt; 0x7FFF    16384    16383   100%        1     0%
ROM_2        0x4000 -&amp;gt; 0x7FFF    16384    16377   100%        7     0%
ROM_3        0x4000 -&amp;gt; 0x7FFF    16384    16383   100%        1     0%
ROM_4        0x4000 -&amp;gt; 0x7FFF    16384     5903    36%    10481    64%
SRAM_0       0xA000 -&amp;gt; 0xBFFF     8192      427     5%     7765    95%
WRAM_LO      0xC000 -&amp;gt; 0xCFFF     4096     1650    40%     2446    60%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So I'm using five ROM banks in total for code and data (which has to be rounded up to eight banks, or 128KiB, as cartridges only can &lt;a href=&quot;https://gbdev.io/pandocs/The_Cartridge_Header.html#0148--rom-size&quot;&gt;speficy ROM bank counts&lt;/a&gt; that are a power of two).
And a single SRAM bank (8KiB) for persistent highscores, configs and a savegame, which is the smallest number of &lt;a href=&quot;https://gbdev.io/pandocs/The_Cartridge_Header.html#0149--ram-size&quot;&gt;SRAM banks possible&lt;/a&gt;.
Take care not to fill ROM bank 0 &lt;a href=&quot;https://gbdk.org/docs/api/docs_faq.html#faq_bank_overflow_errors&quot;&gt;too close to the limit&lt;/a&gt;, as the ROM header is not always properly taken into account in all tools.&lt;/p&gt;
&lt;h3&gt;Game Boy Printer&lt;/h3&gt;
&lt;p&gt;The GBDK already comes with a &lt;a href=&quot;https://github.com/gbdk-2020/gbdk-2020/tree/develop/gbdk-lib/examples/gb/gbprinter&quot;&gt;Game Boy Printer example&lt;/a&gt; which I cleaned up and modified.
Instead of reading a converted image I'm directly getting the tile data from VRAM to print either the window or background map.
The data is read in blocks of two tile rows which are blacked out after transmission as a progress indicator.&lt;/p&gt;
&lt;pre class=&quot;sh_c&quot; data-offset=&quot;176
&quot;&gt;
enum PRN_STATUS gbprinter_screenshot(uint8_t win, uint8_t palette) BANKED {
    static uint8_t tile_buff[2 * DEVICE_SCREEN_WIDTH * 16];
    static struct prn_config params;
    enum PRN_STATUS r = PRN_STATUS_OK;

    printer_send_command(PRN_CMD_INIT, NULL, 0);

    for (int y = 0; y &amp;lt; DEVICE_SCREEN_HEIGHT; y += 2) {
        for (int y2 = 0; y2 &amp;lt; 2; y2++) {
            for (int x = 0; x &amp;lt; DEVICE_SCREEN_WIDTH; x++) {
                uint8_t tile = win ? get_win_tile_xy(x, y + y2) : get_bkg_tile_xy(x, y + y2);
                win ? get_win_data(tile, 1, tile_buff + ((x + (y2 * DEVICE_SCREEN_WIDTH)) * 16))
                    : get_bkg_data(tile, 1, tile_buff + ((x + (y2 * DEVICE_SCREEN_WIDTH)) * 16));
            }

            // black out rows we have sent, to indicate transfer progress
            win ? fill_win_rect(0, y + y2, DEVICE_SCREEN_WIDTH, 1, 0)
                : fill_bkg_rect(0, y + y2, DEVICE_SCREEN_WIDTH, 1, 0);
        }

        if (win) {
            if (y == 0) {
                win_str_helper(&amp;quot;gb printer&amp;quot;, 0);
            } else if (y == 2) {
                win_str_helper(&amp;quot;transmit&amp;quot;, 2);
            } else if (y == 8) {
                win_str_helper(&amp;quot;in&amp;quot;, 8);
            } else if (y == 10) {
                win_str_helper(&amp;quot;progress&amp;quot;, 10);
            } else if (y == 16) {
                win_str_helper(&amp;quot;printing&amp;quot;, 16);
            }
        }

        r = printer_send_command(PRN_CMD_DATA, tile_buff, sizeof(tile_buff));
        if ((r &amp;amp; ~PRN_STATUS_UNTRAN) != PRN_STATUS_OK) {
            r |= PRN_STATUS_AT_DATA;
            goto end;
        }

        if (printer_check_cancel()) {
            printer_send_command(PRN_CMD_BREAK, NULL, 0);
            r |= PRN_STATUS_CANCELLED;
            goto end;
        }
    }

    printer_send_command(PRN_CMD_DATA, NULL, 0);

    params.sheets = 1;
    params.margins = PRN_FINAL_MARGIN;
    params.palette = palette;
    params.exposure = PRN_EXPO_DARK;

    printer_send_command(PRN_CMD_PRINT, (uint8_t *)&amp;amp;params, sizeof(struct prn_config));

    r = printer_wait(PRN_BUSY_TIMEOUT, PRN_STATUS_BUSY, PRN_STATUS_BUSY);
    if ((r &amp;amp; ~(PRN_STATUS_FULL | PRN_STATUS_TIMEOUT)) &amp;amp; PRN_STATUS_MASK_ERRORS) {
        r |= PRN_STATUS_AT_BUSY;
        goto end;
    }

    r = printer_wait(PRN_PRINT_TIMEOUT, PRN_STATUS_BUSY, 0);
    if ((r &amp;amp; ~PRN_STATUS_FULL) &amp;amp; PRN_STATUS_MASK_ERRORS) {
        r |= PRN_STATUS_AT_FINAL;
        goto end;
    }

end:
#ifdef DEBUG
    EMU_printf(&amp;quot;%s: 0x%04x\n&amp;quot;,  __func__, (uint16_t)r);
#endif // DEBUG
    return (r &amp;amp; ~PRN_STATUS_FULL);
}&lt;/pre&gt;

&lt;p class=&quot;sh_link_upstream&quot;&gt;Link to the complete file &quot;&lt;a href=&quot;https://raw.githubusercontent.com/xythobuz/Duality/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/gbprinter.c&quot;&gt;gbprinter.c&lt;/a&gt;&quot;
 (&lt;a href=&quot;https://codeberg.org/xythobuz/Duality/raw/commit/4315e0f8c17c29cfeb3be8a3eda745ff6a51b450/src/gbprinter.c&quot;&gt;alt 1&lt;/a&gt;)
&lt;/p&gt;

&lt;p&gt;Here is a screencast of printing in the &lt;a href=&quot;https://github.com/shonumi/gbe-plus&quot;&gt;GBE+&lt;/a&gt; emulator and a copy of the results.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-video='{&quot;source&quot;: [{&quot;src&quot;:&quot;img/duality_print_anim.webm&quot;, &quot;type&quot;:&quot;video/webm&quot;}], &quot;attributes&quot;: {&quot;preload&quot;: false, &quot;playsinline&quot;: true, &quot;controls&quot;: true}}' data-lg-size=&quot;479-431&quot; data-poster=&quot;img/duality_print_anim_poster.png&quot; data-sub-html=&quot;print screen animation&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_print_anim.webm&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_print_anim_thumb.png&quot; alt=&quot;print screen animation&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_score_print.png&quot; data-lg-size=&quot;640-576&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_score_print.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_score_print_small.png&quot; alt=&quot;digital score printout&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;And this is a recording of my actual old childhood GB Printer in action.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-video='{&quot;source&quot;: [{&quot;src&quot;:&quot;img/duality_print_video.webm&quot;, &quot;type&quot;:&quot;video/webm&quot;}], &quot;attributes&quot;: {&quot;preload&quot;: false, &quot;playsinline&quot;: true, &quot;controls&quot;: true}}' data-lg-size=&quot;960-540&quot; data-poster=&quot;img/duality_print_video_poster.png&quot; data-sub-html=&quot;printing process&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_print_video.webm&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_print_video_thumb.png&quot; alt=&quot;printing process&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_score_print.jpg&quot; data-lg-size=&quot;1600-1549&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_score_print.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_score_print_small.jpg&quot; alt=&quot;physical score printout&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;Physical Cartridges&lt;/h2&gt;
&lt;p&gt;To build physical cartridges I ordered two &lt;a href=&quot;https://de.aliexpress.com/item/1005008596849158.html&quot;&gt;flashcarts&lt;/a&gt; as well as &lt;a href=&quot;https://de.aliexpress.com/item/1005006340286928.html&quot;&gt;cartridge cases&lt;/a&gt; in black and white.&lt;/p&gt;
&lt;p&gt;Unfortunately the flash carts use FRAM and a custom CPLD implementation to replace the mapper chip.
It seems the timings or voltages or some other electrical or physical parameters are not quite correct.
I can't get them to work in the &lt;a href=&quot;https://github.com/Staacks/gbinterceptor&quot;&gt;GB Interceptor&lt;/a&gt; for example.
But in my real DMG and GBC they work fine.&lt;/p&gt;
&lt;p&gt;I'm using &lt;a href=&quot;https://de.aliexpress.com/item/1005007652321830.html&quot;&gt;this clone writer&lt;/a&gt;, although I wouldn't really recommend it.
It's very slow and can't be updated, probably due to being a chinese clone.
Also the &lt;a href=&quot;https://github.com/simonkwng/GBFlash&quot;&gt;&quot;upstream repo&quot;&lt;/a&gt; is completely empty.
Unfortunately all other writers supported by &lt;a href=&quot;https://github.com/lesserkuma/FlashGBX&quot;&gt;FlashGBX&lt;/a&gt; are not really free hardware or software either.&lt;/p&gt;
&lt;p&gt;To write to the carts I'm using the &lt;code&gt;DIY cart with MX29LV640 @ WR&lt;/code&gt; setting of FlashGBX.&lt;/p&gt;
&lt;p&gt;I ordered three pieces of the label printed on foil, with the rounded corners cut out, at &lt;a href=&quot;https://www.klebefisch.de/eigenes-motiv/aufkleber-drucken&quot;&gt;Klebefisch&lt;/a&gt;, for 30€ including postage, or about 10€ per label.
The parameters were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;White foil&lt;/li&gt;
&lt;li&gt;Cut outline&lt;/li&gt;
&lt;li&gt;Not mirrored&lt;/li&gt;
&lt;li&gt;42mm by 37mm&lt;/li&gt;
&lt;li&gt;1.5mm corner radius&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The size and finish of the sticker are great, but unfortunately the print quality is pretty bad with a low resolution (even though I sent high-res files with 2100px x 1850px).&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_cart_3.jpg&quot; data-lg-size=&quot;1043-893&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_cart_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/duality_cart_3_small.jpg&quot; alt=&quot;printed cartridge label&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;But still the finished cartridges look and feel great!
It's nice to have something physical in hand instead of &quot;just&quot; software.&lt;/p&gt;
&lt;p&gt;The cost for these came out to around 15€ per piece from AliExpress, excluding the label and working hours.
There are other manufacturers of new carts for homebrew games, like &lt;a href=&quot;https://shop.insidegadgets.com/product/custom-gameboy-flash-cart/&quot;&gt;insideGadgets&lt;/a&gt;, but surprisingly they are not really much cheaper, even for larger production runs.&lt;/p&gt;
&lt;p&gt;It's also possible to manufacture your own cartridge PCBs, but there are some caveats with this.
The main problem is the memory bank controller, in my case the most common &lt;code&gt;MBC5&lt;/code&gt; from Nintendo.
They are no longer manufactured and can not be sourced new, so you either have to salvage them from original old donor games (which hurts the archivist in me too much) or find some kind of replacement.&lt;/p&gt;
&lt;p&gt;Different people came up with implementations based on CPLDs or microcontrollers.
Some of them are even open-source, like the &lt;a href=&quot;https://github.com/insidegadgets/Gameboy-MBC5-MBC1-Hybrid&quot;&gt;MBC5 CPLD code from insideGadgets&lt;/a&gt; or &lt;a href=&quot;https://abc.decontextualize.com/&quot;&gt;Allison's Bootleg Cart&lt;/a&gt;.
In a &lt;a href=&quot;https://github.com/sillyhatday/GAMEBOY-CPLD-FRAM-2MB&quot;&gt;modern design&lt;/a&gt; you can use this with an FRAM chip to avoid the need for a backup battery for savegames, although this comes with some timing incompatibilities that may give problems with some games.
You can also &lt;a href=&quot;https://github.com/sillyhatday/GAMEBOY-CPLD-SRAM-2MB&quot;&gt;use a design&lt;/a&gt; with an SRAM and a coin cell.
Many different flash chips are compatible in theory, though you need to make sure it can handle 5V or add voltage translation circuitry.&lt;/p&gt;
&lt;p&gt;Unfortunately, doing some back-of-the-envelope cost calculation, this all comes out as more expensive for small production runs.
It's cheaper and faster to just buy the chinese flash carts.&lt;/p&gt;
&lt;p&gt;But to be honest, I'm not sure how much interest people would have in buying physical copies of this game anyway, so I shelved this idea for now.
Of course you can always easily make your own if you'd like.&lt;/p&gt;
&lt;h2&gt;Asset Recreation&lt;/h2&gt;
&lt;p&gt;In order to get some inspiration from the original versions I extracted the relevant assets from the GTA:SA game files, like sprites, backgrounds, sound effects and music.
I then re-created these in scaled-down versions that fit the Game Boy hardware.&lt;/p&gt;
&lt;p&gt;Extrating the audio files can be done with &lt;a href=&quot;https://www.gtagarage.com/mods/show.php?id=5777&quot;&gt;Alci's SAAT GUI FrontEnd&lt;/a&gt;.
The graphics can be extracted from &lt;code&gt;.txd&lt;/code&gt; files with the &lt;a href=&quot;https://www.gtagarage.com/mods/show.php?id=8320&quot;&gt;TXD Workshop&lt;/a&gt;.
Both of these run fine in Wine.&lt;/p&gt;
&lt;p&gt;For the menu music I've re-created the San Andreas Theme, the game-over screen uses the victory fanfare from Final Fantasy VII.
Fortunately both of these have spread very widely and there's lots of MIDI interpretations available.
I used &lt;a href=&quot;https://musescore.org/en&quot;&gt;MuseScore&lt;/a&gt; to convert these MIDI files to sheet music and then transcribed the notes into my note lists in the source code.&lt;/p&gt;
&lt;p&gt;The Duality in-game theme song was more difficult.
It consists more of noises and some LFOs instead of clearly defined notes, so transcribing was pretty hard.
In the end I used a trial version of &lt;a href=&quot;https://www.lunaverus.com/&quot;&gt;AnthemScore&lt;/a&gt; in Wine to get an approximate idea of the notes, but the result is not great.&lt;/p&gt;
&lt;p&gt;Here are some direct comparisons.
First the San Andreas Theme, used as menu music.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.youtube.com/watch?v=7qfbi3HACV8&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=7qfbi3HACV8&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://img.youtube.com/vi/7qfbi3HACV8/0.jpg&quot; alt=&quot;San Andreas Theme&quot; style=&quot;width:300px;&quot; data-poster=&quot;https://img.youtube.com/vi/7qfbi3HACV8/0.jpg&quot;&gt;&lt;img src=&quot;https://www.xythobuz.de/img/video-play.png&quot; class=&quot;picthumb&quot; alt=&quot;Media play button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_music_menu.opus&quot; data-iframe=&quot;true&quot;&gt;
&lt;audio controls preload=&quot;none&quot; style=&quot;display:block;&quot;&gt;&lt;source src=&quot;https://www.xythobuz.de/img/duality_music_menu.opus&quot; type=&quot;audio/ogg&quot; &gt;Your browser does not support audio.&lt;/audio&gt;
&lt;p class=&quot;audio_text&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_music_menu.opus&quot;&gt;Download audio&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Next the score screen music, which comes from the FF7 victory fanfare.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.youtube.com/watch?v=rgUksX6eM0Y&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=rgUksX6eM0Y&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://img.youtube.com/vi/rgUksX6eM0Y/0.jpg&quot; alt=&quot;Final Fantasy VII Victory Fanfare&quot; style=&quot;width:300px;&quot; data-poster=&quot;https://img.youtube.com/vi/rgUksX6eM0Y/0.jpg&quot;&gt;&lt;img src=&quot;https://www.xythobuz.de/img/video-play.png&quot; class=&quot;picthumb&quot; alt=&quot;Media play button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_music_score.opus&quot; data-iframe=&quot;true&quot;&gt;
&lt;audio controls preload=&quot;none&quot; style=&quot;display:block;&quot;&gt;&lt;source src=&quot;https://www.xythobuz.de/img/duality_music_score.opus&quot; type=&quot;audio/ogg&quot; &gt;Your browser does not support audio.&lt;/audio&gt;
&lt;p class=&quot;audio_text&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_music_score.opus&quot;&gt;Download audio&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;And the in-game music, from the original Duality.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.youtube.com/watch?v=duiUhk5ZkaA&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=duiUhk5ZkaA&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://img.youtube.com/vi/duiUhk5ZkaA/0.jpg&quot; alt=&quot;Duality Theme&quot; style=&quot;width:300px;&quot; data-poster=&quot;https://img.youtube.com/vi/duiUhk5ZkaA/0.jpg&quot;&gt;&lt;img src=&quot;https://www.xythobuz.de/img/video-play.png&quot; class=&quot;picthumb&quot; alt=&quot;Media play button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/duality_music_game.opus&quot; data-iframe=&quot;true&quot;&gt;
&lt;audio controls preload=&quot;none&quot; style=&quot;display:block;&quot;&gt;&lt;source src=&quot;https://www.xythobuz.de/img/duality_music_game.opus&quot; type=&quot;audio/ogg&quot; &gt;Your browser does not support audio.&lt;/audio&gt;
&lt;p class=&quot;audio_text&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/duality_music_game.opus&quot;&gt;Download audio&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;I started working on this project at the end of May 2025, and basically finished it (to the state described here) at the end of July 2025, so in a span of about two months.
Of course this was only a side-project in my free time, outside my real job that pays the bills.
For this relatively short time I'm pretty happy with the results.&lt;/p&gt;
&lt;p&gt;As always there is still lots of room for improvements.&lt;/p&gt;
&lt;p&gt;The code needs to be optimized so it runs full-speed on the DMG, maybe by improving object handling and rendering, which has some 𝒪(𝓃²) behavior.&lt;/p&gt;
&lt;p&gt;I also started working on a multiplayer mode, but due to me only having a single GBC and DMG to test each, I didn't really progress much there.&lt;/p&gt;
&lt;p&gt;And the background map scrolling could be improved.
I played around with mirroring the map when overflowing on the sides, like some kind of endless scrolling, but haven't gotten far unfortunately.&lt;/p&gt;
&lt;p&gt;Working on a well-documented and well-designed retro game system like this was really lots of fun.
In the future I hope to also take a closer look at other retro platforms.&lt;/p&gt;
&lt;h2&gt;License&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://codeberg.org/xythobuz/Duality&quot;&gt;The Duality source code&lt;/a&gt; is licensed under the &lt;a href=&quot;https://www.gnu.org/licenses/gpl-3.0.en.html&quot;&gt;GNU General Public License v3&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Copyright (C) 2025 Thomas Buck &amp;lt;thomas@xythobuz.de&amp;gt;

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

See &amp;lt;http://www.gnu.org/licenses/&amp;gt;.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Duality uses the &lt;a href=&quot;https://gbdk.org&quot;&gt;GBDK-2020&lt;/a&gt; libraries and is based on their example code.
The files &lt;code&gt;sgb_border.c&lt;/code&gt; and &lt;code&gt;sgb_border.h&lt;/code&gt; are copied directly from their &lt;code&gt;sgb_border&lt;/code&gt; example.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;util/cvtsample.py&lt;/code&gt; script is based on a &lt;a href=&quot;https://github.com/gbdk-2020/gbdk-2020/blob/develop/gbdk-lib/examples/gb/wav_sample/utils/cvtsample.py&quot;&gt;GBDK example&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/DavidDiPaola/font_vincent&quot;&gt;8x8 font&lt;/a&gt; is public domain.&lt;/p&gt;
&lt;p&gt;The included cartridge label graphic in &lt;code&gt;artwork/cart_label.xcf&lt;/code&gt; is based on the &lt;a href=&quot;https://github.com/Dinierto/Cartridge-Label-Templates&quot;&gt;'Cartridge-Label-Templates' by Dinierto&lt;/a&gt; licensed as CC0.&lt;/p&gt;
&lt;p&gt;The included cartridge graphic in &lt;code&gt;artwork/cartridge.xcf&lt;/code&gt; is based on the &lt;a href=&quot;https://github.com/Duimon/Front-End-Assets&quot;&gt;'Front-End-Assets' by Duimon&lt;/a&gt;.&lt;/p&gt;</description>
    <pubDate>Tue, 19 Aug 2025 12:00:00 -0000</pubDate>
    <atom:updated>2025-09-01T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/duality.html</guid>
</item>

<item>
    <title>AutoBrightness</title>
    <link>https://www.xythobuz.de/auto_brightness.html</link>
    <description>&lt;p&gt;One of my two ~10 year old 23&quot; 1080p main displays died recently.
So I finally upgraded to two used 27&quot; 1440p displays.
These are now the first displays on my desktop that allow adjustments of the backlight from software.
So I tried to find out how to go about that.&lt;/p&gt;
&lt;p&gt;Turns out on laptops both the display backlight intensity and the ambient light sensors are controlled via ACPI, with proper kernel support already available (see eg. the &lt;a href=&quot;https://wiki.archlinux.org/title/Backlight&quot;&gt;Arch Wiki&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;But on desktops no standard for ambient light sensors seems to be established.
Instead of ACPI, the backlight of some desktop monitors can be controlled using &lt;a href=&quot;https://en.wikipedia.org/wiki/Display_Data_Channel#DDC/CI&quot;&gt;DDC/CI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are some projects, both &lt;a href=&quot;https://www.yoctopuce.com/EN/products/usb-environmental-sensors/yocto-light-v3&quot;&gt;hardware&lt;/a&gt; and &lt;a href=&quot;https://github.com/FedeDP/Clight&quot;&gt;software&lt;/a&gt;, available for this already.
But as usual I decided to make my own.&lt;/p&gt;
&lt;h2&gt;Prototype Hardware&lt;/h2&gt;
&lt;p&gt;Initially I tought I could just go the most simple route and use an LDR on the ADC of an MCU.
I already had a &lt;a href=&quot;https://www.az-delivery.de/en/products/digispark-board&quot;&gt;Digispark Rev. 3 clone&lt;/a&gt;, LDR, resistor and potentiometer on hand.&lt;/p&gt;
&lt;p&gt;But deep down I already knew this would not be good.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/autobrightness_ldr_1.jpg&quot; data-lg-size=&quot;680-680&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/autobrightness_ldr_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/autobrightness_ldr_1_small.jpg&quot; alt=&quot;Front view of AutoBrightness prototype&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/autobrightness_ldr_2.jpg&quot; data-lg-size=&quot;680-680&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/autobrightness_ldr_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/autobrightness_ldr_2_small.jpg&quot; alt=&quot;Back view of AutoBrightness prototype&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The range of LDRs is far too big to easily measure the human eye dynamic range with an ADC.
You can extend the range by switching different resistor values into your voltage divider using GPIOs, but I didn't want to go that far.
Instead I added a 1M potentiometer to manually adjust the measurement range.&lt;/p&gt;
&lt;p&gt;This gave me an opportunity to play around with integer low pass filters using bit shifts, as described &lt;a href=&quot;https://www.infineon.com/dgdl/Infineon-AN2099_PSoC_1_PSoC_3_PSoC_4_and_PSoC_5LP_Single_Pole_Infinite_Impulse_Response_%28IIR%29_Filters-ApplicationNotes-v11_00-EN.pdf?fileId=8ac78c8c7cdc391c017d072cde6e51bd&quot;&gt;here&lt;/a&gt; (which I got from &lt;a href=&quot;https://stackoverflow.com/a/38927630&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;So as suspected, the resulting values were not able to measure both a dark room at night and a sunny day.&lt;/p&gt;
&lt;h2&gt;Proper Hardware&lt;/h2&gt;
&lt;p&gt;To get usable values I had to use a &quot;real&quot; sensor.&lt;/p&gt;
&lt;p&gt;The final hardware is just a &lt;a href=&quot;https://www.az-delivery.de/en/products/digispark-board&quot;&gt;Digispark Rev. 3 clone&lt;/a&gt; with a &lt;a href=&quot;https://www.az-delivery.de/en/products/gy-302-bh1750-lichtsensor-lichtstaerke-modul-fuer-arduino-und-raspberry-pi&quot;&gt;GY-302 breakout board (for the BH1750 sensor)&lt;/a&gt; connected to it.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/autobrightness_pcb_1.jpg&quot; data-lg-size=&quot;680-680&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/autobrightness_pcb_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/autobrightness_pcb_1_small.jpg&quot; alt=&quot;Front view of AutoBrightness device&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/autobrightness_pcb_2.jpg&quot; data-lg-size=&quot;680-680&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/autobrightness_pcb_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/autobrightness_pcb_2_small.jpg&quot; alt=&quot;Back view of AutoBrightness device&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The &lt;a href=&quot;https://www.mouser.com/datasheet/2/348/bh1750fvi-e-186247.pdf&quot;&gt;BH1750&lt;/a&gt; is a nice small ambient light sensor and very easy to use.
This is literally the whole driver I wrote.&lt;/p&gt;
&lt;pre class=&quot;sh_c&quot;&gt;
void luxInit(void) {
    twiWrite(LUX_ADDR, OP_POWER_ON); // reset registers
    twiWrite(LUX_ADDR, OP_CONT_0_5X); // continuous measurement at 0.5lx resolution
}

uint16_t luxGet(void) {
    uint16_t val = twiRead(LUX_ADDR); // read measurement
    return val;
}
&lt;/pre&gt;

&lt;h2&gt;USB Communication&lt;/h2&gt;
&lt;p&gt;The Digispark has the USB D+ and D- signals directly connected to GPIOs of the AtTiny85.
So the USB protocol is bit-banged using the &lt;a href=&quot;https://github.com/obdev/v-usb&quot;&gt;V-USB library&lt;/a&gt;.
Because I did not use the Arduino Cores already available, I had to do some &lt;a href=&quot;https://codeberg.org/xythobuz/AutoBrightness/commit/d50da00006edd87d9363d83befc8eb5bc9274fb5&quot;&gt;fiddling&lt;/a&gt; to configure the library properly for this device.&lt;/p&gt;
&lt;p&gt;The code is based on the &lt;a href=&quot;https://github.com/obdev/v-usb/tree/master/examples/custom-class&quot;&gt;custom-class example&lt;/a&gt; from V-USB.
This abuses USB control transfers to transmit data.&lt;/p&gt;
&lt;p&gt;On the PC side I'm using &lt;a href=&quot;https://github.com/pyusb/pyusb&quot;&gt;PyUSB&lt;/a&gt; instead of going to libusb directly, as in the example.&lt;/p&gt;
&lt;pre class=&quot;sh_python&quot;&gt;
CUSTOM_RQ_GET = 2 # get ldr value

def is_target_device(dev):
    if dev.manufacturer == &quot;xythobuz.de&quot; and dev.product == &quot;AutoBrightness&quot;:
        return True
    return False

dev = usb.core.find(idVendor=0x16c0, idProduct=0x05dc, custom_match=is_target_device)
dev.set_configuration()

r = dev.ctrl_transfer(usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN, CUSTOM_RQ_GET, 0, 0, 2)
val = int.from_bytes(r, &quot;little&quot;)
&lt;/pre&gt;

&lt;p&gt;To run this without root permissions you need to add a udev rule (in eg. &lt;code&gt;/etc/udev/rules.d/49-autobrightness.rules&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SUBSYSTEMS==&quot;usb&quot;, ATTRS{idVendor}==&quot;16c0&quot;, ATTRS{idProduct}==&quot;05dc&quot;, ATTRS{manufacturer}==&quot;xythobuz.de&quot;, ATTRS{product}==&quot;AutoBrightness&quot;, MODE:=&quot;0666&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I'm using the shared V-USB vendor and product IDs, so I &lt;a href=&quot;https://github.com/obdev/v-usb/blob/master/usbdrv/USB-IDs-for-free.txt&quot;&gt;have to&lt;/a&gt; always do the matching using my manufacturer and product strings as well.&lt;/p&gt;
&lt;h2&gt;Prototype Client&lt;/h2&gt;
&lt;p&gt;With the hardware side out of the way the next step was adjusting the display brightness.
I made a &lt;a href=&quot;https://codeberg.org/xythobuz/AutoBrightness/commit/6fcab3b981bb5705028e1dd0f3b52e4eed609253&quot;&gt;short prototype&lt;/a&gt; using &lt;a href=&quot;https://www.ddcutil.com/&quot;&gt;ddcutil&lt;/a&gt; to set the values.&lt;/p&gt;
&lt;p&gt;To calculate the resulting values I made some measurements at midday (~500 lux) and night (~50 lux).
And I thought about my habits (the MSI display seems ~10% brighter than the HP).&lt;/p&gt;
&lt;pre class=&quot;sh_python&quot;&gt;
c_in = 0.6, -60.0, # in_a, in_b
calibration = {
    &quot;HPN:HP 27xq:CNK1072BJY&quot;: [
        1.0, 30.0, # out_a, out_b
    ],

    &quot;MSI:MSI G27CQ4:&quot;: [
        1.0, 20.0, # out_a, out_b
    ],
}

def cal(v, c):
    # out = out_b + out_a * in_a * max(0, in_b + in)
    return c[1] + c[0] * c_in[0] * max(0, c_in[1] + v)
&lt;/pre&gt;

&lt;p&gt;This simple formula gives surprisingly good results.
To avoid noticable noisy changes I do some simple low-pass filtering of the sensor values.&lt;/p&gt;
&lt;pre class=&quot;sh_python&quot;&gt;
filter_fact = 0.9

def filter_lux(old, new):
    return (old * filter_fact) + (new * (1.0 - filter_fact))
&lt;/pre&gt;

&lt;p&gt;All this just runs once per second.&lt;/p&gt;
&lt;p&gt;Unfortunately, using ddcutil to adjust the brightness causes a noticable stutter of the whole system each time the value is changed.
So this is not a good long-term solution.&lt;/p&gt;
&lt;p&gt;Telling ddcutil &lt;a href=&quot;https://codeberg.org/xythobuz/AutoBrightness/commit/b4888f009f3685c036866fa689759ffdbe9227cb&quot;&gt;to directly talk to the I2C bus&lt;/a&gt; helped a bit, but it still stutters slightly.&lt;/p&gt;
&lt;p&gt;To alleviate this a bit I'm now using a &lt;a href=&quot;https://develop.kde.org/docs/plasma/kwin/&quot;&gt;KWin script&lt;/a&gt; to check for a full-screen app so I can pause brightness updates.&lt;/p&gt;
&lt;pre class=&quot;sh_javascript&quot;&gt;
var win = workspace.activeWindow;
var name = win.caption;
var pid = win.pid;
var state = (win.bufferGeometry == win.output.geometry);
var obj = { name: name, pid: pid, fullscreen: state };
print(JSON.stringify(obj));
&lt;/pre&gt;

&lt;p&gt;As usual I'm also sending all these values to my local InfluxDB.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/autobrightness_influx.png&quot; data-lg-size=&quot;1600-710&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/autobrightness_influx.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/autobrightness_influx_small.png&quot; alt=&quot;Input and Output data in Grafana&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;Proper Client&lt;/h2&gt;
&lt;p&gt;My initial idea for the client was to use the ambient light sensor to also &quot;calibrate&quot; the two displays to each other.
To do this, a white square could be shown on both screens.
Then the sensor can be placed in front of each display to measure their brightness &quot;ramps&quot;.
This could then give the &lt;code&gt;calibration&lt;/code&gt; dictionary shown above.&lt;/p&gt;
&lt;p&gt;To determine the &lt;code&gt;c_in&lt;/code&gt; values the room brightness has to be measured at day and night.&lt;/p&gt;
&lt;p&gt;This should automate the process I've done manually to determine the calibration values.&lt;/p&gt;
&lt;p&gt;But as you may have noticed, I'm more the prototype kind of guy and don't really do finished products on here.
So...&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To Do&lt;/strong&gt; 😅&lt;/p&gt;
&lt;h2&gt;Update after a year (2025-07)&lt;/h2&gt;
&lt;p&gt;I have now been running this little gadget for nearly a year.
It has been working great, and the calibration features above really seem unnecessary.&lt;/p&gt;
&lt;p&gt;To improve usability slightly I've now also made a system-tray application.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/autobrightness_tray.png&quot; data-lg-size=&quot;418-177&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/autobrightness_tray.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/autobrightness_tray_small.png&quot; alt=&quot;System tray application&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a class=&quot;anchor al2&quot; id=&quot;license&quot; href=&quot;#license&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;License&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://codeberg.org/xythobuz/AutoBrightness&quot;&gt;AutoBrightness project&lt;/a&gt; is licensed under the &lt;a href=&quot;https://www.gnu.org/licenses/gpl-3.0.en.html&quot;&gt;GNU General Public License&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

See &amp;lt;http://www.gnu.org/licenses/&amp;gt;.
&lt;/code&gt;&lt;/pre&gt;</description>
    <pubDate>Sat, 07 Sep 2024 12:00:00 -0000</pubDate>
    <atom:updated>2025-07-27T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/auto_brightness.html</guid>
</item>

<item>
    <title>Game Boy Color modding and experiments</title>
    <link>https://www.xythobuz.de/2025_03_16_gameboy.html</link>
    <description>&lt;p&gt;I recently pulled out my old childhood Game Boy Color from a drawer to play around with it.
The idea is to use &lt;a href=&quot;https://www.littlesounddj.com/&quot;&gt;LSDJ&lt;/a&gt; to accompany &lt;a href=&quot;lars_v2.html&quot;&gt;LARS&lt;/a&gt;.
Unfortunately I kinda forgot &lt;em&gt;how bad&lt;/em&gt; the GBC screen is, without any backlight.&lt;/p&gt;
&lt;p&gt;So I looked around AliExpress a bit and found the &lt;a href=&quot;https://www.aliexpress.com/store/1102204288&quot;&gt;store&lt;/a&gt; of &lt;a href=&quot;https://hispeedido.com/home&quot;&gt;HISPEEDIDO&lt;/a&gt;.
They offer many nice modding kits for old consoles, so I got the &lt;a href=&quot;https://www.aliexpress.com/item/1005007009553275.html&quot;&gt;OLED screen and USB-C battery replacements&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_mod_9.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_mod_9.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_mod_9_small.jpg&quot; alt=&quot;Front of modded GBC&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_mod_8.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_mod_8.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_mod_8_small.jpg&quot; alt=&quot;Back of modded GBC with USB-C charging&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The installation is very easy, you just need to solder a single wire according to their manual and it all worked out fine.&lt;/p&gt;
&lt;p&gt;I also found my old Game Boy printer, a small thermal printer that can be used with some games and, most importantly, the Game Boy camera.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_mod_10.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_mod_10.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_mod_10_small.jpg&quot; alt=&quot;My old GB Printer&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I didn't have my old Game Boy camera, as I took it apart and used the sensor many years ago for my &lt;a href=&quot;xyrobot.html&quot;&gt;robot&lt;/a&gt;.
So I got a used one on eBay.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_cam_1.png&quot; data-lg-size=&quot;160-144&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_cam_1.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_cam_1_small.png&quot; alt=&quot;Cat pic of Ares (1)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_cam_2.png&quot; data-lg-size=&quot;160-144&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_cam_2.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_cam_2_small.png&quot; alt=&quot;Cat pic of Ares (2)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_cam_6.png&quot; data-lg-size=&quot;160-144&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_cam_6.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_cam_6_small.png&quot; alt=&quot;Cat pic of Ares (3)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Here are some photos I took for testing.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_cam_7.png&quot; data-lg-size=&quot;160-144&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_cam_7.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_cam_7_small.png&quot; alt=&quot;Cat pic of Ares (4)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_cam_3.png&quot; data-lg-size=&quot;160-144&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_cam_3.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_cam_3_small.png&quot; alt=&quot;Ares and Aphrodite together&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_cam_12.png&quot; data-lg-size=&quot;160-144&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_cam_12.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_cam_12_small.png&quot; alt=&quot;GTA:SA screenshot&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;As you can tell, Ares has better contrast compared to Aphrodite 😄&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_cam_4.png&quot; data-lg-size=&quot;160-144&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_cam_4.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_cam_4_small.png&quot; alt=&quot;Cat pic of Aphrodite (1)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_cam_5.png&quot; data-lg-size=&quot;160-144&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_cam_5.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_cam_5_small.png&quot; alt=&quot;Cat pic of Aphrodite (2)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_cam_8.png&quot; data-lg-size=&quot;160-144&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_cam_8.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_cam_8_small.png&quot; alt=&quot;Cat pic of Aphrodite (3)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;And you really need to take the lighting situation into consideration.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_cam_9.png&quot; data-lg-size=&quot;160-144&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_cam_9.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_cam_9_small.png&quot; alt=&quot;Cat pic of Aphrodite (4)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_cam_10.png&quot; data-lg-size=&quot;160-144&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_cam_10.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_cam_10_small.png&quot; alt=&quot;Cat pic of Aphrodite (5)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_cam_11.png&quot; data-lg-size=&quot;160-144&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_cam_11.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_cam_11_small.png&quot; alt=&quot;Cat pic of Aphrodite (6)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;To get the pictures off the cartridge I built an ESP8266-based WiFi printer emulator.
I ordered these &lt;a href=&quot;https://github.com/cristofercruz/gbp-esp-shield-pcb&quot;&gt;PCBs&lt;/a&gt; and soldered them to a &lt;a href=&quot;https://www.aliexpress.com/item/4000429611680.html&quot;&gt;LILYGO TTGO D1 mini&lt;/a&gt; as mentioned in the README of the PCB repo.
The OLED screen was still left over from my &lt;a href=&quot;2024_09_16_istick_pico_oled.html&quot;&gt;E-Cig repair&lt;/a&gt;.
The software running on there is the &lt;a href=&quot;https://github.com/HerrZatacke/wifi-gbp-emulator&quot;&gt;wifi-gbp-emulator&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_mod_1.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_mod_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_mod_1_small.jpg&quot; alt=&quot;Using the WiFi GB Printer emulator&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_mod_7.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_mod_7.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_mod_7_small.jpg&quot; alt=&quot;Front of WiFi GB Printer emulator&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_mod_6.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_mod_6.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_mod_6_small.jpg&quot; alt=&quot;Back of WiFi GB Printer emulator&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;To get back to the original topic of creating sounds though, there are multiple options.
The most popular one, as mentioned, is &lt;a href=&quot;https://www.littlesounddj.com/&quot;&gt;LSDJ&lt;/a&gt;.
This is basically a tracker or sequencer for the sound hardware in the Game Boy.
Using external hardware, it can either sync on incoming MIDI signals or output MIDI notes for other devices.
For the other way round, playing the Game Boy using incoming MIDI notes, the &lt;a href=&quot;https://github.com/trash80/mGB&quot;&gt;mGB&lt;/a&gt; synthesizer can be used.&lt;/p&gt;
&lt;p&gt;The hardware to interface with these is called &lt;a href=&quot;https://github.com/trash80/arduinoboy&quot;&gt;Arduinoboy&lt;/a&gt;, but I used the &lt;a href=&quot;https://github.com/thikness/Arduinoboy-ProMicroGal&quot;&gt;ProMicroGal&lt;/a&gt; fork with an ATmega32u4 that can interface to MIDI devices via USB, so I left out the hardware MIDI interface.
You can get (backwards-compatible) GBA link cable sockets from &lt;a href=&quot;https://www.aliexpress.com/item/1005006358075502.html&quot;&gt;AliExpress&lt;/a&gt;.
To easily be able to solder them, I ordered the GB-BRK-LINK-B PCBs from the &lt;a href=&quot;https://github.com/Gekkio/gb-hardware&quot;&gt;gb-hardware&lt;/a&gt; repo.
I also was lazy and used &lt;a href=&quot;https://github.com/xythobuz/Arduinoboy-ProMicroGal/commit/aa66eabc54f39b34d05347ec0711d3e2e204d3c8&quot;&gt;pio to build the firmware&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_mod_5.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_mod_5.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_mod_5_small.jpg&quot; alt=&quot;Front of ArduinoBoy&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_mod_4.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_mod_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_mod_4_small.jpg&quot; alt=&quot;Back of ArduinoBoy&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;To run LSDJ or mGB you can either use a &lt;a href=&quot;https://www.aliexpress.com/item/1005006294370333.html&quot;&gt;EEPROM cartridge&lt;/a&gt; and write the ROM to it with a &lt;a href=&quot;https://www.aliexpress.com/item/1005007652321830.html&quot;&gt;USB programmer&lt;/a&gt;, or get an &lt;a href=&quot;https://krikzz.com/our-products/cartridges/edgbx7.html&quot;&gt;Everdrive&lt;/a&gt;.
Unfortunately they are really expensive, so I got a &lt;a href=&quot;https://www.aliexpress.com/item/1005007560023741.html&quot;&gt;chinese clone&lt;/a&gt; (that is unfortunately no longer available, but you may be able to &lt;a href=&quot;https://www.aliexpress.com/w/wholesale-game-boy-cartridge-micro-sd.html&quot;&gt;find others&lt;/a&gt;).
It works fine with all games I tested and also both LSDJ and mGB.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_mod_3.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_mod_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_mod_3_small.jpg&quot; alt=&quot;Chinese Everdrive clone&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/gb_mod_2.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/gb_mod_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/gb_mod_2_small.jpg&quot; alt=&quot;Screenshot of Everdrive-clone menu&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;And it's great fun, all &lt;a href=&quot;https://archive.org/details/Cyles_Gameboy_roms&quot;&gt;GB&lt;/a&gt; and &lt;a href=&quot;https://archive.org/details/CylesGameBoyColorRomPack&quot;&gt;GBC&lt;/a&gt; games ever released come in at less than 2GB combined 🕵️&lt;/p&gt;</description>
    <pubDate>Sun, 16 Mar 2025 12:00:00 -0000</pubDate>
    <atom:updated>2025-03-16T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/2025_03_16_gameboy.html</guid>
</item>

<item>
    <title>Pico BLE Vape Remote</title>
    <link>https://www.xythobuz.de/ble_remote.html</link>
    <description>&lt;p&gt;Like many modern devices, some of the medical vaporizer devices from S&amp;amp;B (the &lt;a href=&quot;https://www.storz-bickel.com/de/crafty-plus-c&quot;&gt;Crafty&lt;/a&gt;, &lt;a href=&quot;https://www.storz-bickel.com/de/venty&quot;&gt;Venty&lt;/a&gt; and the &lt;a href=&quot;https://www.storz-bickel.com/de/volcanohybrid&quot;&gt;Volcano Hybrid&lt;/a&gt;) offer Bluetooth connectivity and &lt;a href=&quot;https://app.storz-bickel.com/&quot;&gt;an app&lt;/a&gt; to control them.&lt;/p&gt;
&lt;p&gt;Unfortunately the official mobile apps have / had some problems.
The iOS version is no longer available after Apple decided to remove &quot;drug related&quot; Apps.
So understandably the company no longer developed either their iOS or Android apps, instead releasing a web app that uses the Chrome Bluetooth APIs.&lt;/p&gt;
&lt;p&gt;Yet all of these variants are problematic in different ways.
The Android app offers customizable workflows for the Volcano Hybrid.
But the Bluetooth implementation is very wonky, sometimes simply missing steps in the workflow.
And because there's no error checking, the workflow simply continues in a broken state.
The webapp doesn't have this problem, but instead the workflows are no longer customizable, instead just offering a small number of hard-coded pre-defined workflows.&lt;/p&gt;
&lt;p&gt;This is not satisfying, of course, and also presented itself as a great opportunity to play around with BLE.&lt;/p&gt;
&lt;p&gt;Fortunately the official web app is made with non-minimized an unobfuscated JavaScript, so the BLE protocol for the different devices can easily be reverse engineered.
I wrote a small &lt;a href=&quot;https://codeberg.org/xythobuz/Volcano-Remote/src/branch/master/web-app/fetch.sh&quot;&gt;script&lt;/a&gt; that helps with downloading and beautifying the official sources.&lt;/p&gt;
&lt;p&gt;As a first attempt I implemented the Volcano workflow functionality in Python on the PC.
I had some problems, so I made two implementations, with &lt;a href=&quot;https://bleak.readthedocs.io/en/latest/&quot;&gt;bleak&lt;/a&gt; and &lt;a href=&quot;https://simpleble.readthedocs.io/en/latest/simplepyble/usage.html&quot;&gt;SimplePyBLE&lt;/a&gt;.
Turns out my difficulties came from having multiple bluetooth adapters on my Linux machine.
Now the scripts both work fine for me.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/volcano_remote_pc_2.jpg&quot; data-lg-size=&quot;806-540&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/volcano_remote_pc_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/volcano_remote_pc_2_small.jpg&quot; alt=&quot;Volcano Remote script in action (old version)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/volcano_remote_pc_1.png&quot; data-lg-size=&quot;681-319&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/volcano_remote_pc_1.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/volcano_remote_pc_1_small.png&quot; alt=&quot;Volcano Remote script in action (current version)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Then I implemented the same thing on a &lt;a href=&quot;https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html#raspberry-pi-pico-w-and-pico-wh&quot;&gt;Raspberry Pi Pico W&lt;/a&gt;, with a &lt;a href=&quot;https://www.waveshare.com/wiki/Pico-LCD-1.3&quot;&gt;Waveshare Pico LCD 1.3&lt;/a&gt; display.
I first did this in MicroPython but ran into issues as well.
It works fine but I couldn't include all the functionality I wanted.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/volcano_remote_micropython.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/volcano_remote_micropython.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/volcano_remote_micropython_small.jpg&quot; alt=&quot;Volcano Remote MicroPython prototype&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;So I made a fourth iteration, this time in C with the plain Pico SDK.
It can interact with the Volcano Hybrid, the Crafty and the Venty.
Everything can be controlled using on-screen menus on the device itself.
It even has a WiFi capable OTA bootloader to support wireless firmware upgrades.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/volcano_remote_c_dev.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/volcano_remote_c_dev.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/volcano_remote_c_dev_small.jpg&quot; alt=&quot;Volcano Remote C version in development&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/volcano_remote_bootloader.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/volcano_remote_bootloader.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/volcano_remote_bootloader_small.jpg&quot; alt=&quot;Volcano Remote WiFi Bootloader&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;To contain everything I designed a snug little 3D-printed case in OpenSCAD.
It contains the Pico W, a &lt;a href=&quot;https://shop.pimoroni.com/products/pico-lipo-shim?variant=32369543086163&quot;&gt;Pico LiPo Shim&lt;/a&gt; and a &lt;a href=&quot;https://www.ebay.de/itm/255510046348?var=555462939784&quot;&gt;&quot;80mAh / 20 x 11 x 5mm / 501220&quot; LiPo battery&lt;/a&gt;.
The Pi is mounted with four screws and the battery is held onto it with a drop of hot glue.
Then the LCD board just plugs on top to close it.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/volcano_remote_case_1.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/volcano_remote_case_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/volcano_remote_case_1_small.jpg&quot; alt=&quot;Volcano Remote case assembly&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/volcano_remote_case_2.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/volcano_remote_case_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/volcano_remote_case_2_small.jpg&quot; alt=&quot;Volcano Remote case opened&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/volcano_remote_case_3.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/volcano_remote_case_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/volcano_remote_case_3_small.jpg&quot; alt=&quot;Volcano Remote case power button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;This is what the final result looks like.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/volcano_remote_case_4.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/volcano_remote_case_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/volcano_remote_case_4_small.jpg&quot; alt=&quot;Volcano Remote (final)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I'm quite happy with it, especially the software part.
It has many things I expect from nice modern embedded projects, and I've used a bunch of libraries to achieve these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/raspberrypi/pico-sdk&quot;&gt;Pico SDK&lt;/a&gt; for working with the Pico&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/hathach/tinyusb&quot;&gt;TinyUSB&lt;/a&gt; for the USB device implementation&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/bluekitchen/btstack&quot;&gt;BTstack&lt;/a&gt; for the Bluetooth connection&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/abbrev/fatfs&quot;&gt;FatFS&lt;/a&gt; for the USB filesystem&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mcufont/mcufont&quot;&gt;MCUFont&lt;/a&gt; for rendering text&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/hepingood/st7789&quot;&gt;st7789&lt;/a&gt; for interacting with the LCD&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/usedbytes/picowota&quot;&gt;picowota&lt;/a&gt; bootloader (&lt;a href=&quot;https://github.com/xythobuz/picowota&quot;&gt;modified&lt;/a&gt; to work with the Flash config storage and LCD)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As usual you can find everything on &lt;a href=&quot;https://codeberg.org/xythobuz/Volcano-Remote&quot;&gt;Codeberg&lt;/a&gt; and on &lt;a href=&quot;https://github.com/xythobuz/Volcano-Remote&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;</description>
    <pubDate>Sat, 07 Dec 2024 12:00:00 -0000</pubDate>
    <atom:updated>2024-12-07T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/ble_remote.html</guid>
</item>

<item>
    <title>iStick Pico 25 OLED Replacement</title>
    <link>https://www.xythobuz.de/2024_09_16_istick_pico_oled.html</link>
    <description>&lt;p&gt;Years ago I bought a cheap chinese E-Cigarette 510 box mod, the Eleaf iStick Pico 25.
It's running the ArcticFox custom firmware which sadly seems to be &lt;a href=&quot;https://nfeteam.org/&quot;&gt;no longer available&lt;/a&gt;.
I never really used it for anything, but for &lt;a href=&quot;https://vapeengineering.de/&quot;&gt;reasons&lt;/a&gt; I want to get it going again.
Unfortunately the display is so dim that it can no longer be used.
So I wanted to repair the device.&lt;/p&gt;
&lt;p&gt;I found &lt;a href=&quot;https://old.reddit.com/r/Vaping/comments/4t5w0k/the_screen_of_my_eleaf_pico_is_fading_every_day/jz009z6/&quot;&gt;this comment on Reddit&lt;/a&gt; explaining that it's just a standard SSD1306 128x32 0.91&quot; OLED display.
&lt;a href=&quot;https://old.reddit.com/r/Vaping/comments/18d9kkq/brand_new_istick_pico_75w_with_a_dim_screen/krzd9ix/&quot;&gt;This guy&lt;/a&gt; says initially Eleaf used ribbon cable connectors, making the replacement easy, and one shouldn't try without them.
But actually soldering on a replacement is very easy.&lt;/p&gt;
&lt;p&gt;First open the device and carefully remove the plastic display mount and desolder the ribbon cable.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/istick_pico_oled_replacement_1.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/istick_pico_oled_replacement_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/istick_pico_oled_replacement_1_small.jpg&quot; alt=&quot;Old display, top view&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/istick_pico_oled_replacement_2.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/istick_pico_oled_replacement_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/istick_pico_oled_replacement_2_small.jpg&quot; alt=&quot;Display holder removed&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The most difficult part for me was getting the &lt;a href=&quot;https://www.az-delivery.de/en/products/0-91-zoll-i2c-oled-display&quot;&gt;replacement OLED&lt;/a&gt; removed from the breakout board.
I used some lighter fuel to dissolve the glue and carefully levered and cut it off, trying not to break the glass or destroy the ribbon PCB.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/istick_pico_oled_replacement_3.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/istick_pico_oled_replacement_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/istick_pico_oled_replacement_3_small.jpg&quot; alt=&quot;Replacement display&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Then just solder it on like any other SMD part.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/istick_pico_oled_replacement_4.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/istick_pico_oled_replacement_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/istick_pico_oled_replacement_4_small.jpg&quot; alt=&quot;Repair done&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Works great!&lt;/p&gt;</description>
    <pubDate>Mon, 16 Sep 2024 12:00:00 -0000</pubDate>
    <atom:updated>2024-09-16T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/2024_09_16_istick_pico_oled.html</guid>
</item>

<item>
    <title>LARS v2</title>
    <link>https://www.xythobuz.de/lars_v2.html</link>
    <description>&lt;p&gt;So you may have read about our project &lt;a href=&quot;lars.html&quot;&gt;LARS&lt;/a&gt;.
As mentioned I didn't have any hardware in the end to finish firmware development.
So I slightly updated the circuitry, layed out a new board, had it manufactured in China and assembled at home.&lt;/p&gt;
&lt;p&gt;This is LARS v2, the second iteration of the same idea.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_1.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_1_small.jpg&quot; alt=&quot;LARS v2&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-video='{&quot;source&quot;: [{&quot;src&quot;:&quot;img/lars2_midi.mp4&quot;, &quot;type&quot;:&quot;video/mp4&quot;}], &quot;attributes&quot;: {&quot;preload&quot;: false, &quot;playsinline&quot;: true, &quot;controls&quot;: true}}' data-lg-size=&quot;720-910&quot; data-poster=&quot;img/lars2_midi_poster.png&quot; data-sub-html=&quot;LARS v2 playing MIDI example file&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars2_midi.mp4&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars2_midi_thumb.png&quot; alt=&quot;LARS v2 playing MIDI example file&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;It's basically the same device, just with five additional switches.
This allows for some more freedom in the user interface design.
Now the loop mode can mute individual tracks.
There's also USB MIDI support.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars2_parts.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars2_parts.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars2_parts_small.jpg&quot; alt=&quot;LARS v2 PCBs and parts&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_loop_controls.jpg&quot; data-lg-size=&quot;1600-1199&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_loop_controls.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_loop_controls_small.jpg&quot; alt=&quot;LARS Loop Station controls&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;As &lt;a href=&quot;2024_05_05_auto_project_docs.html&quot;&gt;usual&lt;/a&gt; I've added auto-generated &lt;a href=&quot;https://xythobuz.github.io/lars/&quot;&gt;documentation&lt;/a&gt;, including &lt;a href=&quot;https://xythobuz.github.io/lars/pcb2_pcb.html&quot;&gt;interactive 3D renders of the PCB&lt;/a&gt; and some &lt;a href=&quot;https://xythobuz.github.io/lars/usage.html&quot;&gt;usage hints&lt;/a&gt;.
The &lt;a href=&quot;https://xythobuz.github.io/lars/plot/fab_pcb2.zip&quot;&gt;gerber files&lt;/a&gt; and &lt;a href=&quot;https://xythobuz.github.io/lars/pcb2.html&quot;&gt;BOM&lt;/a&gt; to order your own are also available.&lt;/p&gt;
&lt;p&gt;Unfortunately I'm kind of in the same situation now as with LARS v1.
I've gone much further with the firmware, it's now mostly usable.
But I've also given the hardware away again, so I can't finish it.&lt;/p&gt;
&lt;p&gt;&lt;s&gt;I should assemble another one soon...&lt;/s&gt;&lt;/p&gt;
&lt;h2&gt;Assembly Guide&lt;/h2&gt;
&lt;p&gt;You want to build your own LARS v2 and have all the parts ready to go?
Then let's build it!&lt;/p&gt;
&lt;p&gt;In general you want to go from SMD / SMT (surface mount) parts to THT (through-hole), going from large to small for the SMD parts, and small to large for the THT parts.&lt;/p&gt;
&lt;p&gt;First place the SMD Raspberry Pi Pico module so all pads are properly aligned.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_1.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_1_small.jpg&quot; alt=&quot;Empty PCB&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_2.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_2_small.jpg&quot; alt=&quot;Pico placed&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Now first solder on one corner, then go through both rows.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_3.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_3_small.jpg&quot; alt=&quot;First Pico corner soldered&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_4.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_4_small.jpg&quot; alt=&quot;One Pico row soldered&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_5.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_5.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_5_small.jpg&quot; alt=&quot;Pico soldering finished&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The battery holder snaps in with two plastic clips.
Soldering the pads can be a bit tricky.
Even though there is a hole for the solder to flow through to the pad, you can not see it well.
Try to apply a bit of pressure to get the pads to connect reliably.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_6.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_6.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_6_small.jpg&quot; alt=&quot;Battery holder snapped in&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_7.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_7.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_7_small.jpg&quot; alt=&quot;Battery holder soldered on&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Now place all the pushbuttons and solder them in.
Their legs have a snap fit so they will hold on properly.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_8.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_8.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_8_small.jpg&quot; alt=&quot;Pushbuttons placed&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_9.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_9.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_9_small.jpg&quot; alt=&quot;Buttons soldered&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Next the resistors.
I'm bending their legs slightly so they stay in place when turning the board over.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_10.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_10.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_10_small.jpg&quot; alt=&quot;LED resistors placed&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_11.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_11.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_11_small.jpg&quot; alt=&quot;LED resistor legs bent&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_12.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_12.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_12_small.jpg&quot; alt=&quot;LED resistors soldered in&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_13.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_13.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_13_small.jpg&quot; alt=&quot;Voltage divider resistors done&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The diodes and LEDs can be placed next, with the same leg-bending trick as before.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_14.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_14.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_14_small.jpg&quot; alt=&quot;Diodes in place&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_15.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_15.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_15_small.jpg&quot; alt=&quot;LEDs placed&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The encoder snaps in as well.
For the switch and OLED, make sure to apply a bit of pressure when turning the board over, so they are firmly in place.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_16.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_16.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_16_small.jpg&quot; alt=&quot;Switch, encoder and OLED placed&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_17.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_17.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_17_small.jpg&quot; alt=&quot;Switch, encoder and OLED soldered&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The pins of the MOSFET module screw-terminals fit into the board holes and can be soldered on directly.
For the small logic pins use a bit of the cut-off legs from the LEDs or resistors.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_18.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_18.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_18_small.jpg&quot; alt=&quot;MOSFETs in place, top&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_19.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_19.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_19_small.jpg&quot; alt=&quot;MOSFETS in place, bottom&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_20.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_20.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_20_small.jpg&quot; alt=&quot;MOSFET terminals soldered&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_21.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_21.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_21_small.jpg&quot; alt=&quot;MOSFET logic pins soldered&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;For the regulator I'm using standard pins like they probably came with your Pico.
But you could also use some more of the cut-off legs from the previous steps.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_22.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_22.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_22_small.jpg&quot; alt=&quot;Regulator pins prepared&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_23.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_23.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_23_small.jpg&quot; alt=&quot;Regulator in place&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_24.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_24.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_24_small.jpg&quot; alt=&quot;Regulators soldered, top&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_25.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_25.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_25_small.jpg&quot; alt=&quot;Regulators soldered, bottom&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The charger pad does not quite fit the connections of the module.
So I'm using two pins per connection in a 90 degree angle.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_26.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_26.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_26_small.jpg&quot; alt=&quot;Charger pins prepared&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_27.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_27.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_27_small.jpg&quot; alt=&quot;Charger module prepared&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;And that's it.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_28.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_v2_assembly_28.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_v2_assembly_28_small.jpg&quot; alt=&quot;Finished board&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Good luck and have fun!&lt;/p&gt;</description>
    <pubDate>Sat, 07 Sep 2024 12:00:00 -0000</pubDate>
    <atom:updated>2024-09-16T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/lars_v2.html</guid>
</item>

<item>
    <title>xyBeast</title>
    <link>https://www.xythobuz.de/aurora100.html</link>
    <description>&lt;p&gt;&lt;span class=&quot;listdesc&quot;&gt;&lt;a href=&quot;quadcopters.html&quot;&gt;...back to Flying RC Vehicles overview&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Not being totally happy with the power available with &lt;a href=&quot;aurora90.html&quot;&gt;my 2S xyAurora90&lt;/a&gt; I decided to build a 3S Aurora100 in July 2017.
The total weight with a 3S 450mAh battery is around 131g.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/xyaurora100_1.jpg&quot; data-lg-size=&quot;1600-1237&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/xyaurora100_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/xyaurora100_1_small.jpg&quot; alt=&quot;Original Setup with AIO cam, Front Top view&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/aurora100_weight.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/aurora100_weight.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/aurora100_weight_small.jpg&quot; alt=&quot;RTF with battery on a scale&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Here are some recordings of the FPV footage.
Be aware that these videos are recorded from the received signal on the ground.
Not only can you see lots of interference and other HF problems, the loss of quality from the video recorder and the YouTube encoding is also very noticeable.
In real-life, the picture doesn't look &lt;em&gt;that&lt;/em&gt; bad.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.youtube.com/watch?v=MnF7B3rD5VM&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=MnF7B3rD5VM&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/xyaurora100_crash_thumb.jpg&quot; alt=&quot;Aurora100 flight with crash&quot;&gt;&lt;img src=&quot;https://www.xythobuz.de/img/video-play.png&quot; class=&quot;picthumb&quot; alt=&quot;Media play button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.youtube.com/watch?v=798ncBkBHos&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=798ncBkBHos&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/xyaurora100_owl_thumb.jpg&quot; alt=&quot;Micro Quadcopter attacked by Owl&quot;&gt;&lt;img src=&quot;https://www.xythobuz.de/img/video-play.png&quot; class=&quot;picthumb&quot; alt=&quot;Media play button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.youtube.com/watch?v=KEHhSCol_AY&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=KEHhSCol_AY&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/xybeast_prop_break_thumb.jpg&quot; alt=&quot;Brushless 100mm 3S Quadcopter Test, 5-blade prop breaking in-flight&quot;&gt;&lt;img src=&quot;https://www.xythobuz.de/img/video-play.png&quot; class=&quot;picthumb&quot; alt=&quot;Media play button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Initially I tried the 1104 7000KV DYS motors, but they had problems with vibrations I couldn't fix with any soft-mounting solutions I've tried, and after tuning the filters with a blackbox.
I then swapped to the Eachine motors listed below, with them everything works perfectly with stock PID settings.&lt;/p&gt;
&lt;p&gt;Also, at first I was using the &lt;a href=&quot;https://www.banggood.com/Eachine-AIO-FPV-5_8G-25mW-48CH-VTX-600TVL-CMOS-1-or-4-inch-Camera-For-Aurora-90-100-RC-Drone-FPV-Racing-p-1122902.html?akmClientCountry=DE&amp;amp;p=3F201911077692015010&amp;amp;cur_warehouse=CN&quot;&gt;Eachine 25mW AIO Cam&lt;/a&gt; with this build.
However, because I flew often simultaneously with friends, we decided to all switch to RunCams and TBS transmitters, as listed below.&lt;/p&gt;
&lt;h2&gt;Parts List&lt;/h2&gt;
&lt;p&gt;These are the prices as recorded by Rotorbuilds.
To be honest, I have no clue if they are what I paid back when I built this copter.&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Frame
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.banggood.com/Eachine-Aurora-100-100MM-Mini-Brushless-FPV-Multirotor-Racing-Frame-14_5g-Carbon-Fiber-p-1133462.html?p=3F201911077692015010&quot;&gt;Eachine Aurora 100 100MM Mini Brushless FPV Multirotor Racing Frame 14.5g Carbon Fiber&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
11.99$
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Spare
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.banggood.com/Eachine-Aurora-100-Mini-Brushless-FPV-Racer-Spare-Part-2mm-2_5mm-Bottom-Plate-3K-Carbon-Fiber-p-1144532.html?p=3F201911077692015010&amp;cur_warehouse=CN&amp;ID=529763&quot;&gt;Eachine Aurora 100 Mini Brushless FPV Racer Spare Part 2mm 2.5mm Bottom Plate 3K Carbon Fiber&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
5.39$
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
FC
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.banggood.com/Eachine-Minicube-20x20mm-F4-OSD-Compatible-Frsky-Flysky-DSM-RX-Blheli_S-10A-For-Aurora-68-90-100-p-1165366.html?p=3F201911077692015010&quot;&gt;Eachine Minicube 20x20mm F4 OSD Compatible Frsky Flysky DSM RX Blheli_S 10A For Aurora 68 90 100&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
60.99$
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Motors
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.banggood.com/Eachine-1104-6500KV-1-3S-Brushless-Motor-For-Eachine-Aurora-90-100-Mini-FPV-Racer-p-1138072.html?p=3F201911077692015010&amp;cur_warehouse=CN&quot;&gt;4 x Eachine Upgrade Motor 1104 6500KV Brushless Motor 1-3S For Eachine Aurora 90 100 RC Drone FPV Racing&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
35.56$
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Props
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.banggood.com/10-Pairs-Racerstar-1935-50mm-5-Blade-Racing-Propeller-1_5mm-Mounting-Hole-For-Micro-FPV-Frame-p-1129109.html?p=3F201911077692015010&quot;&gt;10 Pairs Racerstar 1935 50mm 5 Blade Racing Propeller 1.5mm Mounting Hole For Micro FPV Frame&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
8.29$
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Cam
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://shop.runcam.com/runcam-micro-sparrow/&quot;&gt;RunCam Micro Sparrow 2.1mm&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
31.97$
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
VTx
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.team-blacksheep.com/products/prod:unify_pro&quot;&gt;TBS Unify Pro V3 5V&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
54.74$
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Bats
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.gensace.de/tattu-450mah-11-1v-75c-3s1p-lipo-battery-pack-long-size-for-h-frame.html&quot;&gt;Tattu 450mAh 11.1V 75C 3S1P Lipo Battery Pack- Long Size for H Frame&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
15.89$
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Misc
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.banggood.com/4-PCS-Eachine-Propeller-Guard-For-Aurora-100-Mini-FPV-Racing-RC-Drone-1102-1103-1104-1105-Brushless-Motor-p-1143685.html?akmClientCountry=DE&amp;p=3F201911077692015010&amp;cur_warehouse=CN&amp;ID=224&quot;&gt;4 PCS Eachine Propeller Guard For Aurora 100 Mini FPV Racing RC Drone 1102 1103 1104 1105 Brushless Motor&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
2.49$
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;

&lt;/td&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Sum
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
227.31$
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;This write-up was first published on &lt;a href=&quot;https://rotorbuilds.com/build/5577&quot;&gt;Rotorbuilds&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can find my Betaflight configuration dumps &lt;a href=&quot;https://codeberg.org/xythobuz/copter-configs/src/branch/master/4_aurora100&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</description>
    <pubDate>Mon, 17 Jul 2017 12:00:00 -0000</pubDate>
    <atom:updated>2024-05-10T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/aurora100.html</guid>
</item>

<item>
    <title>xyLX5</title>
    <link>https://www.xythobuz.de/lx5.html</link>
    <description>&lt;p&gt;&lt;span class=&quot;listdesc&quot;&gt;&lt;a href=&quot;quadcopters.html&quot;&gt;...back to Flying RC Vehicles overview&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;At the end of 2017 I had to give in to the urge of building a bigger more powerful copter.
After my first failed attempts with large copters, gaining some experience with smaller frames seemed useful.
But I had built up some skills and was ready for the next step.
So I built a 220mm frame copter with 2205 motors.&lt;/p&gt;
&lt;p&gt;It has all the bells-and-whistles available at the time, like DShot, Betaflight OSD, Smartaudio, as well as S-Bus and Smartport for two-way communication.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lx5_3.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lx5_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lx5_3_small.jpg&quot; alt=&quot;Front view&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lx5_1.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lx5_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lx5_1_small.jpg&quot; alt=&quot;Left side view&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lx5_2.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lx5_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lx5_2_small.jpg&quot; alt=&quot;Right side view&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lx5_4.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lx5_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lx5_4_small.jpg&quot; alt=&quot;Left stack closeup view&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lx5_5.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lx5_5.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lx5_5_small.jpg&quot; alt=&quot;Right stack closeup view&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-video='{&quot;source&quot;: [{&quot;src&quot;:&quot;img/lx5_toolbox_instagram.mp4&quot;, &quot;type&quot;:&quot;video/mp4&quot;}], &quot;attributes&quot;: {&quot;preload&quot;: false, &quot;playsinline&quot;: true, &quot;controls&quot;: true}}' data-lg-size=&quot;640-640&quot; data-poster=&quot;img/lx5_toolbox_instagram_poster.png&quot; data-sub-html=&quot;Footage of me flying at a cocktail machine related event&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lx5_toolbox_instagram.mp4&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lx5_toolbox_instagram_thumb.png&quot; alt=&quot;Footage of me flying at a cocktail machine related event&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_7.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_7.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_7_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Here are some recordings of the FPV footage.
Be aware that these videos are recorded from the received signal on the ground.
Not only can you see lots of interference and other HF problems, the loss of quality from the video recorder and the YouTube encoding is also very noticeable.
In real-life, the picture doesn't look &lt;em&gt;that&lt;/em&gt; bad.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.youtube.com/watch?v=5Fv40mtiXZU&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=5Fv40mtiXZU&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/xylx5_test_flight_thumb.jpg&quot; alt=&quot;LX5 Quadcopter Test Flight&quot;&gt;&lt;img src=&quot;https://www.xythobuz.de/img/video-play.png&quot; class=&quot;picthumb&quot; alt=&quot;Media play button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.youtube.com/watch?v=Xgcp8qG9qD8&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Xgcp8qG9qD8&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/xylx5_chase_thumb.jpg&quot; alt=&quot;LX5 Quadcopter chase&quot;&gt;&lt;img src=&quot;https://www.xythobuz.de/img/video-play.png&quot; class=&quot;picthumb&quot; alt=&quot;Media play button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.youtube.com/watch?v=wUiboR8pkoA&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=wUiboR8pkoA&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/xylx5_range_thumb.jpg&quot; alt=&quot;LX5 Quadcopter range&quot;&gt;&lt;img src=&quot;https://www.xythobuz.de/img/video-play.png&quot; class=&quot;picthumb&quot; alt=&quot;Media play button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.youtube.com/watch?v=SRrjPk51ng0&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=SRrjPk51ng0&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://img.youtube.com/vi/SRrjPk51ng0/0.jpg&quot; alt=&quot;Flying below a bridge, over a creek&quot; style=&quot;width:300px;&quot; data-poster=&quot;https://img.youtube.com/vi/SRrjPk51ng0/0.jpg&quot;&gt;&lt;img src=&quot;https://www.xythobuz.de/img/video-play.png&quot; class=&quot;picthumb&quot; alt=&quot;Media play button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.youtube.com/watch?v=Zh9_t0R_MWw&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Zh9_t0R_MWw&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://img.youtube.com/vi/Zh9_t0R_MWw/0.jpg&quot; alt=&quot;First flights after BF 4.4.1 upgrade&quot; style=&quot;width:300px;&quot; data-poster=&quot;https://img.youtube.com/vi/Zh9_t0R_MWw/0.jpg&quot;&gt;&lt;img src=&quot;https://www.xythobuz.de/img/video-play.png&quot; class=&quot;picthumb&quot; alt=&quot;Media play button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.youtube.com/watch?v=CLe2nqUcd8E&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=CLe2nqUcd8E&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://img.youtube.com/vi/CLe2nqUcd8E/0.jpg&quot; alt=&quot;Prop Explosion&quot; style=&quot;width:300px;&quot; data-poster=&quot;https://img.youtube.com/vi/CLe2nqUcd8E/0.jpg&quot;&gt;&lt;img src=&quot;https://www.xythobuz.de/img/video-play.png&quot; class=&quot;picthumb&quot; alt=&quot;Media play button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Using a 3D printed baseplate, I mounted the FrSky Rx as the third/top level of the PCB stack.
To fit in the VTx as well, I simply zip-tied it to the bottom side of this plate.&lt;/p&gt;
&lt;p&gt;To protect the remote control antennas, I added two zip-ties to the cockpit frame, trimmed to the length of the antennas, and shrinkwrapped the antenna coax cables to the whole zip-tie.
This works very well as crash protection, I never managed to destroy an antenna with this setup.&lt;/p&gt;
&lt;p&gt;The VTx Antenna pigtail is comfortably mounted with a special 3D printed piece.
This way, the 5.8GHz Antenna (I'm using a Padoga v2) can easily be changed after crashes and removed for transportation.&lt;/p&gt;
&lt;p&gt;For the bottom most level of the PCB stack I used rubber vibration-insulating stand-offs.
I'm not sure if they are really necessary, but they don't seem to hurt either.
Everything else is mounted with rigid spacers.&lt;/p&gt;
&lt;h2&gt;Parts List&lt;/h2&gt;
&lt;p&gt;These are the original prices I paid back when I initially bought the parts.&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Frame
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://geprc.com/product/gep-lx5-frame/&quot;&gt;GEP-LX5 Leopard Frame, Green Color&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
47.20€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
FC
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.hobbywing.com/goods.php?id=590&quot;&gt;Hobbywing XRotor Omnibus F4 Flight Controller&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
34.08€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
ESCs
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.hobbywing.com/goods.php?id=588&quot;&gt;Hobbywing XRotor Micro 40A 2-5S 4 in 1 ESC&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
52.44€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Motors
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.banggood.com/4X-Emax-RS2205-2300-2205-2300KV-Racing-Edition-CW-or-CCW-Motor-For-RC-FPV-Racing-Drone-p-1032857.html?cur_warehouse=CN&quot;&gt;4x Emax RS2205 2300KV CW/CCW Motor&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
46.32€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Props
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.banggood.com/10-Pairs-Racerstar-S5048-PC-3-blade-Propeller-5_0mm-Mounting-Hole-for-RC-Multirotor-FPV-Racing-Drone-p-1169658.html&quot;&gt;10 Pairs Racerstar S5048 PC 3-blade Propeller 5.0mm Mounting Hole&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
10.48€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Cam
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://shop.runcam.com/runcam-micro-sparrow/&quot;&gt;RunCam Micro Sparrow 2.1mm&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
26.22€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
VTx
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.team-blacksheep.com/products/prod:unify_pro&quot;&gt;TBS Unify Pro V3 5V&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
44.90€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Rx
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.frsky-rc.com/product/xsr/&quot;&gt;FrSky XSR&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
13.98€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Battery
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.stefansliposhop.de/en/batteries/sls-quantum/sls-quantum-65c/sls-quantum-1300mah-4s1p-14-8v-65c-130c::1602.html&quot;&gt;SLS Quantum 1300mAh 4S1P 14,8V 65C/130C&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
26.00€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;

&lt;/td&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Sum
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
301.62€
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;I have 3D printed the following parts for this copter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.thingiverse.com/thing:2416095&quot;&gt;&quot;GEP LX5&quot; arm guards by Wthompson87&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.thingiverse.com/thing:2544507&quot;&gt;&quot;GEP LX TBS Unify Pro VTX Antenna Holder&quot; by SnappyFPV&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.thingiverse.com/thing:2616057&quot;&gt;&quot;Adapter for Runcam Micro to Full Size&quot; by phezter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.thingiverse.com/thing:2171446&quot;&gt;&quot;FrSky XSR stack mount with holes for zip ties&quot; by tozes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can find my Betaflight configuration dumps &lt;a href=&quot;https://codeberg.org/xythobuz/copter-configs/src/branch/master/5_lx5&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</description>
    <pubDate>Wed, 01 Nov 2017 12:00:00 -0000</pubDate>
    <atom:updated>2024-05-10T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/lx5.html</guid>
</item>

<item>
    <title>Auto-generated project docs with 3D preview</title>
    <link>https://www.xythobuz.de/2024_05_05_auto_project_docs.html</link>
    <description>&lt;p&gt;A recurring problem I ran into with my projects is documentation.
Especially with 3D printed parts or PCBs it's often required to generate preview images or PDFs so someone interested can take a look without having to clone the repo and install some development tools to open the original files.
Doing this manually is time-consuming and repetitive.
So it should be automated.&lt;/p&gt;
&lt;p&gt;I've come across a couple of similar projects recently, like &lt;a href=&quot;https://hackaday.com/2024/05/04/giving-your-kicad-pcb-repository-pretty-pictures/&quot;&gt;this&lt;/a&gt; or &lt;a href=&quot;https://hackaday.com/2024/03/11/share-your-projects-kicad-automations-and-pretty-renders/&quot;&gt;these&lt;/a&gt;.
But they mostly spit out non-interactive images.
So I've decided to go a slightly different and more interactive route.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/github_pages_sch.png&quot; data-lg-size=&quot;800-699&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/github_pages_sch.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/github_pages_sch_small.png&quot; alt=&quot;2D KiCad board visualization&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/github_pages_pcb.png&quot; data-lg-size=&quot;800-645&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/github_pages_pcb.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/github_pages_pcb_small.png&quot; alt=&quot;3D KiCad board visualization&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/github_pages_stl.png&quot; data-lg-size=&quot;801-686&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/github_pages_stl.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/github_pages_stl_small.png&quot; alt=&quot;3D STL visualization&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Using &lt;a href=&quot;https://docs.github.com/en/actions&quot;&gt;GitHub Actions&lt;/a&gt; and &lt;a href=&quot;https://docs.github.com/en/pages&quot;&gt;GitHub Pages&lt;/a&gt;, I'm creating a static documentation website using &lt;a href=&quot;https://rust-lang.github.io/mdBook/&quot;&gt;mdBook&lt;/a&gt;.
To visualize KiCad schematics and 2D board designs I'm exporting them as svg images and display them interactively using &lt;a href=&quot;https://github.com/bumbu/svg-pan-zoom&quot;&gt;svg-pan-zoom&lt;/a&gt;.
KiCad PCBs are also exported in 3D as VRML files and displayed using &lt;a href=&quot;https://threejs.org/&quot;&gt;three.js&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To generate these files I'm mostly using shell scripts in the project repository.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;generate_stls.sh&lt;/code&gt; is creating STL files from OpenSCAD sources&lt;/li&gt;
&lt;li&gt;&lt;code&gt;generate_fab.sh&lt;/code&gt; is creating Gerber files to order PCBs&lt;/li&gt;
&lt;li&gt;&lt;code&gt;generate_plot.sh&lt;/code&gt; is creating the 2D and 3D files for KiCad projects&lt;/li&gt;
&lt;li&gt;&lt;code&gt;generate_docs.sh&lt;/code&gt; is creating the static website using the output from the previous scripts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then the workflows can be kept relatively short, just installing the required dependencies, running the proper script and putting the resulting files where they are needed.
This way the coupling to the proprietary GitHub features is not that thight and can hopefully be ported to other providers with relative ease.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cmake.yml&lt;/code&gt; is creating firmware archives (also usable for OTA updates)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scad.yml&lt;/code&gt; to create the STL files&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kicad.yml&lt;/code&gt; to generate board gerber files&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docs.yml&lt;/code&gt; to create the static documentation pages&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The whole workflow is relatively customizable.
So for some projects I'm for example creating special board previews for etching PCBs at home.&lt;/p&gt;
&lt;p&gt;I've implemented this workflow in a couple of projects now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://kauzerei.github.io/openautolab/&quot;&gt;OpenAutoLab&lt;/a&gt; (&lt;a href=&quot;https://github.com/kauzerei/openautolab&quot;&gt;Repo&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://xythobuz.github.io/lars/&quot;&gt;LARS&lt;/a&gt; (&lt;a href=&quot;https://github.com/xythobuz/lars&quot;&gt;Repo&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://drinkrobotics.github.io/dispensy/&quot;&gt;Dispensy&lt;/a&gt; (&lt;a href=&quot;https://github.com/drinkrobotics/dispensy&quot;&gt;Repo&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kauzerei.github.io/tritonmischer/&quot;&gt;Tritonmischer&lt;/a&gt; (&lt;a href=&quot;https://github.com/kauzerei/tritonmischer&quot;&gt;Repo&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Take a look there for the implementation details.&lt;/p&gt;
&lt;h2&gt;Example Implementation&lt;/h2&gt;
&lt;p&gt;Here are the basics to get you started.&lt;/p&gt;
&lt;h4&gt;1) mdBook dependency&lt;/h4&gt;
&lt;p&gt;Fetch the &lt;a href=&quot;https://github.com/rust-lang/mdBook/releases&quot;&gt;pre-built mdBook binary&lt;/a&gt; and place it in your PATH.&lt;/p&gt;
&lt;h4&gt;2) mdBook initialization&lt;/h4&gt;
&lt;p&gt;Add a &lt;a href=&quot;https://github.com/xythobuz/lars/tree/master/docs&quot;&gt;docs&lt;/a&gt; directory to your project, either by copying it from one of my repos, or creating a new one and adapting the resulting &lt;code&gt;book.toml&lt;/code&gt; config.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mdbook init docs
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3) svg-pan-zoom dependency&lt;/h4&gt;
&lt;p&gt;If you want to visualize SVG images for KiCad schematics and boards, add svg-pan-zoom as a subrepo.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd docs
git submodule add https://github.com/bumbu/svg-pan-zoom
mkdir -p src/js
cd src/js
ln -s ../../svg-pan-zoom/dist/svg-pan-zoom.js svg-pan-zoom.js
ln -s ../../svg-pan-zoom/dist/svg-pan-zoom.min.js svg-pan-zoom.min.js
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4) Generating docs&lt;/h4&gt;
&lt;p&gt;Add the &lt;code&gt;docs/generate_docs.sh&lt;/code&gt; script and adapt the config and calls to other script at the beginning, as needed for your project:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://codeberg.org/xythobuz/lars/raw/commit/314bf218ca5e958d6ffa825d92d702cb5431abf6/docs/generate_docs.sh&quot;&gt;Here&lt;/a&gt; is an example &lt;code&gt;generate_docs.sh&lt;/code&gt; file.&lt;/p&gt;
&lt;pre class=&quot;sh_sh&quot;&gt;
#!/bin/bash

# SPDX-FileCopyrightText: 2024 Thomas Buck &amp;lt;thomas@xythobuz.de&amp;gt;
# SPDX-License-Identifier: CERN-OHL-S-2.0+
#
#  ------------------------------------------------------------------------------
# | Copyright (c) 2024 Thomas Buck &amp;lt;thomas@xythobuz.de&amp;gt;                          |
# |                                                                              |
# | This source describes Open Hardware and is licensed under the CERN-OHL-S v2  |
# | or any later version.                                                        |
# |                                                                              |
# | You may redistribute and modify this source and make products using it under |
# | the terms of the CERN-OHL-S v2 (https://ohwr.org/cern_ohl_s_v2.txt)          |
# | or any later version.                                                        |
# |                                                                              |
# | This source is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY,          |
# | INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A         |
# | PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 (or any later version)      |
# | for applicable conditions.                                                   |
# |                                                                              |
# | Source location: https://git.xythobuz.de/thomas/drumkit                      |
# |                                                                              |
# | As per CERN-OHL-S v2 section 4, should You produce hardware based on this    |
# | source, You must where practicable maintain the Source Location visible      |
# | on the external case of the Gizmo or other products you make using this      |
# | source.                                                                      |
#  ------------------------------------------------------------------------------

INSCH=&amp;quot;../pcb/drumkit.kicad_sch ../pcb2/lars2.kicad_sch&amp;quot;
INPCB=&amp;quot;../pcb/drumkit.kicad_pcb ../pcb2/lars2.kicad_pcb&amp;quot;

cd &amp;quot;$(dirname &amp;quot;$0&amp;quot;)&amp;quot;

if [ &amp;quot;$1&amp;quot; = &amp;quot;build&amp;quot; ] ; then
    echo &amp;quot;Generating plots&amp;quot;
    ../pcb/generate_plot.sh
    echo
    echo &amp;quot;Generating fab&amp;quot;
    ../pcb/generate_fab.sh
    echo

    echo &amp;quot;Generating plots 2&amp;quot;
    ../pcb2/generate_plot.sh
    echo
    echo &amp;quot;Generating fab 2&amp;quot;
    ../pcb2/generate_fab.sh
    echo

    echo &amp;quot;Generating stls&amp;quot;
    ../3dprint/generate_stls.sh
    echo
fi

rm -rf src/plot
cp -r ../pcb/plot src
cp -r ../pcb2/plot/* src/plot/
cp ../pcb/fab.zip src/plot/fab_pcb.zip
cp ../pcb2/fab.zip src/plot/fab_pcb2.zip

rm -rf src/stl
cp -r ../3dprint/stl src

INSTL=`ls ../3dprint/stl/*.stl`

for path in $INSCH
do
    IN=`echo $path | sed &amp;quot;s:../pcb.\\?/::g&amp;quot;`
    o=&amp;quot;src/inc_$IN.md&amp;quot;
    echo &amp;quot;Include for $IN at $o&amp;quot;

    rm -rf $o
    echo &amp;quot;&amp;lt;script src=\&amp;quot;js/svg-pan-zoom.js\&amp;quot; charset=\&amp;quot;UTF-8\&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&amp;quot; &amp;gt;&amp;gt; $o

    for f in `ls src/plot/$IN.svg/*.svg | sort -r`; do
        file=`echo $f | sed 's:src/:./:g'`
        name=`echo $f | sed &amp;quot;s:src/plot/$IN.svg/::g&amp;quot; | sed 's:.svg::g'`
        echo &amp;quot;Sheet at $name&amp;quot;

        echo &amp;quot;&amp;lt;h2&amp;gt;$name&amp;lt;/h2&amp;gt;&amp;quot; &amp;gt;&amp;gt; $o
        echo &amp;quot;&amp;lt;div style=\&amp;quot;background-color: white; border: 1px solid black;\&amp;quot;&amp;gt;&amp;quot; &amp;gt;&amp;gt; $o
        echo &amp;quot;&amp;lt;embed type=\&amp;quot;image/svg+xml\&amp;quot; src=\&amp;quot;$file\&amp;quot; id=\&amp;quot;pz_$name\&amp;quot; style=\&amp;quot;width:100%;\&amp;quot;/&amp;gt;&amp;quot; &amp;gt;&amp;gt; $o
        echo &amp;quot;&amp;lt;script&amp;gt;&amp;quot; &amp;gt;&amp;gt; $o
        echo &amp;quot;document.getElementById('pz_$name').addEventListener('load', function(){&amp;quot; &amp;gt;&amp;gt; $o
        echo &amp;quot;svgPanZoom(document.getElementById('pz_$name'), {controlIconsEnabled: true, minZoom: 1.0});&amp;quot; &amp;gt;&amp;gt; $o
        echo &amp;quot;})&amp;quot; &amp;gt;&amp;gt; $o
        echo &amp;quot;&amp;lt;/script&amp;gt;&amp;quot; &amp;gt;&amp;gt; $o
        echo &amp;quot;&amp;lt;/div&amp;gt;&amp;quot; &amp;gt;&amp;gt; $o
        echo &amp;gt;&amp;gt; $o
        echo &amp;quot;[Direct link to \`$name\`]($file).&amp;quot; &amp;gt;&amp;gt; $o
        echo &amp;gt;&amp;gt; $o
    done

    echo
done

plot_3d() {
    echo '&amp;lt;script type=&amp;quot;importmap&amp;quot;&amp;gt;' &amp;gt;&amp;gt; $1
    echo '    {' &amp;gt;&amp;gt; $1
    echo '        &amp;quot;imports&amp;quot;: {' &amp;gt;&amp;gt; $1
    echo '            &amp;quot;three&amp;quot;: &amp;quot;https://cdn.jsdelivr.net/npm/three@0.163.0/build/three.module.js&amp;quot;,' &amp;gt;&amp;gt; $1
    echo '            &amp;quot;three/addons/&amp;quot;: &amp;quot;https://cdn.jsdelivr.net/npm/three@0.163.0/examples/jsm/&amp;quot;' &amp;gt;&amp;gt; $1
    echo '        }' &amp;gt;&amp;gt; $1
    echo '    }' &amp;gt;&amp;gt; $1
    echo '&amp;lt;/script&amp;gt;' &amp;gt;&amp;gt; $1
    echo &amp;quot;&amp;lt;p&amp;gt;Status: \&amp;quot;&amp;lt;span id=\&amp;quot;3d_info_$2\&amp;quot;&amp;gt;Preparing 3D model...&amp;lt;/span&amp;gt;\&amp;quot;&amp;lt;/p&amp;gt;&amp;quot; &amp;gt;&amp;gt; $1
    echo &amp;quot;&amp;lt;div id=\&amp;quot;3d_viewer_$2\&amp;quot; style=\&amp;quot;width: 100%; height: 100%; background-color: white; border: 1px solid black; position: relative;\&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;quot; &amp;gt;&amp;gt; $1
    echo '&amp;lt;script type=&amp;quot;module&amp;quot;&amp;gt;' &amp;gt;&amp;gt; $1
    echo &amp;quot;    var info = document.getElementById(\&amp;quot;3d_info_$2\&amp;quot;);&amp;quot; &amp;gt;&amp;gt; $1
    echo &amp;quot;    var view = document.getElementById(\&amp;quot;3d_viewer_$2\&amp;quot;);&amp;quot; &amp;gt;&amp;gt; $1
    echo '    view.style.height = Math.floor(view.clientWidth * 0.707) + &amp;quot;px&amp;quot;;' &amp;gt;&amp;gt; $1
    echo '    import { init_3d } from &amp;quot;./js/3d.js&amp;quot;;' &amp;gt;&amp;gt; $1
    echo &amp;quot;    init_3d(\&amp;quot;$3\&amp;quot;, view, info, view.clientWidth, view.clientHeight);&amp;quot; &amp;gt;&amp;gt; $1
    echo '&amp;lt;/script&amp;gt;' &amp;gt;&amp;gt; $1
}

for path in $INPCB
do
    IN=`echo $path | sed &amp;quot;s:../pcb.\\?/::g&amp;quot;`
    o=&amp;quot;src/inc_$IN.md&amp;quot;
    file=&amp;quot;plot/$IN.wrl&amp;quot;
    name=`echo $file | sed &amp;quot;s:plot/::g&amp;quot; | sed 's:.wrl::g'`
    echo &amp;quot;Include for $IN at $o, $file, $name&amp;quot;
    rm -rf $o

    plot_3d $o $name $file
done
echo

for IN in $INSTL
do
    o=`echo $IN | sed &amp;quot;s:../3dprint/stl/:src/inc_:g&amp;quot; | sed &amp;quot;s:.stl:.stl.md:g&amp;quot;`
    file=`echo $IN | sed &amp;quot;s:../3dprint/::g&amp;quot;`
    name=`echo $file | sed &amp;quot;s:stl/::g&amp;quot; | sed 's:.stl::g'`
    echo &amp;quot;Include for $IN at $o, $file, $name&amp;quot;
    rm -rf $o

    plot_3d $o $name $file
done
echo

echo &amp;quot;Generating docs&amp;quot;
if [ &amp;quot;$1&amp;quot; = &amp;quot;serve&amp;quot; ] ; then
    mdbook serve --open
elif [ &amp;quot;$1&amp;quot; = &amp;quot;build&amp;quot; ] ; then
    mdbook build
else
    echo &amp;quot;Invalid command. 'build' or 'serve'.&amp;quot;
fi
&lt;/pre&gt;

&lt;h4&gt;5) Auto-generating and publishing docs&lt;/h4&gt;
&lt;p&gt;Add the &lt;code&gt;.github/workflows/docs.yml&lt;/code&gt; script:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://codeberg.org/xythobuz/lars/raw/commit/314bf218ca5e958d6ffa825d92d702cb5431abf6/.github/workflows/docs.yml&quot;&gt;Here&lt;/a&gt; is an example &lt;code&gt;docs.yml&lt;/code&gt; file.&lt;/p&gt;
&lt;pre class=&quot;sh_yaml&quot;&gt;
# SPDX-FileCopyrightText: 2024 Thomas Buck &amp;lt;thomas@xythobuz.de&amp;gt;
# SPDX-License-Identifier: CERN-OHL-S-2.0+
#
#  ------------------------------------------------------------------------------
# | Copyright (c) 2024 Thomas Buck &amp;lt;thomas@xythobuz.de&amp;gt;                          |
# |                                                                              |
# | This source describes Open Hardware and is licensed under the CERN-OHL-S v2  |
# | or any later version.                                                        |
# |                                                                              |
# | You may redistribute and modify this source and make products using it under |
# | the terms of the CERN-OHL-S v2 (https://ohwr.org/cern_ohl_s_v2.txt)          |
# | or any later version.                                                        |
# |                                                                              |
# | This source is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY,          |
# | INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A         |
# | PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 (or any later version)      |
# | for applicable conditions.                                                   |
# |                                                                              |
# | Source location: https://git.xythobuz.de/thomas/drumkit                      |
# |                                                                              |
# | As per CERN-OHL-S v2 section 4, should You produce hardware based on this    |
# | source, You must where practicable maintain the Source Location visible      |
# | on the external case of the Gizmo or other products you make using this      |
# | source.                                                                      |
#  ------------------------------------------------------------------------------

name: Docs

# only build single instance of docs for latest main branch
on:
  push:
    branches:
      - master

jobs:
  deploy:
    runs-on: ubuntu-latest

    permissions:
      contents: write
      pages: write
      id-token: write

    steps:
      - name: Checkout repo
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Checkout repo submodules
        run: git submodule update --init

      - name: Install dependencies
        run: |
          sudo add-apt-repository --yes ppa:kicad/kicad-8.0-releases
          sudo apt update
          sudo apt install -y --install-recommends kicad pipx libfuse2 libegl1 poppler-utils openscad zip

      - name: Install latest mdbook
        run: |
          tag=$(curl 'https://api.github.com/repos/rust-lang/mdbook/releases/latest' | jq -r '.tag_name')
          url=&amp;quot;https://github.com/rust-lang/mdbook/releases/download/${tag}/mdbook-${tag}-x86_64-unknown-linux-gnu.tar.gz&amp;quot;
          mkdir mdbook
          curl -sSL $url | tar -xz --directory=./mdbook
          echo `pwd`/mdbook &amp;gt;&amp;gt; $GITHUB_PATH

      - name: Build Book
        run: docs/generate_docs.sh build

      - name: Setup Pages
        uses: actions/configure-pages@v2

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v1
        with:
          path: 'docs/book'

      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v1
&lt;/pre&gt;

&lt;p&gt;And don't forget to set the GitHub Pages source in the repo settings to GitHub Actions:&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/github_pages_actions.png&quot; data-lg-size=&quot;1128-526&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/github_pages_actions.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/github_pages_actions_small.png&quot; alt=&quot;What to change in the GitHub repo settings&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h4&gt;6) STL 3D visualization&lt;/h4&gt;
&lt;p&gt;If you want to visualize 3D print files, do the same with &lt;code&gt;3dprint/generate_stls.sh&lt;/code&gt; and &lt;code&gt;.github/workflows/scad.yml&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://codeberg.org/xythobuz/lars/raw/commit/314bf218ca5e958d6ffa825d92d702cb5431abf6/3dprint/generate_stls.sh&quot;&gt;Here&lt;/a&gt; is an example &lt;code&gt;generate_stls.sh&lt;/code&gt; file.&lt;/p&gt;
&lt;pre class=&quot;sh_sh&quot;&gt;
#!/bin/bash

# ----------------------------------------------------------------------------
# Copyright (c) 2023 Kauzerei (openautolab@kauzerei.de)
# Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# See &amp;lt;http://www.gnu.org/licenses/&amp;gt;.
# ----------------------------------------------------------------------------

# space separated list of scad files (without extension)
MODULES=&amp;quot;actuator beam enclosure tamb_mount&amp;quot;
OUTDIR=&amp;quot;stl&amp;quot;

# enter directory of script (case)
cd &amp;quot;$(dirname &amp;quot;$0&amp;quot;)&amp;quot;

# Detect OS
if [[ &amp;quot;$OSTYPE&amp;quot; == &amp;quot;darwin&amp;quot;* ]]; then
    echo &amp;quot;Mac OS X detected&amp;quot;
    SCAD=&amp;quot;open -n -a OpenSCAD --args&amp;quot;
else
    echo &amp;quot;Linux detected&amp;quot;
    SCAD=&amp;quot;openscad&amp;quot;
fi

echo &amp;quot;deleting previous build output&amp;quot;
rm -rf $OUTDIR
mkdir -p $OUTDIR

for MODULE in $MODULES
do
    PARTS=$(grep -o &amp;quot;part.*//.*\[.*]&amp;quot; ${MODULE}.scad | sed 's/,/ /g' | sed 's/.*\[\([^]]*\)\].*/\1/g')
    echo &amp;quot;generating from ${MODULE}&amp;quot;

    for PART in ${PARTS}
    do
        if [[ &amp;quot;${PART}&amp;quot; != &amp;quot;OPT_&amp;quot;* ]]; then
            echo ${PART}
            FILENAME=$(echo $OUTDIR/${MODULE}_${PART}.stl | tr '[:upper:]' '[:lower:]')
            $SCAD $(pwd)/${MODULE}.scad --D part=\&amp;quot;${PART}\&amp;quot; --o $(pwd)/${FILENAME}
        fi
    done
done
&lt;/pre&gt;

&lt;p&gt;&lt;a href=&quot;https://codeberg.org/xythobuz/lars/raw/commit/314bf218ca5e958d6ffa825d92d702cb5431abf6/.github/workflows/scad.yml&quot;&gt;Here&lt;/a&gt; is an example &lt;code&gt;scad.yml&lt;/code&gt; file.&lt;/p&gt;
&lt;pre class=&quot;sh_yaml&quot;&gt;
# SPDX-FileCopyrightText: 2024 Thomas Buck &amp;lt;thomas@xythobuz.de&amp;gt;
# SPDX-License-Identifier: CERN-OHL-S-2.0+
#
#  ------------------------------------------------------------------------------
# | Copyright (c) 2024 Thomas Buck &amp;lt;thomas@xythobuz.de&amp;gt;                          |
# |                                                                              |
# | This source describes Open Hardware and is licensed under the CERN-OHL-S v2  |
# | or any later version.                                                        |
# |                                                                              |
# | You may redistribute and modify this source and make products using it under |
# | the terms of the CERN-OHL-S v2 (https://ohwr.org/cern_ohl_s_v2.txt)          |
# | or any later version.                                                        |
# |                                                                              |
# | This source is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY,          |
# | INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A         |
# | PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 (or any later version)      |
# | for applicable conditions.                                                   |
# |                                                                              |
# | Source location: https://git.xythobuz.de/thomas/drumkit                      |
# |                                                                              |
# | As per CERN-OHL-S v2 section 4, should You produce hardware based on this    |
# | source, You must where practicable maintain the Source Location visible      |
# | on the external case of the Gizmo or other products you make using this      |
# | source.                                                                      |
#  ------------------------------------------------------------------------------

name: STLs

# build for each push and pull request
on: [push, pull_request]

jobs:
  render:
    runs-on: ubuntu-latest

    permissions:
      contents: write

    steps:
      - name: Checkout repo
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Checkout repo submodules
        run: git submodule update --init

      - name: Install dependencies
        run: |
          sudo apt update
          sudo apt-get install -y openscad zip

      - name: Render STLs
        run: |
          ./3dprint/generate_stls.sh

      - name: Upload part files
        uses: actions/upload-artifact@v4.0.0
        with:
          name: drumkit-stl
          path: 3dprint/stl
          if-no-files-found: error

      - name: Archive release files
        if: startsWith(github.ref, 'refs/tags/')
        run: |
          cd 3dprint
          zip -r drumkit-stl stl

      - name: Upload release files
        if: startsWith(github.ref, 'refs/tags/')
        uses: softprops/action-gh-release@v1
        with:
          files: 3dprint/drumkit-stl.zip
&lt;/pre&gt;

&lt;p&gt;And then add something like this to the mdBook sources where you want the visualization to appear:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\{{#include inc_enclosure_bottom.stl.md}}

[Direct link to this file](./stl/enclosure_bottom.stl).
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;7) PCB gerber files&lt;/h4&gt;
&lt;p&gt;If you want to generate gerber files from PCBs, do something similar with &lt;code&gt;pcb/generate_fab.sh&lt;/code&gt; and &lt;code&gt;.github/workflows/kicad.yml&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://codeberg.org/xythobuz/lars/raw/commit/314bf218ca5e958d6ffa825d92d702cb5431abf6/pcb2/generate_fab.sh&quot;&gt;Here&lt;/a&gt; is an example &lt;code&gt;generate_fab.sh&lt;/code&gt; file.&lt;/p&gt;
&lt;pre class=&quot;sh_sh&quot;&gt;
#!/bin/bash

# SPDX-FileCopyrightText: 2024 Thomas Buck &amp;lt;thomas@xythobuz.de&amp;gt;
# SPDX-License-Identifier: CERN-OHL-S-2.0+
#
#  ------------------------------------------------------------------------------
# | Copyright (c) 2024 Thomas Buck &amp;lt;thomas@xythobuz.de&amp;gt;                          |
# |                                                                              |
# | This source describes Open Hardware and is licensed under the CERN-OHL-S v2  |
# | or any later version.                                                        |
# |                                                                              |
# | You may redistribute and modify this source and make products using it under |
# | the terms of the CERN-OHL-S v2 (https://ohwr.org/cern_ohl_s_v2.txt)          |
# | or any later version.                                                        |
# |                                                                              |
# | This source is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY,          |
# | INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A         |
# | PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 (or any later version)      |
# | for applicable conditions.                                                   |
# |                                                                              |
# | Source location: https://git.xythobuz.de/thomas/drumkit                      |
# |                                                                              |
# | As per CERN-OHL-S v2 section 4, should You produce hardware based on this    |
# | source, You must where practicable maintain the Source Location visible      |
# | on the external case of the Gizmo or other products you make using this      |
# | source.                                                                      |
#  ------------------------------------------------------------------------------

cd &amp;quot;$(dirname &amp;quot;$0&amp;quot;)&amp;quot;

INFILE=&amp;quot;lars2.kicad_pcb&amp;quot;
INFILE_SCH=&amp;quot;lars2.kicad_sch&amp;quot;
OUTDIR=&amp;quot;fabrication&amp;quot;
OUTZIP=&amp;quot;fab&amp;quot;

echo &amp;quot;Creating output directory&amp;quot;
rm -rf $OUTDIR
mkdir -p $OUTDIR

echo &amp;quot;Exporting drill files&amp;quot;
#kicad-cli pcb export drill -o $OUTDIR/ --format excellon --generate-map --map-format pdf $INFILE
kicad-cli pcb export drill -o $OUTDIR/ --format gerber --generate-map --map-format gerberx2 $INFILE

echo &amp;quot;Exporting gerber files&amp;quot;
#kicad-cli pcb export gerbers -o $OUTDIR/ $INFILE
kicad-cli pcb export gerbers -o $OUTDIR/ -l F.Cu,B.Cu,F.Mask,B.Mask,F.Paste,B.Paste,F.Silkscreen,B.Silkscreen,Edge.Cuts $INFILE

echo &amp;quot;Exporting BOM files&amp;quot;
kicad-cli sch export python-bom -o $OUTDIR/bom.xml $INFILE_SCH

# TODO convert BOM XML to proper format for JLCPCB

echo &amp;quot;Compressing archive&amp;quot;
rm -rf $OUTZIP.zip
zip -r $OUTZIP fabrication
&lt;/pre&gt;

&lt;p&gt;&lt;a href=&quot;https://codeberg.org/xythobuz/lars/raw/commit/314bf218ca5e958d6ffa825d92d702cb5431abf6/.github/workflows/kicad.yml&quot;&gt;Here&lt;/a&gt; is an example &lt;code&gt;kicad.yml&lt;/code&gt; file.&lt;/p&gt;
&lt;pre class=&quot;sh_yaml&quot;&gt;
# SPDX-FileCopyrightText: 2024 Thomas Buck &amp;lt;thomas@xythobuz.de&amp;gt;
# SPDX-License-Identifier: CERN-OHL-S-2.0+
#
#  ------------------------------------------------------------------------------
# | Copyright (c) 2024 Thomas Buck &amp;lt;thomas@xythobuz.de&amp;gt;                          |
# |                                                                              |
# | This source describes Open Hardware and is licensed under the CERN-OHL-S v2  |
# | or any later version.                                                        |
# |                                                                              |
# | You may redistribute and modify this source and make products using it under |
# | the terms of the CERN-OHL-S v2 (https://ohwr.org/cern_ohl_s_v2.txt)          |
# | or any later version.                                                        |
# |                                                                              |
# | This source is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY,          |
# | INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A         |
# | PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 (or any later version)      |
# | for applicable conditions.                                                   |
# |                                                                              |
# | Source location: https://git.xythobuz.de/thomas/drumkit                      |
# |                                                                              |
# | As per CERN-OHL-S v2 section 4, should You produce hardware based on this    |
# | source, You must where practicable maintain the Source Location visible      |
# | on the external case of the Gizmo or other products you make using this      |
# | source.                                                                      |
#  ------------------------------------------------------------------------------

name: PCB

# build for each push and pull request
on: [push, pull_request]

jobs:
  fabrication:
    runs-on: ubuntu-latest

    permissions:
      contents: write

    steps:
      - name: Checkout repo
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Checkout repo submodules
        run: git submodule update --init

      - name: Install dependencies
        run: |
          sudo add-apt-repository --yes ppa:kicad/kicad-8.0-releases
          sudo apt update
          sudo apt install -y --install-recommends kicad
          sudo apt-get install -y zip

      - name: Generate fabrication files
        run: |
          ./pcb/generate_fab.sh
          ./pcb2/generate_fab.sh

      - name: Upload board files
        uses: actions/upload-artifact@v4.0.0
        with:
          name: prototype-pcb
          path: pcb/fabrication

      - name: Upload board files
        uses: actions/upload-artifact@v4.0.0
        with:
          name: v2-pcb
          path: pcb2/fabrication

      - name: Rename release files
        if: startsWith(github.ref, 'refs/tags/')
        run: |
          mv pcb/fab.zip fab_proto.zip
          mv pcb2/fab.zip fab_v2.zip

      - name: Upload release files
        if: startsWith(github.ref, 'refs/tags/')
        uses: softprops/action-gh-release@v1
        with:
          files: fab_proto.zip fab_v2.zip
&lt;/pre&gt;

&lt;h4&gt;8) PCB visualization&lt;/h4&gt;
&lt;p&gt;If you want to visualize KiCad schematics and PCBs in 2D and 3D, add &lt;code&gt;pcb/generate_plot.sh&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://codeberg.org/xythobuz/lars/raw/commit/314bf218ca5e958d6ffa825d92d702cb5431abf6/pcb2/generate_plot.sh&quot;&gt;Here&lt;/a&gt; is an example &lt;code&gt;generate_plot.sh&lt;/code&gt; file.&lt;/p&gt;
&lt;pre class=&quot;sh_sh&quot;&gt;
#!/bin/bash

# SPDX-FileCopyrightText: 2024 Thomas Buck &amp;lt;thomas@xythobuz.de&amp;gt;
# SPDX-License-Identifier: CERN-OHL-S-2.0+
#
#  ------------------------------------------------------------------------------
# | Copyright (c) 2024 Thomas Buck &amp;lt;thomas@xythobuz.de&amp;gt;                          |
# |                                                                              |
# | This source describes Open Hardware and is licensed under the CERN-OHL-S v2  |
# | or any later version.                                                        |
# |                                                                              |
# | You may redistribute and modify this source and make products using it under |
# | the terms of the CERN-OHL-S v2 (https://ohwr.org/cern_ohl_s_v2.txt)          |
# | or any later version.                                                        |
# |                                                                              |
# | This source is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY,          |
# | INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A         |
# | PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 (or any later version)      |
# | for applicable conditions.                                                   |
# |                                                                              |
# | Source location: https://git.xythobuz.de/thomas/drumkit                      |
# |                                                                              |
# | As per CERN-OHL-S v2 section 4, should You produce hardware based on this    |
# | source, You must where practicable maintain the Source Location visible      |
# | on the external case of the Gizmo or other products you make using this      |
# | source.                                                                      |
#  ------------------------------------------------------------------------------

INSCH=&amp;quot;lars2.kicad_sch&amp;quot;
INPCB=&amp;quot;lars2.kicad_pcb&amp;quot;
OUTDIR=&amp;quot;plot&amp;quot;
LAYER_F=&amp;quot;F.Cu,F.Mask,F.Paste,F.Silkscreen,Edge.Cuts,User.Drawings&amp;quot;
LAYER_B=&amp;quot;B.Cu,B.Mask,B.Paste,B.Silkscreen,Edge.Cuts,User.Drawings&amp;quot;
LAYER_MANUAL=&amp;quot;B.Cu,Edge.Cuts&amp;quot;

cd &amp;quot;$(dirname &amp;quot;$0&amp;quot;)&amp;quot;
rm -rf $OUTDIR
mkdir -p $OUTDIR

#  --------------
# | 2D Schematic |
#  --------------

for IN in $INSCH
do
    echo &amp;quot;Exporting schematic $IN&amp;quot;

    for TYPE in pdf svg
    do
        echo &amp;quot;Exporting schematic $TYPE&amp;quot;
        rm -rf $OUTDIR/$IN.$TYPE
        kicad-cli sch export $TYPE \
            -t &amp;quot;KiCad Default&amp;quot; \
            -o $OUTDIR/$IN.$TYPE \
            $IN
        echo
    done
done

for IN in $INPCB
do
    echo &amp;quot;Exporting board $IN&amp;quot;

    #  -----------
    # | 2D Layout |
    #  -----------

    for TYPE in pdf svg
    do
        rm -rf $OUTDIR/$IN.$TYPE

        echo &amp;quot;Exporting board $TYPE&amp;quot;
        kicad-cli pcb export $TYPE \
            -t &amp;quot;KiCad Classic&amp;quot;  \
            -l $LAYER_F,$LAYER_B \
            -o $OUTDIR/$IN.$TYPE \
            $IN
        echo

        echo &amp;quot;Exporting DIY board $VAR&amp;quot;
        rm -rf $OUTDIR/${IN}_diy.$TYPE
        kicad-cli pcb export $TYPE \
            -t &amp;quot;KiCad Default&amp;quot;  \
            -l $LAYER_MANUAL \
            --black-and-white \
            --negative \
            -o $OUTDIR/${IN}_diy.$TYPE \
            $IN
        echo
    done

    #  -----------
    # | 3D Layout |
    #  -----------
    echo &amp;quot;Exporting board 3D&amp;quot;
    kicad-cli pcb export vrml \
        -o $OUTDIR/$IN.wrl \
        $IN
    echo
done
&lt;/pre&gt;

&lt;p&gt;Then include this where you want the 2D schematic visualization to appear:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;You can also view the [schematics as PDF](./plot/lars2.kicad_sch.pdf).

\{{#include inc_lars2.kicad_sch.md}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this where you want to visualize the 2D PCB layout:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;You can also view the [2D PCB layout as PDF](./plot/lars2.kicad_pcb.pdf).

&amp;lt;script src=&quot;js/svg-pan-zoom.js&quot; charset=&quot;UTF-8&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;div style=&quot;background-color: white; border: 1px solid black;&quot;&amp;gt;
    &amp;lt;embed type=&quot;image/svg+xml&quot; src=&quot;./plot/lars2.kicad_pcb.svg&quot; id=&quot;pz_drumkit0&quot; style=&quot;width: 100%;&quot;/&amp;gt;
    &amp;lt;script&amp;gt;
        document.getElementById('pz_drumkit0').addEventListener('load', function(){
            svgPanZoom(document.getElementById('pz_drumkit0'), {controlIconsEnabled: true, minZoom: 1.0});
        })
    &amp;lt;/script&amp;gt;
&amp;lt;/div&amp;gt;

[Direct link to this file](./plot/lars2.kicad_pcb.svg).
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And add this where you want to visualize the 3D PCB layout:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\{{#include inc_lars2.kicad_pcb.md}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That should more or less be all that's required.&lt;/p&gt;
&lt;p&gt;Of course, adapt paths as needed to match your project layout.
And test locally by calling the scripts manually on your machine:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./pcb/generate_plot.sh
./3dprint/generate_stls.sh

./docs/generate_docs.sh serve
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hope this helps.&lt;/p&gt;</description>
    <pubDate>Sun, 05 May 2024 12:00:00 -0000</pubDate>
    <atom:updated>2024-05-05T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/2024_05_05_auto_project_docs.html</guid>
</item>

<item>
    <title>LARS - Looping Automated Rhythm Station</title>
    <link>https://www.xythobuz.de/lars.html</link>
    <description>&lt;p&gt;For the recent birthday party of one of our &lt;a href=&quot;drinkrobotics.html&quot;&gt;DrinkRobotics&lt;/a&gt; and musician friends, &lt;a href=&quot;https://github.com/kauzerei&quot;&gt;Kauzerei&lt;/a&gt; had the great idea to build a DIY drummachine as a present, to allow them to play the bass without having to gather a jam session group.
He recruited me for some electronics design brainstorming and to write the firmware.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_3.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_3_small.jpg&quot; alt=&quot;LARS drum and controller. © 2024 Kauzerei.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_7.jpg&quot; data-lg-size=&quot;1280-446&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_7.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_7_small.jpg&quot; alt=&quot;LARS logo&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_10.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_10.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_10_small.jpg&quot; alt=&quot;LARS actually running&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;It is mainly made out of repurposed old parts from the recycling bin.
The drum body consists of a tambourine with a 3D printed frame attached, with three hand-wound solenoids mounted on there.
The positioning and different hammers create some (slightly) differing sounds.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_8.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_8.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_8_small.jpg&quot; alt=&quot;Solenoids mounted to frame&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_4.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_4_small.jpg&quot; alt=&quot;LARS on table. © 2024 Kauzerei.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The electronics are made with a Raspberry Pi Pico (or Pico W in this case, although WiFi or BT are not used yet).
This keeps the future option of a USB Midi input open.
The user interface consists of four buttons, each with an LED above, and a clickable rotary encoder, as well as a power switch and an 128x64 SSD1306 OLED display.
To drive the solenoids, three step-up converters create ~16V from the single Li-Ion 18650 cell.
Their output is then switched to the solenoids using MOSFETs.
A small TP4056 module handles recharging the battery.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_14.png&quot; data-lg-size=&quot;1184-820&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_14.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_14_small.png&quot; alt=&quot;Schematic&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_11.jpg&quot; data-lg-size=&quot;582-393&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_11.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_11_small.jpg&quot; alt=&quot;Step up converters&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_12.jpg&quot; data-lg-size=&quot;663-613&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_12.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_12_small.jpg&quot; alt=&quot;LED and button for panel mounting&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;All of this is mounted on a custom hand-etched PCB.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_15.png&quot; data-lg-size=&quot;862-685&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_15.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_15_small.png&quot; alt=&quot;PCB layout&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_13.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_13.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_13_small.jpg&quot; alt=&quot;PCB with photoresist, before etching&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_9.jpg&quot; data-lg-size=&quot;1280-524&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_9.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_9_small.jpg&quot; alt=&quot;Side view of case/pcb sandwich&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;We only came three hours late to the birthday party due to finishing the LARS hardware, and I spent some more time at the party putting some final work into the firmware.
It actually was functionally usable on the party evening, even before everyone was too drunk to care 🥴&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_16.png&quot; data-lg-size=&quot;987-771&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_16.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_16_small.png&quot; alt=&quot;PCB 3D render back&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_17.png&quot; data-lg-size=&quot;1089-878&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_17.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_17_small.png&quot; alt=&quot;PCB 3D render front&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Unfortunately there was no time yet to take good pictures or a video of LARS in action.&lt;/p&gt;
&lt;p&gt;As usual all 3D print files, PCB layout design and firmware source code is &lt;a href=&quot;https://codeberg.org/xythobuz/lars&quot;&gt;available in the repo&lt;/a&gt;.
You can also order the PCB pre-fabricated from an online supplier, the repo includes a script to generate the gerber files.&lt;/p&gt;
&lt;p&gt;The software is not quite finished yet, but without access to the hardware I'm not really able to work on it further.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;lars_v2.html&quot;&gt;For now&lt;/a&gt;...&lt;/p&gt;
&lt;p&gt;&lt;a class=&quot;anchor al2&quot; id=&quot;more_pictures&quot; href=&quot;#more_pictures&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;More Pictures&lt;/h2&gt;
&lt;div class=&quot;collapse&quot;&gt;Some more photographs I didn't use above.&lt;/div&gt;

&lt;div class=&quot;collapsecontent&quot;&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_1.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_1_small.jpg&quot; alt=&quot;LARS front panel. © 2024 Kauzerei.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_2.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_2_small.jpg&quot; alt=&quot;LARS on table. © 2024 Kauzerei.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_5.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_5.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_5_small.jpg&quot; alt=&quot;LARS on table. © 2024 Kauzerei.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/lars_6.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/lars_6.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/lars_6_small.jpg&quot; alt=&quot;LARS on table. © 2024 Kauzerei.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
    <pubDate>Mon, 25 Mar 2024 12:00:00 -0000</pubDate>
    <atom:updated>2024-03-25T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/lars.html</guid>
</item>

<item>
    <title>Toolbox Markdorf anniversary</title>
    <link>https://www.xythobuz.de/2024_03_09_toolbox_anniversary.html</link>
    <description>&lt;p&gt;Last saturday our local hackerspace, the Toolbox Markdorf, held it's &lt;a href=&quot;https://toolbox-bodensee.de/blog/recap-tag-der-offenen-toolbox/&quot;&gt;10 year anniversary celebrations&lt;/a&gt; with an open-door-day.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.youtube.com/watch?v=W0bIb0fHymg&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=W0bIb0fHymg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://img.youtube.com/vi/W0bIb0fHymg/0.jpg&quot; alt=&quot;FPV flights with LX5 at Toolbox Markdorf&quot; style=&quot;width:300px;&quot; data-poster=&quot;https://img.youtube.com/vi/W0bIb0fHymg/0.jpg&quot;&gt;&lt;img src=&quot;https://www.xythobuz.de/img/video-play.png&quot; class=&quot;picthumb&quot; alt=&quot;Media play button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I set up my tents in the &lt;code&gt;E-Lab 2&lt;/code&gt; to show off some projects, mainly the &lt;a href=&quot;osci_music_player.html&quot;&gt;Oscilloscope Music player&lt;/a&gt; and the &lt;a href=&quot;ledmatrix_v2.html&quot;&gt;LED Matrix&lt;/a&gt; as well as my &lt;a href=&quot;quadcopters.html&quot;&gt;Quadcopter stuff&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_1.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_1_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_2.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_2_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_3.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_3_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Outside, in the afternoon, I also did a bit of FPV flying with a TV and receiver setup for the spectators.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_4.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_4_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_9.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_9.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_9_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_14.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_14.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_14_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;We had a surprisingly large number of visitors.
Hopefully this will help get things going again after the Covid dull.&lt;/p&gt;
&lt;p&gt;&lt;a class=&quot;anchor al2&quot; id=&quot;more_pictures&quot; href=&quot;#more_pictures&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;More Pictures&lt;/h2&gt;
&lt;div class=&quot;collapse&quot;&gt;Some more photographs I didn't use above.&lt;/div&gt;

&lt;div class=&quot;collapsecontent&quot;&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_5.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_5.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_5_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_6.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_6.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_6_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_7.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_7.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_7_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_8.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_8.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_8_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_10.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_10.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_10_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_11.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_11.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_11_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_12.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_12.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_12_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_13.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_13.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_13_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_15.jpg&quot; data-lg-size=&quot;1200-1600&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_15.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_15_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
    <pubDate>Sat, 09 Mar 2024 12:00:00 -0000</pubDate>
    <atom:updated>2024-03-18T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/2024_03_09_toolbox_anniversary.html</guid>
</item>

<item>
    <title>Oscilloscope Music Player</title>
    <link>https://www.xythobuz.de/osci_music_player.html</link>
    <description>&lt;p&gt;Soon our local hackerspace &lt;a href=&quot;https://toolbox-bodensee.de/&quot;&gt;Toolbox Bodensee&lt;/a&gt; has its &lt;a href=&quot;https://toolbox-bodensee.de/blog/ankuendigung-tag-der-offenen-tuer-2024/&quot;&gt;10-year anniversary&lt;/a&gt;, and it will also be present at the &lt;a href=&quot;https://www.ibo-messe.de/&quot;&gt;IBO fair 2024 in Friedrichshafen&lt;/a&gt;.
Some five or six years ago I already prepared a small setup at one of the Toolbox open-door-days with an oscilloscope and a laptop playing &lt;a href=&quot;https://oscilloscopemusic.com/&quot;&gt;Jerobeam Fendersons Oscilloscope Music&lt;/a&gt;.
But it never looked quite right.
So I decided to tackle the topic again this year.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_music_2.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_music_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_music_2_small.jpg&quot; alt=&quot;Front of Osci Music Player&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_1.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_1_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_3.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_3_small.jpg&quot; alt=&quot;© 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-video='{&quot;source&quot;: [{&quot;src&quot;:&quot;img/osci_music_8.mp4&quot;, &quot;type&quot;:&quot;video/mp4&quot;}], &quot;attributes&quot;: {&quot;preload&quot;: false, &quot;playsinline&quot;: true, &quot;controls&quot;: true}}' data-lg-size=&quot;720-1280&quot; data-poster=&quot;img/osci_music_8_poster.png&quot; data-sub-html=&quot;'Function' with the Osci Music Player&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_music_8.mp4&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_music_8_thumb.png&quot; alt=&quot;'Function' with the Osci Music Player&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_music_7.jpg&quot; data-lg-size=&quot;960-918&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_music_7.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_music_7_small.jpg&quot; alt=&quot;'Reconstruct' with the Osci Music Player&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h3&gt;Table Of Contents&lt;/h3&gt;
&lt;p&gt;Want to build your own player?
Skip to the &lt;a href=&quot;osci_music_player.html#hardware&quot;&gt;interesting part&lt;/a&gt; then!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;osci_music_player.html#introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;osci_music_player.html#artists&quot;&gt;Artists&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;osci_music_player.html#playback&quot;&gt;Playback&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;osci_music_player.html#oscisettings&quot;&gt;Oscilloscope Settings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;osci_music_player.html#hardware&quot;&gt;Hardware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;osci_music_player.html#software&quot;&gt;Software&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a class=&quot;anchor al2&quot; id=&quot;introduction&quot; href=&quot;#introduction&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;An oscilloscope usually displays one or more waveforms of an electrical signal.
To achieve this a single dot is moved across the screen, with the voltage or amplitude of the signal controlling the vertical deflection (Y axis), and time controlling the horizontal deflection (X axis), meaning the dot automatically moves from left to right with a pre-configured speed.
But most oscilloscopes can also be configured in something called &lt;code&gt;XY mode&lt;/code&gt;, where the horizontal deflection is no longer controlled by time, but instead by another voltage.
This turnes the oscilloscope into a vector display, where a single dot can be moved across the screen.
Note that the brightness can not be controlled, it is fixed.
Some oscilloscopes have a third input that can be used for that, but this is not needed here.&lt;/p&gt;
&lt;p&gt;A common way to achieve digital to analog conversion (DAC) of a signal, with hardware usually available in every household, is using the soundcard of a PC.
The stereo line output channels are basically connected directly to a DAC chip, with only some filtering hardware in between.&lt;/p&gt;
&lt;p&gt;Now combining these concepts, one can connect the two audio output channels of a PC or similar device to the two inputs of an oscilloscope in XY mode.
This enables a kind of visualization of music, and was also used in the past in something called &quot;audio vectorscope&quot; to check the balance of the channels.
If you have a mono signal, with both channels having the exact same contents, the scope will display a 45-degree angled line.
If only one channel has contents, and the other is silent, you will see either a horizontal or a vertical line, depending on which channel is active.
When one channel outputs a sine, and the other a cosine, with the same frequency and phase, you will see a circle.&lt;/p&gt;
&lt;p&gt;Of course with normal music this doesn't look that interesting.
But what if you were to specifically compose music to look good on such a device?&lt;/p&gt;
&lt;p&gt;That's what &lt;strong&gt;Oscilloscope Music&lt;/strong&gt; is.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-video='{&quot;source&quot;: [{&quot;src&quot;:&quot;img/osci_music_9.mp4&quot;, &quot;type&quot;:&quot;video/mp4&quot;}], &quot;attributes&quot;: {&quot;preload&quot;: false, &quot;playsinline&quot;: true, &quot;controls&quot;: true}}' data-lg-size=&quot;720-1280&quot; data-poster=&quot;img/osci_music_9_poster.png&quot; data-sub-html=&quot;'Reconstruct' with the Osci Music Player&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_music_9.mp4&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_music_9_thumb.png&quot; alt=&quot;'Reconstruct' with the Osci Music Player&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a class=&quot;anchor al2&quot; id=&quot;artists&quot; href=&quot;#artists&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Artists&lt;/h2&gt;
&lt;p&gt;To generate the proper sounds you usually need some kind of software or self-written code.
Many people have already experimented with this, with small scripts running on a PC, or directly on a microcontroller.&lt;/p&gt;
&lt;p&gt;In 2016 &lt;a href=&quot;https://oscilloscopemusic.com/watch/oscilloscope_music&quot;&gt;Jerobeam Fenderson released an album&lt;/a&gt; based on this concept.
He also collaborated with &lt;a href=&quot;https://github.com/kritzikratzi/&quot;&gt;Hansi Raber&lt;/a&gt; and they released &lt;a href=&quot;https://oscilloscopemusic.com/software/&quot;&gt;some software to play and generate this music&lt;/a&gt;.
You can also find high-quality recordings on &lt;a href=&quot;https://www.youtube.com/@jerobeamfenderson1&quot;&gt;his YouTube channel&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-video='{&quot;source&quot;: [{&quot;src&quot;:&quot;img/osci_music_cats_1.mp4&quot;, &quot;type&quot;:&quot;video/mp4&quot;}], &quot;attributes&quot;: {&quot;preload&quot;: false, &quot;playsinline&quot;: true, &quot;controls&quot;: true}}' data-lg-size=&quot;720-1280&quot; data-poster=&quot;img/osci_music_cats_1_poster.png&quot; data-sub-html=&quot;Cats listening to Jerobeam Fenderson&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_music_cats_1.mp4&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_music_cats_1_thumb.png&quot; alt=&quot;Cats listening to Jerobeam Fenderson&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.youtube.com/watch?v=5WBWIKnr0Os&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=5WBWIKnr0Os&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://img.youtube.com/vi/5WBWIKnr0Os/0.jpg&quot; alt=&quot;Reconstruct - Jerobeam Fenderson&quot; style=&quot;width:300px;&quot; data-poster=&quot;https://img.youtube.com/vi/5WBWIKnr0Os/0.jpg&quot;&gt;&lt;img src=&quot;https://www.xythobuz.de/img/video-play.png&quot; class=&quot;picthumb&quot; alt=&quot;Media play button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.youtube.com/watch?v=ywdRQ3zU6Uc&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=ywdRQ3zU6Uc&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://img.youtube.com/vi/ywdRQ3zU6Uc/0.jpg&quot; alt=&quot;Function - Jerobeam Fenderson&quot; style=&quot;width:300px;&quot; data-poster=&quot;https://img.youtube.com/vi/ywdRQ3zU6Uc/0.jpg&quot;&gt;&lt;img src=&quot;https://www.xythobuz.de/img/video-play.png&quot; class=&quot;picthumb&quot; alt=&quot;Media play button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I also found &lt;a href=&quot;https://www.youtube.com/channel/UCSb9_amN9Oh2WJhDTwnG3NA&quot;&gt;Chris Allen on YouTube&lt;/a&gt;, another artist that offers a &lt;a href=&quot;https://bit.ly/OscMusic&quot;&gt;download of the original files&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-video='{&quot;source&quot;: [{&quot;src&quot;:&quot;img/osci_music_cats_2.mp4&quot;, &quot;type&quot;:&quot;video/mp4&quot;}], &quot;attributes&quot;: {&quot;preload&quot;: false, &quot;playsinline&quot;: true, &quot;controls&quot;: true}}' data-lg-size=&quot;1280-720&quot; data-poster=&quot;img/osci_music_cats_2_poster.png&quot; data-sub-html=&quot;Cats listening to Chris Allen&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_music_cats_2.mp4&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_music_cats_2_thumb.png&quot; alt=&quot;Cats listening to Chris Allen&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.youtube.com/watch?v=FTHoDWog8qQ&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=FTHoDWog8qQ&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://img.youtube.com/vi/FTHoDWog8qQ/0.jpg&quot; alt=&quot;Moon Patrol De-Rastered - C. Allen&quot; style=&quot;width:300px;&quot; data-poster=&quot;https://img.youtube.com/vi/FTHoDWog8qQ/0.jpg&quot;&gt;&lt;img src=&quot;https://www.xythobuz.de/img/video-play.png&quot; class=&quot;picthumb&quot; alt=&quot;Media play button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.youtube.com/watch?v=_6a_nz4uRd0&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=_6a_nz4uRd0&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://img.youtube.com/vi/_6a_nz4uRd0/0.jpg&quot; alt=&quot;72 Pantera - C. Allen&quot; style=&quot;width:300px;&quot; data-poster=&quot;https://img.youtube.com/vi/_6a_nz4uRd0/0.jpg&quot;&gt;&lt;img src=&quot;https://www.xythobuz.de/img/video-play.png&quot; class=&quot;picthumb&quot; alt=&quot;Media play button&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a class=&quot;anchor al2&quot; id=&quot;playback&quot; href=&quot;#playback&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Playback&lt;/h2&gt;
&lt;p&gt;So there are some technical challenges involved in playing these properly on an oscilloscope.&lt;/p&gt;
&lt;p&gt;First, it is not enough to play these from YouTube or small MP3 files, as the compression algorithms remove too much of the visual contents of the music.
So original WAV file downloads are required.&lt;/p&gt;
&lt;p&gt;Second, most audio outputs are &lt;code&gt;AC coupled&lt;/code&gt; instead of &lt;code&gt;DC coupled&lt;/code&gt;.
What does that mean?
Audio is normally an AC signal without any DC bias.
The spekaer membrane is more or less constantly moving around it's resting position.
If there were a DC offset, this would mean the membrane has a constant offset to one side, but would then vibrate there as usual.
So the sound waves generated are the same.
That's why normally theres a capacitor in line with the audio signal, blocking the DC contents.
But when you consider our oscilloscope, if you want to draw a straight line with an offset from the center of the screen, this looks like a DC bias on the signal, which would be filtered out by the capacitor, causing the line to appear in the middle of the screen instead.
So the impact of this depends on the image shown. If it is symmetrical around the center of the screen, not much will be distorted, but in other cases the image can be completely unrecognizable.&lt;/p&gt;
&lt;p&gt;Third, the audio files have a sample rate of 192kHz.
It's usually assumed that average humans don't hear sounds above 20kHz, so according to Shannon, a sample rate of 40kHz is enough to capture all sounds interesting for humans.
But to draw many interesting shapes in a short time, higher frequencies are needed.
So you need a fast 192kHz DAC and the proper settings in your OS or player software to actually output this without downsampling happening somewhere in the chain.&lt;/p&gt;
&lt;p&gt;So how can you achieve these in practice?
Interestingly, most Apple devices have a DC coupled output.
So when using these, you already get pretty close to the intended output.&lt;/p&gt;
&lt;p&gt;We also tried a &quot;proper&quot; &lt;a href=&quot;https://www.behringer.com/product.html?modelCode=P0BK1&quot;&gt;Behringer UMC404HD&lt;/a&gt; USB audio interface from the &lt;a href=&quot;https://toolbox-bodensee.de/projekte/tonstudio/&quot;&gt;Toolbox Sound Studio&lt;/a&gt;, but the output was the worst of all devices tried.
Even using random cheap AC coupled phone or laptop DACs looked way better.&lt;/p&gt;
&lt;p&gt;But the best solution came in the form of the &lt;a href=&quot;https://www.hifiberry.com/dacs&quot;&gt;Hifiberry DACs&lt;/a&gt;.
These are DC coupled, support 192kHz and are cheap and easy to get.&lt;/p&gt;
&lt;p&gt;Now the image finally looks like in the YouTube videos.
The only problem is, this is supposed to be used and operated at Toolbox events, so it needs to be easy to use and kind of sturdy.&lt;/p&gt;
&lt;p&gt;&lt;a class=&quot;anchor al2&quot; id=&quot;oscisettings&quot; href=&quot;#oscisettings&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Oscilloscope Settings&lt;/h2&gt;
&lt;p&gt;To get the proper picture out of your oscilloscope you need to set it up correctly.
Every oscilloscope has its own kind of front panel buttons, but in general they all have more or less the same kind of settings.&lt;/p&gt;
&lt;p&gt;You need to set the voltage range of both inputs you're using, usually &lt;code&gt;CH1&lt;/code&gt; and &lt;code&gt;CH2&lt;/code&gt;, to the same value, something like &lt;code&gt;0.5V&lt;/code&gt; or &lt;code&gt;0.2V&lt;/code&gt;.
This changes the size of the image on the screen, just like changing the volume on the Raspberry Pi does, too.
My script has an option on top for the volume percentage.
You need to adjust these values so the image properly fills your screen, without drawing outside of its borders.
The output of the Pi should be put as high as possible, to reduce the influence of electrical noise.
Then set the proper range of the oscilloscope, which usually has pretty big steps, so do the final fine-adjustment with the percentage in the script.&lt;/p&gt;
&lt;p&gt;Both input channels should be set to &lt;code&gt;DC coupling&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And you need to put the scope in &lt;code&gt;XY mode&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Put the intensity as high as needed, but as low as possible, to maximize the lifetime of the phosphor coating of the tube.&lt;/p&gt;
&lt;p&gt;Adjust the focus so you have the sharpest possible image.&lt;/p&gt;
&lt;p&gt;Use the horizontal and vertical position controls to center the image on the screen.&lt;/p&gt;
&lt;p&gt;In the case of my Tektronix 2215A, the &lt;code&gt;XY&lt;/code&gt; mode hides as one of the steps of the right-most rotational input, the timebase or &lt;code&gt;A and B SEC/DIV&lt;/code&gt;.
In the picture below I marked all settings you may need to adjust.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_music_6.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_music_6.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_music_6_small.jpg&quot; alt=&quot;My Tektronix 2215A set up for Oscilloscope Music&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a class=&quot;anchor al2&quot; id=&quot;hardware&quot; href=&quot;#hardware&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Hardware&lt;/h2&gt;
&lt;p&gt;We need to consider the environments this device will be used in.
Both at the Toolbox anniversary, as well as the IBO fair, it will be displayed as part of a public show, with many people walking by and maybe stopping for a short time.
And I will not be there all the time, so it will be operated by other Toolbox members (setting it up, turning it on or off), or even the visitors (switching tracks).&lt;/p&gt;
&lt;p&gt;With the part selection outlined above, the solution was kind of obvious.
Have a script on the Pi, automatically starting playback of the music files, with a small OLED and some buttons to control playback, and ideally with some kind of UPS for ease of powering the device and properly shutting down to not corrupt the SD card.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_music_1.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_music_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_music_1_small.jpg&quot; alt=&quot;Top of Osci Music Player&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_music_3.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_music_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_music_3_small.jpg&quot; alt=&quot;Insides of Osci Music Player&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Here are the parts I used and the prices I paid at the time of building this.&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Pi
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.raspberrypi.com/products/raspberry-pi-zero-2-w/&quot;&gt;Raspberry Pi Zero 2 W&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
24.49€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
DAC
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.hifiberry.com/shop/boards/hifiberry-dac-zero/&quot;&gt;HiFiBerry DAC+ Zero&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
30.99€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Batt
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.pisugar.com/&quot;&gt;PiSugar 2&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
35.99€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
OLED
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.az-delivery.de/en/products/0-96zolldisplay&quot;&gt;SSD1306 I2C 128x64&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
4.40€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Btns
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.amazon.de/dp/B0811QKG1R&quot;&gt;2x 12mm momentary switch&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
1.60€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Conn
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.amazon.de/dp/B0B4SG1JM6&quot;&gt;2x Cinch panel mount&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
1.60€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Amp
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.amazon.de/dp/B07KQCKWF8&quot;&gt;Stereo amplifier module&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
1.40€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Spkr
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.amazon.de/dp/B08QFTYB9Z&quot;&gt;Stereo speakers&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
5.00€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Ext
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.amazon.de/dp/B06XZ2NFP1&quot;&gt;Micro-USB panel mount&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
7.69€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
BNC
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.amazon.de/dp/B08L8YBWMY&quot;&gt;Cinch to BNC adapter&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
0.50€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Pot
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&quot;https://www.amazon.de/dp/B08214YZDS&quot;&gt;Volume knob&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
0.85€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Cable
&lt;/td&gt;
&lt;td&gt;
Cinch stereo cable
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
~2.00€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Case
&lt;/td&gt;
&lt;td&gt;
Lunchbox from supermarket
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
~5.00€
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;

&lt;/td&gt;
&lt;td style=&quot; text-align: right;&quot;&gt;
Sum
&lt;/td&gt;
&lt;td style=&quot; font-family: monospace; text-align: right;&quot;&gt;
85.52€
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;Of course you also need some bits of copper wire for the internal connections inside the device.
I also used a bunch of male and female 2.54mm pin headers to be able to disconnect stuff.
Also M2.5 spacers to properly mount the PCBs around the Pi Zero, M2 spacers for the OLED, as well as a couple of cable-ties.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_music_4.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_music_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_music_4_small.jpg&quot; alt=&quot;Audio output of Osci Music Player&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/osci_music_5.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/osci_music_5.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/osci_music_5_small.jpg&quot; alt=&quot;Power input of Osci Music Player&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;For this project I decided to use a cheap plastic lunchbox as a case.
This worked relatively well.
Just take care with the lid, it was quite hard in my case, causing it to crack when I tried to drill a hole that was too big.
But some superglue saved the day.&lt;/p&gt;
&lt;p&gt;&lt;a class=&quot;anchor al2&quot; id=&quot;software&quot; href=&quot;#software&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Software&lt;/h2&gt;
&lt;p&gt;I've been using the &lt;a href=&quot;https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz&quot;&gt;Raspberry Pi OS (Legacy, 32bit) Lite&lt;/a&gt; image.
Install it &lt;a href=&quot;https://www.raspberrypi.com/software/&quot;&gt;as usual&lt;/a&gt;, set up a user account, wireless network connection and SSH login.&lt;/p&gt;
&lt;p&gt;Prepare the environment by installing all required dependencies:&lt;/p&gt;
&lt;pre class=&quot;sh_sh&quot;&gt;
sudo sh -c 'echo &quot;dtoverlay=hifiberry-dac&quot; &gt;&gt; /boot/config.txt'
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install python3 python3-pip python3-pil libjpeg-dev zlib1g-dev libfreetype6-dev liblcms2-dev libopenjp2-7 libtiff5 ffmpeg
curl &quot;http://cdn.pisugar.com/release/pisugar-power-manager.sh&quot; | sudo bash
pip install pisugar luma.oled psutil
sudo usermod -a -G spi,gpio,i2c $USER
&lt;/pre&gt;

&lt;p&gt;Reboot after the last command so the new settings take effect and the power manager can do its thing.
If in doubt, also take a look at the &lt;a href=&quot;https://github.com/PiSugar/PiSugar/wiki/PiSugar2&quot;&gt;PiSugar 2 manual&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next put the script that controls playback on the device.
Here is &lt;a href=&quot;https://codeberg.org/xythobuz/osci-music-player/raw/branch/master/osci-pi.py&quot;&gt;&lt;code&gt;~/osci-pi.py&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;sh_python&quot;&gt;
#!/usr/bin/env python3

# IP address code taken from:
# https://github.com/rm-hull/luma.examples/blob/master/examples/sys_info_extended.py
#
# ----------------------------------------------------------------------------
# Copyright (c) 2024 Thomas Buck (thomas@xythobuz.de)
# Copyright (c) 2024 Philipp Schönberger (mail@phschoen.de)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# See &amp;lt;http://www.gnu.org/licenses/&amp;gt;.
# ----------------------------------------------------------------------------

import sys
import os
import random
import subprocess
import signal
import glob
import socket
from collections import OrderedDict
import time

import pisugar
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
from luma.core.error import DeviceNotFoundError
import psutil
import RPi.GPIO as GPIO
from PIL import ImageFont

basevol = &amp;quot;70&amp;quot;
debouncems = 100

#fontfile = &amp;quot;/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf&amp;quot;
fontfile = &amp;quot;/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf&amp;quot;
fontsize = 11

LCD_REFRESH = 5.0

BTN_ARTIST = 16
BTN_NEXT = 26

currentplaying = None
lcd = None
font = None
bat = None
songlist = None
currentfile = None
lasttime = None
currvol = basevol

vol_list = [
    (&amp;quot;Harley_Matthews&amp;quot;, &amp;quot;100&amp;quot;),
    (&amp;quot;xythobuz&amp;quot;, &amp;quot;50&amp;quot;),
]

basedir = sys.argv[1]
if basedir.endswith(&amp;quot;/&amp;quot;):
    basedir = basedir.removesuffix(&amp;quot;/&amp;quot;)

def get_artist(fn):
    global currvol
    parts = fn.replace(basedir + &amp;quot;/&amp;quot;, &amp;quot;&amp;quot;).split(os.sep)
    artist = parts[0].replace(&amp;quot;_&amp;quot;, &amp;quot; &amp;quot;)
    currvol = basevol
    for i in vol_list:
        if i[0] in fn:
            currvol = i[1]
            break
    return artist

originalsongs = []
originalartists = []
for fn in glob.iglob(os.path.join(basedir, '**', '*.wav'), recursive=True):
    originalsongs.append(fn)

    artist = get_artist(fn)
    if artist not in originalartists:
        originalartists.append(artist)

artists = originalartists.copy()
random.shuffle(artists)
currentartist = artists[0]

def find_single_ipv4_address(addrs):
    for addr in addrs:
        if addr.family == socket.AddressFamily.AF_INET:  # IPv4
            return addr.address

def get_ipv4_address(interface_name=None):
    if_addrs = psutil.net_if_addrs()
    if isinstance(interface_name, str) and interface_name in if_addrs:
        addrs = if_addrs.get(interface_name)
        address = find_single_ipv4_address(addrs)
        return address if isinstance(address, str) else &amp;quot;&amp;quot;
    else:
        if_stats = psutil.net_if_stats()
        if_stats_filtered = {key: if_stats[key] for key, stat in if_stats.items() if &amp;quot;loopback&amp;quot; not in stat.flags}
        if_names_sorted = [stat[0] for stat in sorted(if_stats_filtered.items(), key=lambda x: (x[1].isup, x[1].duplex), reverse=True)]
        if_addrs_sorted = OrderedDict((key, if_addrs[key]) for key in if_names_sorted if key in if_addrs)
        for _, addrs in if_addrs_sorted.items():
            address = find_single_ipv4_address(addrs)
            if isinstance(address, str):
                return address
        return &amp;quot;&amp;quot;

def status(filename):
    try:
        with canvas(lcd) as draw:
            f = filename.replace(&amp;quot;.wav&amp;quot;, &amp;quot;&amp;quot;)
            f = f.replace(basedir + &amp;quot;/&amp;quot;, &amp;quot;&amp;quot;)
            f = f.replace(&amp;quot;/&amp;quot;, &amp;quot;\n&amp;quot;)
            f = f.replace(&amp;quot;_&amp;quot;, &amp;quot; &amp;quot;)

            f += &amp;quot;\n\n&amp;quot;
            f += &amp;quot;Bat: {:.0f}% {:.2f}V {:.2f}A&amp;quot;.format(bat.get_battery_level(), bat.get_battery_voltage(), bat.get_battery_current())

            ip = get_ipv4_address()
            if len(ip) &amp;gt; 0:
                f += &amp;quot;\n&amp;quot;
                f += &amp;quot;IP: %s&amp;quot; % (ip)

            with open(&amp;quot;/proc/asound/card0/pcm0p/sub0/hw_params&amp;quot;, &amp;quot;r&amp;quot;) as rf:
                for line in rf:
                    if line.startswith(&amp;quot;rate:&amp;quot;):
                        rate = int(line.split(&amp;quot; &amp;quot;)[1])

                        f += &amp;quot;\n&amp;quot;
                        f += &amp;quot;Rate: {:.0f}kHz&amp;quot;.format(rate / 1000)

            draw.multiline_text((0, 0), f, font=font, fill=&amp;quot;white&amp;quot;, spacing=-1)
    except Exception as e:
        pass

def stop():
    global currentplaying

    if running():
        try:
            print(&amp;quot;Stopping running player&amp;quot;)
            os.kill(currentplaying.pid, signal.SIGINT)
            if not currentplaying.poll():
                currentplaying = None
            else:
                print(&amp;quot;Error stopping player&amp;quot;)
        except ProcessLookupError as e:
            currentplaying = None
    else:
        currentplaying = None

def play(filename):
    global currentplaying
    global lcd
    global basedir
    global bat
    global currentfile

    stop()

    print('Now playing &amp;quot;' + filename + '&amp;quot;')
    currentfile = filename
    status(currentfile)

    print(&amp;quot;volume %s &amp;quot; %  currvol)
    currentplaying = subprocess.Popen([&amp;quot;ffplay&amp;quot;, &amp;quot;-hide_banner&amp;quot;, &amp;quot;-nostats&amp;quot;, &amp;quot;-nodisp&amp;quot;, &amp;quot;-autoexit&amp;quot;, &amp;quot;-volume&amp;quot;, currvol, filename])

def running():
    global currentplaying

    if currentplaying != None:
        if currentplaying.poll() == None:
            return True
    return False

def playlist():
    global songlist
    global lasttime

    if not running():
        while True:
            if (songlist == None) or (len(songlist) &amp;lt;= 0):
                switch_artist()
                songlist = originalsongs.copy()
                random.shuffle(songlist)

            song = songlist.pop()
            artist = get_artist(song)
            if artist == currentartist:
                play(song)
                lasttime = time.time()
                break
    else:
        if (time.time() - lasttime) &amp;gt;= LCD_REFRESH:
            status(currentfile)
            lasttime = time.time()

def switch_artist():
    global artists
    global currentartist

    if len(artists) &amp;lt;= 0:
        artists = originalartists.copy()
        random.shuffle(artists)

    currentartist = artists.pop()

    switch_track()

def switch_track():
    stop()

def button(ch):
    val = not GPIO.input(ch)

    #name = &amp;quot;Unknown&amp;quot;
    #if ch == BTN_ARTIST:
    #    name = &amp;quot;BTN_ARTIST&amp;quot;
    #elif ch == BTN_NEXT:
    #    name = &amp;quot;BTN_NEXT&amp;quot;
    #print(name + &amp;quot; is now &amp;quot; + str(val))

    if val:
        if ch == BTN_ARTIST:
            switch_artist()
        elif ch == BTN_NEXT:
            switch_track()

def main():
    global lcd
    global font
    global bat
    global currentfile

    if len(sys.argv) &amp;lt;= 1:
        print(&amp;quot;Usage:&amp;quot;)
        print(&amp;quot;\t&amp;quot; + sys.argv[0] + &amp;quot; PATH&amp;quot;)
        sys.exit(1)

    os.system(&amp;quot;killall ffplay&amp;quot;)

    GPIO.setmode(GPIO.BCM)
    for b in [ BTN_ARTIST, BTN_NEXT ]:
        GPIO.setup(b, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        GPIO.add_event_detect(b, GPIO.BOTH, callback=button, bouncetime=debouncems)

    try:
        bus = i2c(port=1, address=0x3C)
        lcd = ssd1306(bus)
        font = ImageFont.truetype(fontfile, fontsize)
    except DeviceNotFoundError as E:
        print(&amp;quot;No LCD connected&amp;quot;)
        lcd = None

    conn, event_conn = pisugar.connect_tcp()
    bat = pisugar.PiSugarServer(conn, event_conn)
    print(bat.get_model() + &amp;quot; &amp;quot; + bat.get_version())
    print(str(bat.get_battery_level()) + &amp;quot;% &amp;quot; + str(bat.get_battery_voltage()) + &amp;quot;V &amp;quot; + str(bat.get_battery_current()) + &amp;quot;A&amp;quot;)
    print(&amp;quot;Plug=&amp;quot; + str(bat.get_battery_power_plugged()) + &amp;quot; Charge=&amp;quot; + str(bat.get_battery_charging()))

    try:
        while True:
            playlist()
            time.sleep(0.05)
    except KeyboardInterrupt:
        print(&amp;quot;Bye&amp;quot;)
        GPIO.cleanup()
        sys.exit(0)

if __name__ == &amp;quot;__main__&amp;quot;:
    main()
&lt;/pre&gt;

&lt;p&gt;And you'll also need &lt;a href=&quot;https://codeberg.org/xythobuz/osci-music-player/raw/branch/master/osci.service&quot;&gt;&lt;code&gt;/etc/systemd/system/osci.service&lt;/code&gt;&lt;/a&gt; to start the script automatically.
Adjust the username and path accordingly.&lt;/p&gt;
&lt;pre class=&quot;sh_desktop&quot;&gt;
[Unit]
Description=Oscilloscope Music Player
After=multi-user.target

[Service]
Type=idle
User=thomas
ExecStart=/home/thomas/osci-music-player/osci-pi.py /home/thomas/music
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;ExecStart&lt;/code&gt; line has the parameters that are passed to the Python script.
This is the path of the directory that contains all the &lt;code&gt;.wav&lt;/code&gt; files for the songs.
In this top-level folder, I called it &lt;code&gt;~/music&lt;/code&gt;, put another level of directories named after the artist, then place the files in there.&lt;/p&gt;
&lt;p&gt;Finally enable and start the new unit.&lt;/p&gt;
&lt;pre class=&quot;sh_sh&quot;&gt;
sudo systemctl daemon-reload
sudo systemctl enable --now osci.service
&lt;/pre&gt;

&lt;p&gt;The device should now start playing the music as soon as the power is turned on.
Before switching the device off, use the on-board button of the PiSugar to properly shutdown the OS, and only then move the power switch to the &lt;code&gt;Off&lt;/code&gt; position.&lt;/p&gt;
&lt;p&gt;You can also check out &lt;a href=&quot;https://codeberg.org/xythobuz/osci-music-player&quot;&gt;the code in its Git repository&lt;/a&gt;.&lt;/p&gt;</description>
    <pubDate>Sun, 11 Feb 2024 12:00:00 -0000</pubDate>
    <atom:updated>2024-03-08T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/osci_music_player.html</guid>
</item>

<item>
    <title>RGB LED Matrix visualizer</title>
    <link>https://www.xythobuz.de/ledmatrix_v2.html</link>
    <description>&lt;p&gt;I initially bought a 32x32 RGB LED matrix in 2018.
Back then the idea was to fit it onto a bag, as some kind of wearable device, for 35C3.
But it never really worked out.
So in preparation for CCCamp23 I noticed the LED panel again and decided to order some more of them.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/toolbox10_2.jpg&quot; data-lg-size=&quot;1600-1200&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/toolbox10_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/toolbox10_2_small.jpg&quot; alt=&quot;LED Matrix at Toolbox anniversary. © 2024 Falko.&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;These are the &lt;a href=&quot;https://shop.pimoroni.com/products/rgb-led-matrix-panel?variant=35962488650&quot;&gt;32x32 4mm pitch RGB LED Matrix Panels from Pimoroni&lt;/a&gt;, with either the &lt;a href=&quot;https://shop.pimoroni.com/products/interstate-75-w?variant=40453881299027&quot;&gt;Interstate 75 W&lt;/a&gt; or the &lt;a href=&quot;https://shop.pimoroni.com/products/adafruit-rgb-matrix-bonnet-for-raspberry-pi?variant=2257849155594&quot;&gt;Adafruit RGB Matrix Bonnet&lt;/a&gt; to run them with a RP2040 or a Raspberry Pi, respectively.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_visual_1.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_visual_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_visual_1_small.jpg&quot; alt=&quot;Tetris (paused)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/cccamp23_matrix_1.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/cccamp23_matrix_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/cccamp23_matrix_1_small.jpg&quot; alt=&quot;Someone tries to beat Breakout&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_3d_1.png&quot; data-lg-size=&quot;690-680&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_3d_1.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_3d_1_small.png&quot; alt=&quot;3D design for panel mounts&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Everything runs from a single Python &lt;a href=&quot;https://codeberg.org/xythobuz/rgb-matrix-visualizer&quot;&gt;codebase&lt;/a&gt;, either simulated in a GUI window on a development PC, on the Raspbian Python interpreter or directly on the Pico MicroPython environment.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_gui_1.png&quot; data-lg-size=&quot;519-543&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_gui_1.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_gui_1_small.png&quot; alt=&quot;Screenshot of weather widget&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_portable_1.jpg&quot; data-lg-size=&quot;744-744&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_portable_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_portable_1_small.jpg&quot; alt=&quot;Portable matrix showing a QR code&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_portable_2.jpg&quot; data-lg-size=&quot;754-860&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_portable_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_portable_2_small.jpg&quot; alt=&quot;Portable matrix showing battery state&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;For portability I'm simply using a voltage regulator to connect a 4S LiPo battery.
I made one of them from recycled single-use vape batteries, but I also used some of my RC-model batteries.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_portable_3.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_portable_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_portable_3_small.jpg&quot; alt=&quot;Recycled vape batteries&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_portable_4.jpg&quot; data-lg-size=&quot;1033-734&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_portable_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_portable_4_small.jpg&quot; alt=&quot;Back side of portable matrix&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_portable_5.jpg&quot; data-lg-size=&quot;582-570&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_portable_5.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_portable_5_small.jpg&quot; alt=&quot;Pico without voltage regulator&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;On the software side I wrote some code to show static images, GIF animations, scroll text across the matrix, etc.
I also implemented Snake, Tetris and Breakout, as well as some other utilities, like OTA updating for the Pico, a Telegram bot integration, a weather widget and a service checking if a device is reachable on the network.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_visual_2.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_visual_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_visual_2_small.jpg&quot; alt=&quot;Tetris&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_visual_3.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_visual_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_visual_3_small.jpg&quot; alt=&quot;Game of Life&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_visual_4.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_visual_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_visual_4_small.jpg&quot; alt=&quot;Aphex Twin logo&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Right now the 64x64 panel is placed next to the TV in my livingroom, randomly cycling through some animations and games.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_visual_5.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_visual_5.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_visual_5_small.jpg&quot; alt=&quot;Animated globe&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_visual_8.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_visual_8.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_visual_8_small.jpg&quot; alt=&quot;Animated Sephiroth&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_visual_9.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_visual_9.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_visual_9_small.jpg&quot; alt=&quot;Animated Cloud&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I wasn't really able to get breakout into a playable state before CCCamp23.
But luckily I also got a patch set (&lt;a href=&quot;https://codeberg.org/xythobuz/rgb-matrix-visualizer/commit/8e257111464a90a983bd2bc4f6092c12ebf08374&quot;&gt;1&lt;/a&gt;, &lt;a href=&quot;https://codeberg.org/xythobuz/rgb-matrix-visualizer/commit/8ff126684afb7dfa48a2d7060e390e8233045bb9&quot;&gt;2&lt;/a&gt;, &lt;a href=&quot;https://codeberg.org/xythobuz/rgb-matrix-visualizer/commit/43d0b92700f2c2a8adf915bb5a020476409e8e08&quot;&gt;3&lt;/a&gt;, &lt;a href=&quot;https://codeberg.org/xythobuz/rgb-matrix-visualizer/commit/2399961348fefa849d1892cdf5b5001265a28ffd&quot;&gt;4&lt;/a&gt;) from Jannis that fixed the collision behaviour of the ball in breakout!
We met on the camp and had a very nice time together.
And soon afterwards the patches appeared in my inbox and actually made the game playable!
This was really awesome 😊💪
Thanks again!&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/cccamp23_matrix_2.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/cccamp23_matrix_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/cccamp23_matrix_2_small.jpg&quot; alt=&quot;Another player already scored 80 points&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_build_1.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_build_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_build_1_small.jpg&quot; alt=&quot;64x64 panel in my livingroom&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_build_5.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_build_5.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_build_5_small.jpg&quot; alt=&quot;Backside of 64x64 panel&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Here are my notes for installing everything on a Raspbian OS.&lt;/p&gt;
&lt;pre class=&quot;sh_sh&quot;&gt;
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install python3-pip git vim htop
git clone https://github.com/adafruit/Raspberry-Pi-Installer-Scripts
cd Raspberry-Pi-Installer-Scripts
sudo ./rgb-matrix.sh
# Answer y, Bonnet, Quality (and solder the mentioned link on the board)
cd ..

git clone https://codeberg.org/xythobuz/rgb-matrix-visualizer
sudo pip3 install Pillow bdfparser &amp;quot;qrcode[pil]&amp;quot; evdev

cd Raspberry-Pi-Installer-Scripts/rpi-rgb-led-matrix/bindings/python
pip3 wheel --no-deps -w dist .
sudo pip3 install dist/rgbmatrix-0.0.1-cp39-cp39-linux_armv7l.whl

sudo apt-get install libopenjp2-7

# append isolcpus=3 to /boot/cmdline.txt
cat &amp;amp;lt;&amp;amp;lt;EOF | sudo tee /etc/modprobe.d/blacklist-rgb-matrix.conf
blacklist snd_bcm2835
EOF

sudo update-initramfs -u
sudo reboot

# test python bindings work
cd rgb-matrix-visualizer
sudo ./pi.py

sudo sh -c 'echo enable_uart=1 &amp;amp;gt;&amp;amp;gt; /boot/config.txt'
&lt;/pre&gt;

&lt;p&gt;Although I'm currently still having some problems getting the &lt;code&gt;wetterdienst&lt;/code&gt; dependency running on the Raspberry Pi.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_build_2.jpg&quot; data-lg-size=&quot;879-1110&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_build_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_build_2_small.jpg&quot; alt=&quot;Two small panels and materials for the large matrix&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_build_3.jpg&quot; data-lg-size=&quot;1242-431&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_build_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_build_3_small.jpg&quot; alt=&quot;Game of Life on four matrices&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_3d_3.png&quot; data-lg-size=&quot;845-530&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_3d_3.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_3d_3_small.png&quot; alt=&quot;96x32 frame design&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I also want to give a shout-out to &lt;a href=&quot;https://shop.pimoroni.com/&quot;&gt;Pimoroni&lt;/a&gt;.
I managed to rip-off the power connector on one of the panels.
They sent a replacement without any additional cost, but I took the chance and ordered some more spare panels.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_build_4.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_build_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_build_4_small.jpg&quot; alt=&quot;Power connector ripped-off from PCB&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The modules bought this year have pretty consistent color reproduction.
But compared to the one module I bought five years earlier, there is a very noticable difference.
On these pictures both modules show the same RGB values.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_colors_1.jpg&quot; data-lg-size=&quot;897-422&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_colors_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_colors_1_small.jpg&quot; alt=&quot;Color reproduction issue (1)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_colors_2.jpg&quot; data-lg-size=&quot;602-247&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_colors_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_colors_2_small.jpg&quot; alt=&quot;Color reproduction issue (2)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I added some simple compensation to mostly adjust for this issue.&lt;/p&gt;
&lt;pre class=&quot;sh_python&quot;&gt;
# For some reason the red and green LEDs on older Pimoroni panels
# are far brighter than on newer panels.
# Adjust this by multiplying rg channels with 0.75 and b channel
# with 0.85, depending on hard-corded coordinate ranges.
class MapperColorAdjust(MapperNull):
    def set_pixel(self, x, y, color):
        # second panel from the left, with 32 &amp;lt;= x,
        # is &amp;quot;old&amp;quot; type with brighter LEDs.
        # rest of panels to the left are less bright.
        # so adjust brightness of other panel channels down.
        if x &amp;gt;= self.gui.panelW:
            color = (int(color[0] * 0.75), int(color[1] * 0.75), color[2] * 0.85)

        self.gui.set_pixel(x, y, color)
&lt;/pre&gt;

&lt;p&gt;I also planned to build a large LED matrix based on WS2812 LED strips.
They can be &lt;a href=&quot;https://iosoft.blog/2020/09/29/raspberry-pi-multi-channel-ws2812/&quot;&gt;driven easily with a Raspberry Pi&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_large_5.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_large_5.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_large_5_small.jpg&quot; alt=&quot;Level shifter for WS2812 strips&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_large_3.jpg&quot; data-lg-size=&quot;1280-960&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_large_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_large_3_small.jpg&quot; alt=&quot;Large matrix frame and strips&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I was even able to get a basic frame finished before CCCamp23.
But that's where time ran out.&lt;/p&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_large_1.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_large_1.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_large_1_small.jpg&quot; alt=&quot;Frame for large matrix, back side&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_large_2.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_large_2.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_large_2_small.jpg&quot; alt=&quot;Frame for large matrix, front side&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_3d_2.png&quot; data-lg-size=&quot;473-464&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_3d_2.png&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_3d_2_small.png&quot; alt=&quot;Corner piece for large 32x32 matrix&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I haven't yet given up that plan completely, but I have to think of a solution for diffusing the light first.
The LEDs are too small and spaced too far apart to look good in this configuration.&lt;/p&gt;
&lt;p&gt;&lt;a class=&quot;anchor al2&quot; id=&quot;more_pictures&quot; href=&quot;#more_pictures&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;More Pictures&lt;/h2&gt;
&lt;div class=&quot;collapse&quot;&gt;Some more photographs I didn't use above.&lt;/div&gt;

&lt;div class=&quot;collapsecontent&quot;&gt;
&lt;div class=&quot;lightgallery_new&quot;&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_visual_7.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_visual_7.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_visual_7_small.jpg&quot; alt=&quot;Animated Nintendo 64 logo&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_visual_6.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_visual_6.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_visual_6_small.jpg&quot; alt=&quot;Empty matrix&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_large_4.jpg&quot; data-lg-size=&quot;960-1280&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_large_4.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_large_4_small.jpg&quot; alt=&quot;LED strips for large matrix&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;border&quot; data-src=&quot;https://www.xythobuz.de/img/led_matrix_colors_3.jpg&quot; data-lg-size=&quot;888-377&quot;&gt;&lt;a href=&quot;https://www.xythobuz.de/img/led_matrix_colors_3.jpg&quot;&gt;&lt;img class=&quot;pic&quot; src=&quot;https://www.xythobuz.de/img/led_matrix_colors_3_small.jpg&quot; alt=&quot;Color reproduction issue (3)&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
    <pubDate>Tue, 05 Sep 2023 12:00:00 -0000</pubDate>
    <atom:updated>2024-03-08T12:00:00Z</atom:updated>
    <guid>https://www.xythobuz.de/ledmatrix_v2.html</guid>
</item>

</channel>
</rss>
