<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.1">Jekyll</generator><link href="https://there.oughta.be/feed.xml" rel="self" type="application/atom+xml" /><link href="https://there.oughta.be/" rel="alternate" type="text/html" /><updated>2025-10-02T13:29:14+02:00</updated><id>https://there.oughta.be/feed.xml</id><title type="html">there.oughta.be/</title><subtitle>There oughta be a blog with awesome projects. A collection of overcomplicated devices nobody needs. A sanctum for all the things that did not get living room clearance, but oughta be seen by fellow nerds.</subtitle><entry><title type="html">There oughta be a Lego Game Boy</title><link href="https://there.oughta.be/a/lego-game-boy" rel="alternate" type="text/html" title="There oughta be a Lego Game Boy" /><published>2025-10-02T00:00:00+02:00</published><updated>2025-10-02T00:00:00+02:00</updated><id>https://there.oughta.be/a/lego-game-boy</id><content type="html" xml:base="https://there.oughta.be/a/lego-game-boy"><![CDATA[<p>UPDATE A FEW HOURS LATER: Turns out that <a href="https://nataliethenerd.com/" target="_blank" class="affiliate">Natalie the Nerd</a> beat me to it by a few hours. But she did not just beat me in terms of releasing a bit earlier, but also in <a href="https://blog.nataliethenerd.com/i-turned-the-lego-game-boy-into-a-working-game-boy-part-1/" target="_blank" class="affiliate">functionality, style and execution</a>. I fully admit defeat in this undeclared race that every nerd with a public channel just seems to secretly have competed in. I will be forming a “Natalie beat us” support group with all the other just-a-little-too-late-and-not-quite-as-good projects that will be popping up over the next weeks :)</p>

<p>Well, ok, there actually <strong>is</strong> a Lego Game Boy and it has just been released <del>today</del> yesterday. What there actually oughta be is a real display for it, which this blog post is about.</p>

<figure class="youtube">
<a href="https://youtu.be/-tp15pGTP_Y" target="_blank">
<img src="/assets/resized/images/2025-10-02/1280/youtube.jpg" style="max-width: min(1921px, 100%)" alt="Thumbnail of the youtube video: The thumbnail shows a close-up of the display part of the Lego Game Boy where you can see a real display showing a scene from the game Tetris." srcset="    /assets/resized/images/2025-10-02/640/youtube.jpg 640w,    /assets/resized/images/2025-10-02/768/youtube.jpg 768w,    /assets/resized/images/2025-10-02/1024/youtube.jpg 1024w,    /assets/resized/images/2025-10-02/1280/youtube.jpg 1280w, /assets/images/2025-10-02/youtube.jpg 1921w" sizes="(max-width: 60rem) 100vw, 60rem" />
</a>
<figcaption>Click the image to see the video on youtube.com.</figcaption>
</figure>

<p>If you just want to see it in action, check out the Youtube video. If you want to add a display to your own Lego Game Boy, read on for all the details on how to do this.</p>

<!--more-->

<p><em>Please note that this article contains affiliate links, i.e. links that tell the target site that I sent you there and that earn me a share of their revenue in return, i.e. as an Amazon Associate I earn from qualifying purchases. In contrast to <a href="https://youtube.com/c/thereoughtabe" target="_blank">regular external links</a>, such affiliate links are marked with a <a href="https://amzn.to/3KOkn37" target="_blank" class="affiliate">dollar sign</a> instead of a box.</em></p>

<h2 id="what-and-why">What and why?</h2>

<p>Ok, I probably have to put a short disclaimer in front of this post. No, this is not as fancy and elaborate as many of my other posts. I literally just researched a screen of the right size, looked for a proper open source emulator and put everything together. The biggest contribution from my side might actually be a simple 3D printed adapter. But it worked out so nicely, I just couldn’t resist making a video about it.<sup id="fnref:affiliate" role="doc-noteref"><a href="#fn:affiliate" class="footnote" rel="footnote">1</a></sup></p>

<p>What am I even talking about? Well, about <a href="https://amzn.to/3KOkn37" target="_blank" class="affiliate">Lego’s new Game Boy model (72046)</a>. They released it today, but when I saw its announcement a while ago I knew I had to pre-order it. I also already had a place in mind where I’d put the model on display as it would look really nice next to my <a href="/a/wooden-game-boy">Wooden Game Boy</a>. But while I was patiently waiting for its release I realized that there is an important difference between my wooden Game Boy and the Lego Game Boy: The wooden one has a modern IPS backlit display, while the Lego Game Boy does not have an actual display at all.</p>

<figure>

<img src="/assets/resized/images/2025-10-02/1280/comparison.jpg" style="max-width: min(3000px, 100%)" alt="Photo of three Game Boys next to each other. From left to right: The original Game Boy, the Lego Game Boy and the wooden Game Boy." srcset="    /assets/resized/images/2025-10-02/640/comparison.jpg 640w,    /assets/resized/images/2025-10-02/768/comparison.jpg 768w,    /assets/resized/images/2025-10-02/1024/comparison.jpg 1024w,    /assets/resized/images/2025-10-02/1280/comparison.jpg 1280w, /assets/images/2025-10-02/comparison.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>While the Lego Game Boy's fantastic lenticular display looks much more like the original it just looks weak next to the modern display in my wooden Game Boy.</figcaption>
</figure>

<p>Don’t get me wrong. Lego did not just put a void into the display area of their model. On the contrary: They created linticular displays that look very similar to the original Game Boy and which even simulate a moving image. These are fantastic and such a clever solution!</p>

<p>These lanticular displays are much closer to the original Game Boy than what my wooden Game Boy does and my modification to the Lego model will actually bring it farther away from the original. But the thing is that my wooden Game Boy will literally outshine the Lego one and I want to balance the looks on the shelf by adding a real display.</p>

<h2 id="what-you-need">What you need</h2>

<p>So, I researched ahead and found that the display of the Lego model is a 6 by 6 window<sup id="fnref:windowsize" role="doc-noteref"><a href="#fn:windowsize" class="footnote" rel="footnote">2</a></sup>, which my sons had handily available in their huge Lego stash. With that as a reference I did some research and ended up with a fitting display and a small microcontroller to run it. Here is the parts list, which is really short if you already have common maker tools at hand. In that case, adding a display to your Lego Game Boy can be done for about 25€.</p>

<p>What you need:</p>
<ul>
  <li><a href="https://amzn.to/3KOkn37" target="_blank" class="affiliate">Lego Game Boy (72046)</a>, 60€</li>
  <li><a href="https://amzn.to/486ajwh" target="_blank" class="affiliate">Seengreat 2inch LCD Display Module</a>, 13€</li>
  <li><a href="https://amzn.to/3WjeFZC" target="_blank" class="affiliate">Waveshare rp2350-zero</a>, 12€</li>
  <li>3D printer -or- cardboard and a box cutter</li>
  <li>A soldering iron and its typical consumables (solder, wires, etc.)</li>
</ul>

<figure>

<img src="/assets/resized/images/2025-10-02/1280/components.jpg" style="max-width: min(3000px, 100%)" alt="Photo of two components. On the left there is a small rp2350-zero microcontroller, which has a USB-C port and an overall size of roughly a thumb. On the right is the backside of the LCD display with several pin header contacts and markings giving the manufacturer Seengreat, the display's resolution of 320x240 pixels, the ST77889V controler and several additional labels.." srcset="    /assets/resized/images/2025-10-02/640/components.jpg 640w,    /assets/resized/images/2025-10-02/768/components.jpg 768w,    /assets/resized/images/2025-10-02/1024/components.jpg 1024w,    /assets/resized/images/2025-10-02/1280/components.jpg 1280w, /assets/images/2025-10-02/components.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>Left: The rp2350-zero has a USB-C port and is only slightly larger than a thumbnail. Right: Backside of the display (yeah, I bent a pin before taking the photo).</figcaption>
</figure>

<p>Note, that I am not familiar with Seengreat and I have no idea if you can find the same display under different brand names. The important part is that it is a 320x240 pixels display with an ST7789V SPI controller. Of course the measures also need to be the same (careful when comparing: The distance between the screw threads is wrong in the amazon listing, but that does not matter for this project anyway).</p>

<p>As mentioned in the listing, I am quite sure that you do not need a 3D printer and can just cut some cardboard. If you have little experience with soldering stuff you might want to ask a friend, though, as I do not see a good way around this.</p>

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

<p>Well, the Lego Game Boy comes with assembly instructions, so let’s assume you already have a fully assembled Lego Game Boy at hand :)</p>

<p>The display has eight pins that you need to connect to our microcontroller. If for some reason you want to connect it differently than I did, you really just need to make sure to connect VCC to the 3V3 pin and GND to GND. All other pins from the display just have to be connected to any GPIO pin on the rp2350. You just need to set up the correct pin mapping in the software later. However, if you want to do exactly what I did, here is the pin mapping I used:</p>

<ul>
  <li>VCC - 3V3</li>
  <li>GND - GND</li>
  <li>DIN - GPIO 9</li>
  <li>SCK - GPIO 10</li>
  <li>CS - GPIO 11</li>
  <li>RST - GPIO 12</li>
  <li>D/C - GPIO 13</li>
  <li>BL - GPIO 14</li>
</ul>

<p>As you can tell from the photos, this mapping allows mostly reusing the pin header that is already soldered onto the display. I directly soldered most of the pins to this header and hooked up the remaining ones with short pieces of wire.</p>

<figure>

<img src="/assets/resized/images/2025-10-02/1024/pins.jpg" style="max-width: min(1186px, 50%)" alt="Photo of how the microcontroller is placed on the display." srcset="    /assets/resized/images/2025-10-02/640/pins.jpg 640w,    /assets/resized/images/2025-10-02/768/pins.jpg 768w,    /assets/resized/images/2025-10-02/1024/pins.jpg 1024w, /assets/images/2025-10-02/pins.jpg 1186w" sizes="(max-width: 60rem) calc(50/100 * 100vw), calc(50/100 * 60rem)" />

<figcaption>I simply bent the existing pins upwards to stick the rp2350-zero on top of them, making it easy to solder 6 out of 8 pins without any need for cables.</figcaption>
</figure>

<figure>

<img src="/assets/resized/images/2025-10-02/1024/soldering.jpg" style="max-width: min(1178px, 50%)" alt="Photo of how the microcontroller is soldered on the display." srcset="    /assets/resized/images/2025-10-02/640/soldering.jpg 640w,    /assets/resized/images/2025-10-02/768/soldering.jpg 768w,    /assets/resized/images/2025-10-02/1024/soldering.jpg 1024w, /assets/images/2025-10-02/soldering.jpg 1178w" sizes="(max-width: 60rem) calc(50/100 * 100vw), calc(50/100 * 60rem)" />

<figcaption>Only GND and VCC need a cable to get to the correct pin on the micro controller.</figcaption>
</figure>

<p>At this point consider to do the software part in the next section first before putting the display into the Game Boy while you can still easily reach the microcontroller. However, it is also not too tricky while it is already in there and I like to keep hardware and software instructions separated, so…</p>

<figure>

<img src="/assets/resized/images/2025-10-02/1280/frame.jpg" style="max-width: min(2500px, 100%)" alt="Three photos arranged as a mathematical formula. Photo 1 plus photo 2 equals photo 3. The first photo shows the display, the seconds a 3D printed frame for the display, the third photo shows the display inserted to the frame." srcset="    /assets/resized/images/2025-10-02/640/frame.jpg 640w,    /assets/resized/images/2025-10-02/768/frame.jpg 768w,    /assets/resized/images/2025-10-02/1024/frame.jpg 1024w,    /assets/resized/images/2025-10-02/1280/frame.jpg 1280w, /assets/images/2025-10-02/frame.jpg 2500w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>From left to right: Front side of the display, 3D printed frame, display placed inside the frame.</figcaption>
</figure>

<p>The next thing you need is the simple adapter to hold the display in the center of the Lego window. If you have a 3D printer, simply grab the model from (<a href="https://www.thingiverse.com/thing:7160058" target="_blank">thingiverse</a> / <a href="https://www.printables.com/model/1430909-lego-game-boy-72046-display-adapter-frame" target="_blank">printables</a> / <a href="https://makerworld.com/de/models/1847978-display-adapter-frame-for-game-boy-set-72046" target="_blank">makerworld</a> - check back in a few minutes) and print it. The display should fit neatly into the center with the flat cable at one side fitting into the gap. Then just put the entire assembly into the Game Boy’s window and you are done. If you do not have a 3D printer, get some thick black cardboard<sup id="fnref:cardboard" role="doc-noteref"><a href="#fn:cardboard" class="footnote" rel="footnote">3</a></sup> and a box cutter to recreate the same shape.</p>

<figure>

<img src="/assets/resized/images/2025-10-02/1280/power-port.jpg" style="max-width: min(2707px, 50%)" alt="Photo of the Lego Game Boy with parts removed to make room for the new display." srcset="    /assets/resized/images/2025-10-02/640/power-port.jpg 640w,    /assets/resized/images/2025-10-02/768/power-port.jpg 768w,    /assets/resized/images/2025-10-02/1024/power-port.jpg 1024w,    /assets/resized/images/2025-10-02/1280/power-port.jpg 1280w, /assets/images/2025-10-02/power-port.jpg 2707w" sizes="(max-width: 60rem) calc(50/100 * 100vw), calc(50/100 * 60rem)" />

<figcaption>You only need to remove the display holder from the original Lego model and find a way to run the cable to the outside. I decided to use the actual AC adapter port from the original Game Boy.</figcaption>
</figure>

<figure>

<img src="/assets/resized/images/2025-10-02/1280/usb.jpg" style="max-width: min(2327px, 50%)" alt="Photo of how I run the USB cable inside the Game Boy." srcset="    /assets/resized/images/2025-10-02/640/usb.jpg 640w,    /assets/resized/images/2025-10-02/768/usb.jpg 768w,    /assets/resized/images/2025-10-02/1024/usb.jpg 1024w,    /assets/resized/images/2025-10-02/1280/usb.jpg 1280w, /assets/images/2025-10-02/usb.jpg 2327w" sizes="(max-width: 60rem) calc(50/100 * 100vw), calc(50/100 * 60rem)" />

<figcaption>There is still plenty of room in the Lego model, however, you may have trouble with USB-C cables with longer plugs.</figcaption>
</figure>

<p>Finally, you just have to attach a USB cable and run it out from your Game Boy. Either do it like I am showing in the photo or remove some bricks if you prefer to run it differently. This may depend on how you want to show the model later and which sides or angles will be visible to the viewer.</p>

<h2 id="getting-actual-games-to-run-on-it">Getting actual games to run on it</h2>

<p>Now, here is the good news. This is not just a trick to run some videos on the display. We are going to run an actual emulator with original games on it! The bad news is that, obviously, I cannot just share ROMs of Nintendo’s games here, so you need to obtain a ROM file from your own games first (search the web).</p>

<p>The emulator that we will run on our rp2350 is “Pico-GB”. Originally, this was <a href="https://github.com/deltabeard/Peanut-GB" target="_blank">Peanut-GB</a> by deltabeard, who already ported it as <a href="https://github.com/deltabeard/RP2040-GB" target="_blank">RP2040-GB</a>, which was forked by YouMakeTech as <a href="https://github.com/YouMakeTech/Pico-GB" target="_blank">Pico-GB</a> with several improvements and which was then again forked by <a href="https://github.com/tobigun/Pico-GB" target="_blank">tobigun</a> with even more improvements, most notably support for many more displays including ours.</p>

<p>Now, before you get confused by all these forks, just get the files from <a href="https://github.com/Staacks/Pico-GB" target="_blank">my fork</a>, which has the configuration files already set to match our display and our needs. It also disables sets inputs to GPIO 0 (i.e. out of the way) and selects a grayscale color palette.</p>

<p>Just clone or download the entire repository (if you are unfamiliar with github, you can download a zip with the green button in the top right corner).</p>

<p>In theory, now you already have everything you need, but you have to get your ROM in there, so it will be compiled into the firmware <sup id="fnref:sdcard" role="doc-noteref"><a href="#fn:sdcard" class="footnote" rel="footnote">4</a></sup>. Unfortunately, you cannot just use the binary ROM file that you have, but instead it needs to be converted into a C header file. On Linux you can simply run <code class="language-plaintext highlighter-rouge">xxd -i your-ROM.gb &gt; game_bin.h</code>, but on less abled operating systems you can use an <a href="https://notisrac.github.io/FileToCArray/" target="_blank">online tool</a>. Upload your ROM, press convert and save the file. Then place the file as <code class="language-plaintext highlighter-rouge">game_bin.h</code> in the <code class="language-plaintext highlighter-rouge">src</code> folder of the repository you just downloaded. Open the file with any text editor and make sure that the first line looks like this:</p>

<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">GAME_DATA</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
  <span class="mi">0</span><span class="n">x</span><span class="p">..,</span> <span class="mi">0</span><span class="n">x</span><span class="p">..,</span> <span class="mi">0</span><span class="n">x</span><span class="p">..,</span> <span class="p">....</span>
  <span class="mi">0</span><span class="n">x</span><span class="p">..,</span> <span class="mi">0</span><span class="n">x</span><span class="p">..,</span> <span class="mi">0</span><span class="n">x</span><span class="p">..,</span> <span class="p">....</span>
  <span class="p">...</span>
<span class="p">}</span></code></pre></figure>

<p>You can ignore or remove any lines starting with <code class="language-plaintext highlighter-rouge">//</code> that the conversion tool added. The important part is that the line with <code class="language-plaintext highlighter-rouge">GAME_DATA[] = </code> comes directly before the big block of 0x..-entries.</p>

<p>Now it is time to compile and flash the code. This is a PlatformIO project, so you would open VSCode and load the project with PlatformIO. I would give step by step instructions on how to install VSCode and PlatformIO, but I am quite sure that things will be different for non-Linux readers, so please refer to the web if you are stuck as there should be plenty of tutorials for this. You basically only have to install VSCode from Microsoft and then use the Add-On-Store in VSCode to install PlatformIO as a plugin. You then have the alien head or insect head or whatever the icon of PlatformIO is at the left hand side of VSCode and can use that to open the folder that you downloaded from github (not <code class="language-plaintext highlighter-rouge">src</code>, but the one containing <code class="language-plaintext highlighter-rouge">src</code>). Press the tiny checkmark sign at the bottom to compile, plug in your rp2350 and press the arrow next to the checkmark to upload it to the rp2350. After a moment, your game should show up on the display<sup id="fnref:upload" role="doc-noteref"><a href="#fn:upload" class="footnote" rel="footnote">5</a></sup>.</p>

<h2 id="results">Results</h2>

<p>Well, the result is a display fitting neatly into the Lego Game Boy and running the game you picked. Of course, at this point it does not have sound or any means to control the game, so you should pick a game that runs on its own in some demo mode. You might even want to look for ROM hacks that reduce the wait time for the demo mode or that do something entirely different.</p>

<p>However, if you want to create a fully functional Lego Game Boy, that should be possible, too. The emulator supports inputs via GPIO, it can generate sound and it should be easy to run the rp2350 on a battery. The tricky part will be getting it all into the Lego Game Boy and especially figuring out some kind of contact that works with the Lego Game Boy’s mechanical buttons. I think it can be done, but it would be total overkill for my purpose, so I am eager to hear from you when you managed it :)</p>

<figure>

<img src="/assets/resized/images/2025-10-02/1280/result.jpg" style="max-width: min(3000px, 100%)" alt="Photo of the wooden Game Boy next to the modified Lego Game Boy" srcset="    /assets/resized/images/2025-10-02/640/result.jpg 640w,    /assets/resized/images/2025-10-02/768/result.jpg 768w,    /assets/resized/images/2025-10-02/1024/result.jpg 1024w,    /assets/resized/images/2025-10-02/1280/result.jpg 1280w, /assets/images/2025-10-02/result.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>Comparison of the two modern displays. To be honest, one can tell that the display in the Lego Game Boy is not the greates and I would love it to not have such a noticable frame. If you find a better fit, let me know!</figcaption>
</figure>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:affiliate" role="doc-endnote">
      <p>And now that I am writing the post, I realize that this might be the first time I have a chance to actually test affiliate links in a meaningful way. But don’t worry, every link has the product name clear enough that you can just search for it yourself if you don’t like those links. <a href="#fnref:affiliate" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:windowsize" role="doc-endnote">
      <p>This means that it is a window that is six studs wide and six full-sized lego blocks high. The depth of the window is of course just one stud. <a href="#fnref:windowsize" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:cardboard" role="doc-endnote">
      <p>Okay, any cardboard should do if you have a black marker. Just shov it in there and try to make it look nice :) <a href="#fnref:cardboard" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:sdcard" role="doc-endnote">
      <p>The emulator’s default solution for this is actually connecting an SD card reader to the rp2350, but since I will not be switching games that often, I did not go that route. If you want that, just look at the emulator’s documentation and change the config files accordingly. <a href="#fnref:sdcard" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:upload" role="doc-endnote">
      <p>Admittedly, I had some trouble to upload directly from VSCode. If the checkmark worked without any error messages, try the following: In the terminal that eventually printed “pico2 SUCCESS 00:00:05.526” you will see a line like “Building .pio/build/pico2/firmware.bin” (or similar on other operating systems). This is relative to your project folder where you can find the compiled firmware. Look for a file that ends with <code class="language-plaintext highlighter-rouge">.uf2</code>. When you found it, plug in your rp2350, then hold its <code class="language-plaintext highlighter-rouge">BOOT</code> button, shortly press the <code class="language-plaintext highlighter-rouge">RUN</code> button and then release the <code class="language-plaintext highlighter-rouge">BOOT</code> button. The rp2350 should now show up as a USB drive on your PC. Throw the uf2 file onto this drive and it should automatically disconnect and reboot with your Game Boy game on it. <a href="#fnref:upload" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[UPDATE A FEW HOURS LATER: Turns out that Natalie the Nerd beat me to it by a few hours. But she did not just beat me in terms of releasing a bit earlier, but also in functionality, style and execution. I fully admit defeat in this undeclared race that every nerd with a public channel just seems to secretly have competed in. I will be forming a “Natalie beat us” support group with all the other just-a-little-too-late-and-not-quite-as-good projects that will be popping up over the next weeks :) Well, ok, there actually is a Lego Game Boy and it has just been released today yesterday. What there actually oughta be is a real display for it, which this blog post is about. Click the image to see the video on youtube.com. If you just want to see it in action, check out the Youtube video. If you want to add a display to your own Lego Game Boy, read on for all the details on how to do this.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://there.oughta.be/assets/images/2025-10-02/youtube.jpg" /><media:content medium="image" url="https://there.oughta.be/assets/images/2025-10-02/youtube.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">There oughta be a Game Boy Photo Booth</title><link href="https://there.oughta.be/a/game-boy-photo-booth" rel="alternate" type="text/html" title="There oughta be a Game Boy Photo Booth" /><published>2025-07-21T00:00:00+02:00</published><updated>2025-07-21T00:00:00+02:00</updated><id>https://there.oughta.be/a/game-boy-photo-booth</id><content type="html" xml:base="https://there.oughta.be/a/game-boy-photo-booth"><![CDATA[<p>Another one of my cousins got married, so it was time for another photo/video booth. This time it uses a Game Boy Camera and a Game Boy Printer.</p>

<figure class="youtube">
<a href="https://youtu.be/9KqTbu14pp0" target="_blank">
<img src="/assets/resized/images/2025-07-21/1280/youtube.jpg" style="max-width: min(1920px, 100%)" alt="Thumbnail of the youtube video: The thumbnail shows a wedding couple on the left hand side. The groom is carrying the bride, both laughing. On the right hand side, there is a details shot of a Game Boy Printer, showing the logo of the device and a print-out coming out of it. The printout shows the couple from the left side of the thumbnail in typically low quality black and white quality of a thermal paper print." srcset="    /assets/resized/images/2025-07-21/640/youtube.jpg 640w,    /assets/resized/images/2025-07-21/768/youtube.jpg 768w,    /assets/resized/images/2025-07-21/1024/youtube.jpg 1024w,    /assets/resized/images/2025-07-21/1280/youtube.jpg 1280w, /assets/images/2025-07-21/youtube.jpg 1920w" sizes="(max-width: 60rem) 100vw, 60rem" />
</a>
<figcaption>Click the image to see the video on youtube.com.</figcaption>
</figure>

<p>The video above gives a good overview of the project. The blog entry below contains a few more technical details as well as links to source code and additional resources.</p>

<!--more-->

<p><em>Please note that this article contains affiliate links, i.e. links that tell the target site that I sent you there and that earn me a share of their revenue in return, i.e. as an Amazon Associate I earn from qualifying purchases. In contrast to <a href="https://youtube.com/c/thereoughtabe" target="_blank">regular external links</a>, such affiliate links are marked with a <a href="https://amzn.to/4kRFDld" target="_blank" class="affiliate">dollar sign</a> instead of a box.</em></p>

<h2 id="what-is-a-game-boy-photo-booth">What is a Game Boy Photo Booth?</h2>

<p>Loyal readers of my blog will certainly remember my <a href="/a/bullet-time-video-booth">bullet time video booth</a>. It was a rig for my cousin’s wedding allowing guests to take short video clips of themselves with a bullet time effect as transition. In fact, building these video booths has become a bit of a tradition as I first created one for my own wedding, followed by a total of three for friends and cousins. Each time resulting in a wonderful memory of the wedding reception with clips from all the guest cut into one long video with upbeat music selected by the couple.</p>

<p>So, when another one of my cousins<sup id="fnref:cousins" role="doc-noteref"><a href="#fn:cousins" class="footnote" rel="footnote">1</a></sup> got married, it was time for another one, video booth number five. Since reusing the bullet time rick was not an option due to space constrictions, it was a perfect chance to make a crossover between my camera stuff and my Game Boy stuff to create a Game Boy photo booth. After all, that cousin also loves the old video game systems.</p>

<figure>

<img src="/assets/resized/images/2025-07-21/1280/action.jpg" style="max-width: min(3000px, 100%)" alt="Wide angle shot of a small room. On the left there is a table with colorful disguises like hats and oversized sunglasses. On the table are also a red and green push button and a Game Boy Printer. Above the table are two large video lights an a screen inbetween those lights with a camera barely visible above the screen. On the right, in front of the camera, are three party guests with diguises singing, linking arms and looking at the camera." srcset="    /assets/resized/images/2025-07-21/640/action.jpg 640w,    /assets/resized/images/2025-07-21/768/action.jpg 768w,    /assets/resized/images/2025-07-21/1024/action.jpg 1024w,    /assets/resized/images/2025-07-21/1280/action.jpg 1280w, /assets/images/2025-07-21/action.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>The video/photo booth in action. Guests control it via the green and red push buttons on the table and will see a preview of their recordings and their printout on the screen.</figcaption>
</figure>

<p>Ok, so what is it? A photo booth or a video booth? Well, both.</p>

<p>When guests push the green button, they get some intructions and a countdown. At the end of the countdown a five second video clip is recorded using a proper modern mirrorless camera and a Game Boy camera simultaneously, resulting in a bunch of video clips for me to assemble later. That’s a video booth. But with this setup, it does not end here. It also takes three stills from the Game Boy Camera recording and offers the guests to print them through a Game Boy Printer as a souvenir for the guests themselves - like typical photo booths do at many wedding receptions<sup id="fnref:customs" role="doc-noteref"><a href="#fn:customs" class="footnote" rel="footnote">2</a></sup>.</p>

<figure>

<img src="/assets/resized/images/2025-07-21/1280/cameras.jpg" style="max-width: min(3000px, 75%)" alt="Detail shot of the camera assembly at the top of the photo booth. From left to right it shows a Game Boy Pocket, a Game Boy Camera in a mount and a Sony a6400 from which mostly a big Tamron lens is visible." srcset="    /assets/resized/images/2025-07-21/640/cameras.jpg 640w,    /assets/resized/images/2025-07-21/768/cameras.jpg 768w,    /assets/resized/images/2025-07-21/1024/cameras.jpg 1024w,    /assets/resized/images/2025-07-21/1280/cameras.jpg 1280w, /assets/images/2025-07-21/cameras.jpg 3000w" sizes="(max-width: 60rem) calc(75/100 * 100vw), calc(75/100 * 60rem)" />

<figcaption>The video/photo booth records with a Game Boy Camera and a Sony a6400 simultaneously.</figcaption>
</figure>

<figure>

<img src="/assets/resized/images/2025-07-21/1280/gbprinter.jpg" style="max-width: min(3000px, 75%)" alt="Close-up of the Game Boy Printer on the table at the wedding reception. A half-printed strip of paper is sticking out from its top, showing two stills from a video recorded by guests." srcset="    /assets/resized/images/2025-07-21/640/gbprinter.jpg 640w,    /assets/resized/images/2025-07-21/768/gbprinter.jpg 768w,    /assets/resized/images/2025-07-21/1024/gbprinter.jpg 1024w,    /assets/resized/images/2025-07-21/1280/gbprinter.jpg 1280w, /assets/images/2025-07-21/gbprinter.jpg 3000w" sizes="(max-width: 60rem) calc(75/100 * 100vw), calc(75/100 * 60rem)" />

<figcaption>Guests can print stills from their videos on the Game Boy Printer.</figcaption>
</figure>

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

<p>The entire setup does not really do something unusual I haven’t done before, but it combines many different components.</p>

<p>At the heart sits a <a href="https://amzn.to/4kRFDld" target="_blank" class="affiliate">Raspberry Pi 4</a>, which runs a <a href="https://github.com/Staacks/there.oughta.be/tree/master/game-boy-photo-booth/photobooth-py" target="_blank">Python script</a> to control everything and to show a GUI to the guests. That GUI is actually a web-interface, based on <a href="https://palletsprojects.com/projects/flask/" target="_blank">Flask</a> and displayed locally via Chromium. In theory, you could use this to show the status of the video booth remotely or display the newest video clips somewhere else at the wedding reception, but I only used it to show the GUI on a screen attached to the same Raspberry Pi for a simple reason: I find it much easier to create a full screen GUI with frame-precise playback<sup id="fnref:frameprecise" role="doc-noteref"><a href="#fn:frameprecise" class="footnote" rel="footnote">3</a></sup> of multiple videos via HTML5 and JavaScript than doing so with some specialized and OS-specific Python modules<sup id="fnref:videomodules" role="doc-noteref"><a href="#fn:videomodules" class="footnote" rel="footnote">4</a></sup>.</p>

<figure>

<img src="/assets/resized/images/2025-07-21/1280/pushbutton.jpg" style="max-width: min(3000px, 75%)" alt="Photo of two large push buttons (the really big emergency stop types). The left one has a green hat, the right one has a red hat." srcset="    /assets/resized/images/2025-07-21/640/pushbutton.jpg 640w,    /assets/resized/images/2025-07-21/768/pushbutton.jpg 768w,    /assets/resized/images/2025-07-21/1024/pushbutton.jpg 1024w,    /assets/resized/images/2025-07-21/1280/pushbutton.jpg 1280w, /assets/images/2025-07-21/pushbutton.jpg 3000w" sizes="(max-width: 60rem) calc(75/100 * 100vw), calc(75/100 * 60rem)" />

<figcaption>Two emergency stop type push buttons are used to control the photo booth via Bluetooth Low Energy.</figcaption>
</figure>

<p>The video box is controlled by two big push buttons. These are exactly the same push buttons I already used for the <a href="/a/bullet-time-video-booth#bluetooth-push-buttons">bullet time video booth</a>. They run on two AA batteries each and have a <a href="https://amzn.to/4f2zd1d" target="_blank" class="affiliate">Raspberry Pi Pico W</a> inside that acts as a Bluetooth Low Energy keyboard, sending only a single button press to the Pi 4 whenever the push button is pressed. Its source code can be found <a href="https://github.com/Staacks/pico-w-ble-button" target="_blank">here</a>.</p>

<figure>

<img src="/assets/resized/images/2025-07-21/1280/pushbutton_pico_w.jpg" style="max-width: min(3000px, 60%)" alt="The top of one of the push buttons is lifted to reveal its insides. The edge of a Raspberry Pi Pico W is visible at the edge." srcset="    /assets/resized/images/2025-07-21/640/pushbutton_pico_w.jpg 640w,    /assets/resized/images/2025-07-21/768/pushbutton_pico_w.jpg 768w,    /assets/resized/images/2025-07-21/1024/pushbutton_pico_w.jpg 1024w,    /assets/resized/images/2025-07-21/1280/pushbutton_pico_w.jpg 1280w, /assets/images/2025-07-21/pushbutton_pico_w.jpg 3000w" sizes="(max-width: 60rem) calc(60/100 * 100vw), calc(60/100 * 60rem)" />

<figcaption>The push buttons contain a Pi Pico W powered by two AA batteries.</figcaption>
</figure>

<p>Then there is the main camera, the Game Boy Camera and the Game Boy Printer, which I will discuss in the next sections.</p>

<h2 id="controlling-the-main-camera">Controlling the main camera</h2>

<p>In the context of this project, controlling the main camera, a <a href="https://amzn.to/4nY9w5O" target="_blank" class="affiliate">Sony a6400</a>, should be the boring part, but there are a few details to it that make this trickier than you might think.</p>

<p>In a perfect world, I would just hook up my modern camera to a USB port and be able to control it and to download recorded videos, so I can show the results to the guests. Unfortunately, that’s not possible with the a6400 (no video download in anything but mass storage mode).</p>

<p>In a less perfect, but still somewhat nice world, I would use the camera’s Wifi interface for this, but nope, also not possible on the a6400 (no video download via Wifi).</p>

<p>In a cumbersome but still livable world, I would grab the video directly from the camera through an HDMI grabber, but that is not possible on the a6400 with USB control (one camera stream is reserved for USB preview and hence unavailable for HDMI out).</p>

<p>The combination that actually worked well for me is (brace yourself!) controlling the camera via Bluetooth Low Energy and retrieving the recording through a Wifi SD card. In fact, this is what gave me the idea to create my <a href="/a/bluetooth-remote">Bluetooth remote control app for alpha cameras</a> for which I <a href="/a/bluetooth-remote#use-bluetooth-instead">already discussed the different control and retrieval options</a>.</p>

<p>The downside is that I had to limit the resolution to 1080p instead of 4k due to the slow memory card, but compared to the Game Boy Cameras resolution that’s still plenty.</p>

<figure>

<img src="/assets/resized/images/2025-07-21/1280/wifi-sd.jpg" style="max-width: min(2723px, 50%)" alt="Photo of fingers holding a white SD card. Its label reads FlashAir W-03, Wireless LAN 16GB Toshiba." srcset="    /assets/resized/images/2025-07-21/640/wifi-sd.jpg 640w,    /assets/resized/images/2025-07-21/768/wifi-sd.jpg 768w,    /assets/resized/images/2025-07-21/1024/wifi-sd.jpg 1024w,    /assets/resized/images/2025-07-21/1280/wifi-sd.jpg 1280w, /assets/images/2025-07-21/wifi-sd.jpg 2723w" sizes="(max-width: 60rem) calc(50/100 * 100vw), calc(50/100 * 60rem)" />

<figcaption>An old Wifi SD card turned out to be the best way to retrieve the data. That should probably tell us a few things about proprietary firmware and generally bad software of Sony cameras.</figcaption>
</figure>

<h2 id="capturing-the-game-boy-camera">Capturing the Game Boy Camera</h2>

<p>Believe it or not, but recording a video stream from the Game Boy Camera is much simpler. At least if you already have another one of my projects at hand: <a href="/a/game-boy-capture-cartridge">The GB Interceptor</a>.</p>

<p>Quick explanation if you have not heard of it: It is a little adapter that goes between a Game Boy and a game cartridge (like the Game Boy Camera). It captures the communication between the two to reconstruct what the Game Boy is showing on the screen, all handled by an rp2040 microcontroller. In case of the Game Boy Camera, you can simply enter the shoot mode of the Game Boy Camera and the video will be available to the GB Interceptor.</p>

<figure>

<img src="/assets/resized/images/2025-07-21/1280/gbcamera.jpg" style="max-width: min(2933px, 50%)" alt="Detail shot of a Game Boy Camera sticking out from a classical Game Boy. The shot shows the camera while only the top edge of the Game Boy is visible." srcset="    /assets/resized/images/2025-07-21/640/gbcamera.jpg 640w,    /assets/resized/images/2025-07-21/768/gbcamera.jpg 768w,    /assets/resized/images/2025-07-21/1024/gbcamera.jpg 1024w,    /assets/resized/images/2025-07-21/1280/gbcamera.jpg 1280w, /assets/images/2025-07-21/gbcamera.jpg 2933w" sizes="(max-width: 60rem) calc(50/100 * 100vw), calc(50/100 * 60rem)" />

<figcaption>This is the original Game Boy Camera from my childhood in the original Game Boy also from my childhood (not used in this project).</figcaption>
</figure>

<p>The GB Interceptor simply acts as a regular USB webcam and since my original article on the GB Interceptor, I managed to implement MJPEG encoding which makes it compatible with pretty much any host device<sup id="fnref:switch2" role="doc-noteref"><a href="#fn:switch2" class="footnote" rel="footnote">5</a></sup>, including computers and laptops with Linux, Windows and MacOS as well as Android tablets and iPads with iPadOS. If you are interested in learning how encoding MJPEG on an rp2040 works when it is already fully occupied following a Game Boy and rendering its graphics, I would recommend watching <a href="https://media.ccc.de/v/37c3-11928-reconstructing_game_footage_from_a_game_boy_s_memory_bus" target="_blank">my talk at the 37c3 Chaos Communication Congress</a> (yes, I am quite proud of that talk).</p>

<p>The point is that I only had to use a regular Game Boy, plug in the GB Interceptor, plug in the Game Boy Camera into the GB Interceptor and connect the GB Interceptor with a USB-C cable to the Raspberry Pi. Maybe the only unusual thing is that I used the break-out board of the GB Interceptor PCB to use a long ribbon cable between the Game Boy and the Game Boy Camera, allowing me to position it more freely.</p>

<figure>

<img src="/assets/resized/images/2025-07-21/1280/gb-camera-capture.jpg" style="max-width: min(3000px, 75%)" alt="Detail shot of the Game Boy and the Game Boy Camera above the photo booth screen. The Game Boy on the left is a blue Game Boy Pocket with a purple PCB sticking out from its top. A USB-C cable is attached to the side of the PCB. On the right is a green Game Boy Camera, held to a mount with zip ties." srcset="    /assets/resized/images/2025-07-21/640/gb-camera-capture.jpg 640w,    /assets/resized/images/2025-07-21/768/gb-camera-capture.jpg 768w,    /assets/resized/images/2025-07-21/1024/gb-camera-capture.jpg 1024w,    /assets/resized/images/2025-07-21/1280/gb-camera-capture.jpg 1280w, /assets/images/2025-07-21/gb-camera-capture.jpg 3000w" sizes="(max-width: 60rem) calc(75/100 * 100vw), calc(75/100 * 60rem)" />

<figcaption>The Game Boy Camera on the right is running on the Game Boy Pocket on the left. It is hooked up with a long ribbon cable that is attached to the back of the GB Interceptor (purple PCB sticking out the back of the Game Boy). The USB-C cable at the side of the GB Interceptor is connected to the Raspberry Pi 4 to capture a video of the Game Boy screen.</figcaption>
</figure>

<figure>

<img src="/assets/resized/images/2025-07-21/1280/interceptor-cable.jpg" style="max-width: min(3000px, 75%)" alt="View from the back of the previous photo. Now the GB Interceptor is visible on the right with the long ribbon cable attached to it and the label GB Interceptor well visible. On the left is the Game Boy Camera (slightly blurred outside the focal plane)." srcset="    /assets/resized/images/2025-07-21/640/interceptor-cable.jpg 640w,    /assets/resized/images/2025-07-21/768/interceptor-cable.jpg 768w,    /assets/resized/images/2025-07-21/1024/interceptor-cable.jpg 1024w,    /assets/resized/images/2025-07-21/1280/interceptor-cable.jpg 1280w, /assets/images/2025-07-21/interceptor-cable.jpg 3000w" sizes="(max-width: 60rem) calc(75/100 * 100vw), calc(75/100 * 60rem)" />

<figcaption>The GB Interceptor is connected to a long ribbon cable and the GB Camera is actually plugged into the GB Interceptor's breakout board to allow for a more flexible placement.</figcaption>
</figure>

<h2 id="printing-on-the-game-boy-printer">Printing on the Game Boy Printer</h2>

<p>And finally, the Game Boy Printer. This is the part for which I had to write the most code. Not because it was particularly difficult, but simply because I had not done it before.</p>

<p>When I am saying that it was not difficult, then this is the case not because I naturally speak Game Boy Printer, but because I could of course once again count on the Game Boy dev community to have long solved how this is to be done. As always, the <a href="https://gbdev.io/pandocs/Gameboy_Printer.html" target="_blank">gbdev.io Pandocs</a> give all the necessary info in a compact overview, but there are also plenty more explanations for the Game Boy Printer communication to be found on the web.</p>

<p>Electrically, the communication is done through a Game Boy link cable, which is basically an SPI interface. In case of the printer it is even more rudimentary as data is never transmitted in both directions at the same time. So, I wrote some <a href="https://github.com/Staacks/there.oughta.be/tree/master/game-boy-photo-booth/pro_micro" target="_blank">minimal Arduino code</a> that receives data through a USB serial interface, writes it as SPI to the Game Boy Printer and returns the answer back to the USB serial.</p>

<figure>

<img src="/assets/resized/images/2025-07-21/1280/spi.jpg" style="max-width: min(3000px, 100%)" alt="A photo of an oscilloscope showing three traces and decoding information below. From top to bottom they are labelled as CLK, MOSI and MISO. The CLK signal is on 5V most of the time with very frequent drops to 0V. MOSI is mostly on 0V with sporadic rises to 5V and MISO shows very pronounced cross-talk from CLK, but basically stays on 0V until some 5V activity towards the end of the signal. The CLK signal appears in clusters and below all three traces are two lines of decoded data labelled MISO and MOSI with one byte represented as two hex digits corresponding to each cluster of the CLK signal." srcset="    /assets/resized/images/2025-07-21/640/spi.jpg 640w,    /assets/resized/images/2025-07-21/768/spi.jpg 768w,    /assets/resized/images/2025-07-21/1024/spi.jpg 1024w,    /assets/resized/images/2025-07-21/1280/spi.jpg 1280w, /assets/images/2025-07-21/spi.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>The GB Printer signal is basically a fixed SPI connection and can be decoded by simple oscilloscopes.</figcaption>
</figure>

<p>The Arduino code runs on a 5V variant of the Pro Micro. You know how much I love the Pi Pico / rp2040, but in this case I had to yield to the old technology of the Game Boy Printer, which has a logic level of 5V. So, as the Printer would not accept the 3.3V commands from a  Pi Pico, instead of fiddling with level shifters, I simply grabbed the first 5V uC from my collection that I could find - which was a Pro Micro.</p>

<figure>

<img src="/assets/resized/images/2025-07-21/1280/pinout.jpg" style="max-width: min(3000px, 100%)" alt="A photo on the left shows the Pro Micro with four cables soldered to the pins 16, 14, 15 and GND. The pins are labelled as MOSI, MISO, SCLK and GND with matching colors of the cables. The photo on the right shows the connector for the Game Boy printer with four of its six pins labelled SIN, SCK, SOUT and GND and matching colors for the Pro Micro. The remaining two pins are labelled as not connected." srcset="    /assets/resized/images/2025-07-21/640/pinout.jpg 640w,    /assets/resized/images/2025-07-21/768/pinout.jpg 768w,    /assets/resized/images/2025-07-21/1024/pinout.jpg 1024w,    /assets/resized/images/2025-07-21/1280/pinout.jpg 1280w, /assets/images/2025-07-21/pinout.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>A quick (and dirty) solder job is enough to hook up the Game Boy Printer to the Pro Micro's SPI interface. Note that the colors are just the ones that happened to be used in the cheap cable I used for this. Also be aware that naturally MOSI and MISO are swapped on both ends of such cables, so even if you had the same cable as shown here, the colors might be different if you use the other end.)</figcaption>
</figure>

<p>The Printer itself is somewhat limited. It can receive commands for line feed and accepts 8 pixel high rows of 2bit tile data in the same format that the Game Boy uses to print batches of eight pixel rows. Its horizontal resolution matches the Game Boy with 160 pixels while you can print endlessly to achieve any vertical resolution. However, it only has internal memory for a single Game Boy sized screen (144 pixels or 18 tile rows), so long images have to be split up into multiple commands. Transfer and printing takes a while although I am driving the Printer’s SPI interface at four times the data rate that could be achieved by the original Game Boy (32kbit/s instead of 8kbit/s).</p>

<p>I wrapped it all into a neat little <a href="https://github.com/Staacks/there.oughta.be/tree/master/game-boy-photo-booth/GBPrinter" target="_blank">Python module</a>, which boils it down to a single command that takes an arbitrary image to be printed on the Game Boy Printer. Well, almost arbitrary: The height of the image must be a multiple of eight after it has been scaled to a width of 160 pixels.</p>

<p>You should be able to easily reuse my work by using <a href="https://github.com/Staacks/there.oughta.be/tree/master/game-boy-photo-booth/pro_micro" target="_blank">my Arduino code</a> on almost any 5V micro controller and using my <a href="https://github.com/Staacks/there.oughta.be/tree/master/game-boy-photo-booth/GBPrinter" target="_blank">Python module for the Game Boy Printer</a>:</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="code"><pre><span class="kn">from</span> <span class="nn">GBPrinter</span> <span class="kn">import</span> <span class="n">GBPrinter</span>

<span class="k">with</span> <span class="n">GBPrinter</span><span class="p">(</span><span class="s">"/dev/ttyACM0"</span><span class="p">,</span> <span class="mi">115200</span><span class="p">)</span> <span class="k">as</span> <span class="n">gbp</span><span class="p">:</span>
    <span class="n">gbp</span><span class="p">.</span><span class="n">printImageFromFile</span><span class="p">(</span><span class="s">"testprint.png"</span><span class="p">,</span> <span class="mf">0.5</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>The second parameter adjusts the “exposure” of the thermo paper, i.e. making the entire printout darker or brighter.</p>

<p>And a final important tip: You do not have to find some of the rare old stocks of original Game Boy Printer paper or expensive reproductions. Any thermal paper with the right width of 38mm and a not too big roll diameter should do fine.</p>

<h2 id="conclusion">Conclusion</h2>

<p>In the end the guests had a lot of fun and especially my cousin who also appreciates old video games loved the Game Boy video/photo booth. Of course, the final video with the clips from all the guests primarily used the footage from the “good” camera. You would not want this memory of your wedding to be lost in 128x112 pixels<sup id="fnref:res" role="doc-noteref"><a href="#fn:res" class="footnote" rel="footnote">6</a></sup>, but I could use the Game Boy Camera footage to spice up the video.</p>

<p>It might also be worth noting that once the setup was running, I did not have to look after it except for refilling the printer paper twice. I emphasize “once it was running”, because I was a bit careless with the quick solder job on the Pro Micro while setting it up, which means that I am now known as that nerd who brought a soldering iron to a wedding. Totally worth it.</p>

<figure>

<img src="/assets/resized/images/2025-07-21/1280/soldering_at_a_wedding.jpg" style="max-width: min(2250px, 50%)" alt="Photo of me in a white shirt holding a soldering iron next to a table with a sign Foto Box (in German) on it." srcset="    /assets/resized/images/2025-07-21/640/soldering_at_a_wedding.jpg 640w,    /assets/resized/images/2025-07-21/768/soldering_at_a_wedding.jpg 768w,    /assets/resized/images/2025-07-21/1024/soldering_at_a_wedding.jpg 1024w,    /assets/resized/images/2025-07-21/1280/soldering_at_a_wedding.jpg 1280w, /assets/images/2025-07-21/soldering_at_a_wedding.jpg 2250w" sizes="(max-width: 60rem) calc(50/100 * 100vw), calc(50/100 * 60rem)" />

<figcaption>If you are too careless about your soldering and packing, you might want to bring a soldering iron...</figcaption>
</figure>

<h2 id="overview-of-source-code-and-links">Overview of source code and links</h2>

<p>Usually, I provide one link to the source for a project, which contains everything, but in this case it is unlikely that you will reuse the entire project (after all, it is quite specific to all the components) and some parts have been reused from previous projects. So, here is an overview of all the bits and pieces already provided throughout the blog article:</p>

<ul>
  <li>There is the <a href="https://github.com/Staacks/there.oughta.be/tree/master/game-boy-photo-booth/photobooth-py" target="_blank">Python code for the Raspberry Pi 4</a>. Note, that this is based on the older <a href="https://github.com/Staacks/there.oughta.be/tree/master/bullet-time-video-booth/bullettimeBooth" target="_blank">code for the bullet time video booth</a>, which might be worth checking out as well, because it might clear up a few oddities in the Game Boy Photo Booth code. For example, there is a rather unnecessary failure state recovery that only restarts the camera Bluetooth connection, which has the more complex task of actually restarting the entire USB bus for the old setup with 13 cameras hooked up to the system.</li>
  <li>I have placed <a href="https://github.com/Staacks/there.oughta.be/tree/master/game-boy-photo-booth/GBPrinter" target="_blank">the module for the Game Boy Printer</a> in its own folder to make it easier to reuse it. This contains a copy of the GBPrinter module also present in the rPi 4 Python code along with a test script.</li>
  <li>The <a href="https://github.com/Staacks/there.oughta.be/tree/master/game-boy-photo-booth/pro_micro" target="_blank">Arduino code for the Pro Micro</a> comes as an Arduino project (just the “gblink” folder) along with a test script and a hookup guide.</li>
  <li>The <a href="https://github.com/Staacks/pico-w-ble-button" target="_blank">BLE push buttons</a> use exactly the same code as the old version for the bullet time video booth.</li>
</ul>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:cousins" role="doc-endnote">
      <p>This is now cousin 3 of 5, so this might not be the last blog/video on the subject. <a href="#fnref:cousins" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:customs" role="doc-endnote">
      <p>Of course, this is what I perceive as “typical”. I could not even say if a wedding tradition is actually typical for Germany or only for the part of Germany where I live. No idea if the rest of the world does the same, but I suspect that a lot of these traditions has been unified by Hollywood. <a href="#fnref:customs" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:frameprecise" role="doc-endnote">
      <p>Frame-precise playback is also the reason that I do most of my presentations in HTML instead of something like PowerPoint. When I started doing this more than ten years ago, the HTML5 players in Chrome and Firefox were pretty much the only ones who could jump to a specific frame and not just the nearest I-frame. Being able to do such jumps and scripting them is so helpful for showing nicely rendered animations. <a href="#fnref:frameprecise" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:videomodules" role="doc-endnote">
      <p>In fact, I am so used to doing this in HTML, which runs pretty much on any platform, I cannot even be bothered to check which alternatives might be available. <a href="#fnref:videomodules" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:switch2" role="doc-endnote">
      <p>With the Nintendo Switch 2 being a notable exception. Since I do not own a Switch 2 I cannot test this, but I suspect that it rejects the odd resolution with a 10:9 aspect ratio. <a href="#fnref:switch2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:res" role="doc-endnote">
      <p>Yes, the Game Boy Camera resolution is even lower than the Game Boy’s 160x144. <a href="#fnref:res" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[Another one of my cousins got married, so it was time for another photo/video booth. This time it uses a Game Boy Camera and a Game Boy Printer. Click the image to see the video on youtube.com. The video above gives a good overview of the project. The blog entry below contains a few more technical details as well as links to source code and additional resources.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://there.oughta.be/assets/images/2025-07-21/youtube.jpg" /><media:content medium="image" url="https://there.oughta.be/assets/images/2025-07-21/youtube.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">There oughta be a Game Boy Slideshow</title><link href="https://there.oughta.be/a/game-boy-slideshow" rel="alternate" type="text/html" title="There oughta be a Game Boy Slideshow" /><published>2025-01-10T00:00:00+01:00</published><updated>2025-01-10T00:00:00+01:00</updated><id>https://there.oughta.be/a/game-boy-slideshow</id><content type="html" xml:base="https://there.oughta.be/a/game-boy-slideshow"><![CDATA[<p>What is the easiest way to create a ROM with a slideshow of your own images for your Game Boy? How about opening a website, dragging your images into the site and hitting a generate button? That’s it. That’s what I created here.</p>

<figure class="youtube">
<a href="https://youtu.be/s0pecEwJihE" target="_blank">
<img src="/assets/resized/images/2025-01-10/1280/youtube.jpg" style="max-width: min(1920px, 100%)" alt="Thumbnail of the youtube video: The image shows a photo of Sebastian with a white frame. An arrow points from the photo to the right at the screen of a green Game Boy Color. The Game Boy Color shows the exact same photo on its screen with slightly washed out colors." srcset="    /assets/resized/images/2025-01-10/640/youtube.jpg 640w,    /assets/resized/images/2025-01-10/768/youtube.jpg 768w,    /assets/resized/images/2025-01-10/1024/youtube.jpg 1024w,    /assets/resized/images/2025-01-10/1280/youtube.jpg 1280w, /assets/images/2025-01-10/youtube.jpg 1920w" sizes="(max-width: 60rem) 100vw, 60rem" />
</a>
<figcaption>Click the image to see the video on youtube.com.</figcaption>
</figure>

<p>Check out this rather short Youtube video to see the tool in action or just look at the top of this post to try it out yourself.</p>

<!--more-->

<p><em>Please note that this article contains affiliate links, i.e. links that tell the target site that I sent you there and that earn me a share of their revenue in return, i.e. as an Amazon Associate I earn from qualifying purchases. In contrast to <a href="https://youtube.com/c/thereoughtabe" target="_blank">regular external links</a>, such affiliate links are marked with a <a href="https://www.amazon.de/Junior-Micro-SD-Spielkarte-Gameboy-Advance-Pocket/dp/B0836LCWGF?__mk_de_DE=%C3%85M%C3%85%C5%BD%C3%95%C3%91&amp;crid=2UF3QKF6GRRC5&amp;dib=eyJ2IjoiMSJ9.VWhFjxj84bk7U2PY9Z9hzMF27rdqX7dypF1SZHZKy39DFJcUentLBmm95MqAiJTAoS__f-YfoBmPNgIQjQVQ_HddCpuTxlb2T3xjCDZFSe4GvZyKTo7c31SPTV4_uOOcxKJs_VMSTfF1jRAI4biy8NiTYnT_LPQGEIUEB5v87q0oMVSQe9q4TtoKZ4jZf0j4ifdF-WZgkeWNZXx2bNmeq3IX-be00MSRoIRrGzEWmkI.5Auj2h4AEfcr2AOoY_aMAgl9G43JyLtDHxC6-lF9Mgc&amp;dib_tag=se&amp;keywords=EZ+Flash+Junior&amp;nsdOptOutParam=true&amp;qid=1736506932&amp;sprefix=ez+flash+junior%2Caps%2C80&amp;sr=8-2&amp;linkCode=ll1&amp;tag=oughtabe-21&amp;linkId=e05bd712968669d37bdff755d5efff73&amp;language=de_DE&amp;ref_=as_li_ss_tl" target="_blank" class="affiliate">dollar sign</a> instead of a box.</em></p>

<p>I think that the tool is mostly self-explanatory, so here it is. You can find further info in an FAQ style below the tool. You can also open it <a href="/assets/html/gbslideshow.html" target="_blank">on its own page</a>.</p>

<div id="gbslideshow" style="width: 100%; margin: 0; background: #ffffff; color: black; overflow: auto; box-shadow: inset 3px 3px 12px -3px rgba(0, 0, 0, 0.69); border-radius: 1em;"></div>
<script src="/assets/js/baserom.gb.js"></script>

<script src="/assets/js/gbslideshow.js"></script>

<h2 id="general">General</h2>

<h3 id="where-is-the-source-code">Where is the source code?</h3>

<p><a href="https://github.com/Staacks/Game-Boy-Slideshow-Generator" target="_blank">On github</a></p>

<h3 id="are-images-being-uploaded-to-your-site">Are images being uploaded to your site?</h3>

<p>No, the entire tool runs in your browser using JavaScript. In fact, you can download the tool and run it from your local drive without any internet connection (see next question).</p>

<h3 id="how-can-i-run-this-locally">How can I run this locally?</h3>

<p>Go to <a href="https://github.com/Staacks/Game-Boy-Slideshow-Generator" target="_blank">github</a> and download the entire project (if you are new to github: Green button labelled “Code” and then “Download ZIP”). If you downloaded it as a zip file, make sure to extract all files. Then simply open <code class="language-plaintext highlighter-rouge">gbslideshow.html</code> in your webbrowser (on most systems just a double click).</p>

<h2 id="usage">Usage</h2>

<p>You can load your images into the tool by either dragging them onto the tool or by pressing the “Add image” button and selecting images from your machine. Each image appears as a group of three previews: The original file you uploaded (scaled down to the Game Boy’s resolution), a preview of the grayscale image for the original Game Boy or Game Boy Pocket (or similar) and a preview for the color image for the Game Boy Color or Game Boy Advance (acting as a Game Boy Color). You can move or remove images with the three buttons in the bottom left corner (arrows and cross).</p>

<p>You can adjust brightness and contrast to your liking and toggle dithering for the grayscale images. In most cases a dithered image looks better as the Game Boy only has four shades of gray (or green), but for logos and drawings an undithered image can look better if you adjust brightness and contrast.</p>

<p>Below each image group is a setting “Seconds before next image”. This determines the duration for which an image is shown. If set to zero it will not advance automatically, but only if you select the next/previous image by pressing a button on the Game Boy. On the Game Boy every image can be advanced by pressing the D-pad to the right or the A button and you can always go back to the previous image by pressing left or the B button.</p>

<p>When you are happy with your selection of images, you can simply press “Generate ROM” at the bottom and your ROM will be downloaded directly in your browser. (If you don’t know what to do with the ROM, read <a href="#how-do-i-get-the-rom-onto-a-real-game-boy">How do I get the ROM onto a real Game Boy?</a> further below.)</p>

<h3 id="can-i-manually-step-through-the-images-on-the-game-boy">Can I manually step through the images on the Game Boy?</h3>

<p>You can always press the D-pad right or left to go the the next or previous image. The same works with the A and B button. If you don’t want the slideshow to advance on its own, set “Seconds before next image” to zero on all images.</p>

<h3 id="what-will-be-the-roms-size">What will be the ROM’s size?</h3>

<p>In the current version each image takes up nearly 16kiB, such that it makes sense to store one image per memory bank. The first bank is used for the code and the image index, so for N images the tool will use 16kiB*(N+1). However, it will pad the file to create the common ROM sizes of 32kiB, 64kiB, 128kiB, 256kiB, 512kiB, 1MiB, 2MiB or 4MiB. The tool is currently limited to 254 images.</p>

<h3 id="how-do-i-get-the-rom-onto-a-real-game-boy">How do I get the ROM onto a real Game Boy?</h3>

<p>You can directly use the ROM in an emulator or on modern devices that can handle ROM files directly (like the Analogue Pocket), but if you want to run it on original Game Boy hardware, you will need some kind of flash cartridge. It should work on any kind of flash cartridge as long as it is large enough for your ROM and properly handles common memory bank controllers (see below). I have tested the following:</p>
<ul>
  <li><a href="https://www.amazon.de/Junior-Micro-SD-Spielkarte-Gameboy-Advance-Pocket/dp/B0836LCWGF?__mk_de_DE=%C3%85M%C3%85%C5%BD%C3%95%C3%91&amp;crid=2UF3QKF6GRRC5&amp;dib=eyJ2IjoiMSJ9.VWhFjxj84bk7U2PY9Z9hzMF27rdqX7dypF1SZHZKy39DFJcUentLBmm95MqAiJTAoS__f-YfoBmPNgIQjQVQ_HddCpuTxlb2T3xjCDZFSe4GvZyKTo7c31SPTV4_uOOcxKJs_VMSTfF1jRAI4biy8NiTYnT_LPQGEIUEB5v87q0oMVSQe9q4TtoKZ4jZf0j4ifdF-WZgkeWNZXx2bNmeq3IX-be00MSRoIRrGzEWmkI.5Auj2h4AEfcr2AOoY_aMAgl9G43JyLtDHxC6-lF9Mgc&amp;dib_tag=se&amp;keywords=EZ+Flash+Junior&amp;nsdOptOutParam=true&amp;qid=1736506932&amp;sprefix=ez+flash+junior%2Caps%2C80&amp;sr=8-2&amp;linkCode=ll1&amp;tag=oughtabe-21&amp;linkId=e05bd712968669d37bdff755d5efff73&amp;language=de_DE&amp;ref_=as_li_ss_tl" target="_blank" class="affiliate">EZ Flash Junior</a></li>
  <li><a href="https://shop.insidegadgets.com/product/gameboy-2mb-32kb-fram-flash-cart-ultra-low-power/" target="_blank">insideGadgets’ 2MB Flash cart</a></li>
  <li>Simple 32kiB EEPROM chips directly connected to the Game Boy’s memory bus (aka using my <a href="/a/wifi-game-boy-cartridge">WiFi cartridge</a> without the ESP)</li>
  <li>An SD cart in an <a href="https://www.analogue.co/pocket" target="_blank">Analogue Pocket</a></li>
  <li>Various emulators</li>
</ul>

<h3 id="which-type-of-cartridge-is-supported-mbc">Which type of cartridge is supported? (MBC?)</h3>

<p>Pretty much any flash cartridge should work as long as it is large enough for your generated ROM. If you upload more than one image, the cartridge needs to properly handle memory bank controllers (MBCs), but the ROM generated here should be compatible with all of the common Nintendo MBCs. The tool will declare in the ROM’s header that MBC-5 is required (unless you generate a 32kiB ROM, in which case it declares a ROM-only cartridge), but if your ROM fits the cartridge it should also run on an MBC-1 or MBC-3 cart (2MiB). If your flasher gives you a warning about the wrong MBC, you can simply ignore it. If declaring a specific MBC is really necessary for any cart, let me know and I will add an option for this.</p>

<h2 id="technology">Technology</h2>

<h3 id="how-does-it-work">How does it work?</h3>

<p>The tool consists of a template ROM that I have written in assembly. This ROM is embedded into some JavaScript code which converts your images and then injects them into the template ROM. The template ROM uses two hardcoded addresses at which it expects a list of images. One for grayscale mode and one for color images. The image list consists of addresses (memory bank and memory address) and the display time for each image, so the JavaScript tool is simply responsible to write the images somewhere into the ROM (contained within a memory bank) and store the addresses there. In principle, different numbers of grayscale or color images could be supported, but that is not implemented in the JavaScript tool.</p>

<h3 id="how-are-the-images-processed">How are the images processed?</h3>

<p>The JavaScript code simply scales the images onto a 160x144 canvas (native resolution of the original Game Boy and Game Boy Color). From there it directly processes the images following the limitations of each Game Boy, so the preview images are exactly what you get on your Game Boy (except maybe for the fact that colors on the Game Boy Color can look a bit different).</p>

<h3 id="how-are-grayscale-images-stored">How are grayscale images stored?</h3>

<p>The grayscale images are stored as 360 tiles and simply displayed by switching the tile data address halfway through the frame. Otherwise it is just the normal 2bit “color” palette of the original Game Boy, so you end up with a 160x144 image with four shades of gray. The tool offers dithering to mitigate this limitation (Sierra dithering to be exact).</p>

<h3 id="how-are-color-images-stored">How are color images stored?</h3>

<p>The tile data works similar as for the grayscale images as there are 360 tiles with four colors per pixel. However, the four colors can refer to different color palettes on the Game Boy Color, so to use this, the tile data of the color images is not identical to the grayscale images.</p>

<p>The Game Boy Color only supports eight color palettes with four colors each. Also, not every pixel can just pick a random palette, but all pixels within an 8x8 tile are assigned to the same palette. Therefore, with a naive approach, the Game Boy Color could only display 32 colors at a time and in practice many of these colors would be duplicates because for example black is typically required in more than a single palette.</p>

<p>The trick for getting more colors is that one can edit the color palettes during hblank of each scanline. This technique has been demonstrated many times before and is commonly referred to as “hicolour”. Depending on the implementation for each line of pixels you can either replace four entire palettes or fewer random colors within any of the eight palettes. The problem with this is that the assignment of color palettes to each tile remains fixed or you would have to pay more bandwidth to also change these assignments. This leads to several possible strategies and rather unusual rules when converting images for the Game Boy Color.</p>

<p>In case of my tool, the assignment of color palettes is not updated each line, but optimized for each image and stored along with the tile data. On each line four entire palettes are replaced in an alternating pattern (palettes 0-3 on odd lines and palettes 4-7 on even lines).</p>

<p>To generate optimized images for this, the tool first uses k-means clustering to find which tiles should share the same palette indices. Then it proceeds to generate palettes line by line. Since each palette is used by some pixels of the previous or next line, palette generation takes into account all pixels in the two neighboring lines that are part of a tile which references the palette to be generated. This limitation is the most common source for artifacts as available colors can be too limited for strong contrasts along lines that run diagonally, forcing color changes within a tile and grouping of neighboring tiles.</p>

<p>Finally, tile data is written based on the already fixed palette assignments and generated palettes with the closest matching color available. So, in total the color image also has a resolution of 160x144 pixels and can theoretically hold up to 2320 colors with 5bit per RGB channel - but that claim about the number of colors leaves out a very important chunk of information on how the colors can be used.</p>

<p>By the way: Since the k-means analysis in the tools is based on random initialization, playing with the brightness/contrast sliders can lead to different results using the same settings. For some tricky images this can help to get lucky with a better result.</p>

<h3 id="could-i-get-more-than-one-image-on-a-rom-only-cartrigde-32kib">Could I get more than one image on a ROM-only cartrigde (32kiB)?</h3>

<p>Since the grayscale images are smaller than the color images and since there is a bit of extra room in bank 0 after the code, a purely grayscale cartridge could hold multiple images without a memory bank controller and the template ROM supports images in random memory locations (as long as they do not go beyond bank boundaries). I just have not implemented this for the JavaScript code as I do not expect many people to be limited to 32kiB ROMs while having a strong use case for grayscale-only slideshows. Let me know if you need this nevertheless.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[What is the easiest way to create a ROM with a slideshow of your own images for your Game Boy? How about opening a website, dragging your images into the site and hitting a generate button? That’s it. That’s what I created here. Click the image to see the video on youtube.com. Check out this rather short Youtube video to see the tool in action or just look at the top of this post to try it out yourself.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://there.oughta.be/assets/images/2025-01-10/youtube.jpg" /><media:content medium="image" url="https://there.oughta.be/assets/images/2025-01-10/youtube.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">There oughta be a Bluetooth Remote app for Sony cameras.</title><link href="https://there.oughta.be/a/bluetooth-remote" rel="alternate" type="text/html" title="There oughta be a Bluetooth Remote app for Sony cameras." /><published>2024-12-07T00:00:00+01:00</published><updated>2024-12-07T00:00:00+01:00</updated><id>https://there.oughta.be/a/bluetooth-remote</id><content type="html" xml:base="https://there.oughta.be/a/bluetooth-remote"><![CDATA[<p>Do you own a Sony camera? Then you probably know the pain of trying to use a phone to remotely trigger the camera. I gave up on using Wifi and the official Play Memories app for that. …or Imaging Edge or whatever it is called now. For quite a while I have been carrying a primitive IR remote, when I learned that my α6400 actually can be controlled via Bluetooth. So, I created the app that I wished Sony would provide. Of course it’s free and open source.</p>

<figure class="youtube">
<a href="https://youtu.be/p4NGWqdkYeQ" target="_blank">
<img src="/assets/resized/images/2024-12-07/1280/youtube.jpg" style="max-width: min(1920px, 100%)" alt="Thumbnail of the youtube video: The image shows a camera in front of cyan and green lights with its display on. In front of this is a smartwatch from which an arrow points to the camera. The arrow is labeled with the word Trigger" srcset="    /assets/resized/images/2024-12-07/640/youtube.jpg 640w,    /assets/resized/images/2024-12-07/768/youtube.jpg 768w,    /assets/resized/images/2024-12-07/1024/youtube.jpg 1024w,    /assets/resized/images/2024-12-07/1280/youtube.jpg 1280w, /assets/images/2024-12-07/youtube.jpg 1920w" sizes="(max-width: 60rem) 100vw, 60rem" />
</a>
<figcaption>Click the image to see the video on youtube.com.</figcaption>
</figure>

<p>The video is mostly about my process of going through all the options to remotely trigger my camera and, yes, I took this as an excuse to create my first “Top 5” listicle. It is a good starting point if you want to know what problem this app solves and if it is for you. This article here will go into more detail of how the app works, how to control the camera via Python, what the app can do, what it cannot do and how to automate the app from other apps like Tasker.</p>

<!--more-->

<p><em>Please note that this article contains affiliate links, i.e. links that tell the target site that I sent you there and that earn me a share of their revenue in return, i.e. as an Amazon Associate I earn from qualifying purchases. In contrast to <a href="https://youtube.com/c/thereoughtabe" target="_blank">regular external links</a>, such affiliate links are marked with a <a href="https://www.amazon.de/Genmitsu-PROVerXL-4030-3-Achsen-Graviermaschine-Kugelumlaufspindel/dp/B0C57YC46Q/ref=sr_1_2?crid=1U17F5MJP5IVT&amp;keywords=proverxl+4030+v2&amp;qid=1698789743&amp;sprefix=proverxl%252Caps%252C136&amp;sr=8-2&amp;_encoding=UTF8&amp;tag=oughtabe-21&amp;linkCode=ur2&amp;linkId=ab08ff80c388456f7dd1a0eac9b976af&amp;camp=1638&amp;creative=6742" target="_blank" class="affiliate">dollar sign</a> instead of a box.</em></p>

<p>Before we start, for those who just want the quick overview:</p>
<ul>
  <li>α-Remote is free, ad-free and open-source</li>
  <li>It requires Android 12 or newer<sup id="fnref:android" role="doc-noteref"><a href="#fn:android" class="footnote" rel="footnote">1</a></sup></li>
  <li>Source code on <a href="https://github.com/Staacks/alpharemote" target="_blank">github</a></li>
  <li>Function is comparable to <a href="https://www.amazon.de/dp/B07PLNHGT4/ref=nosim?tag=oughtabe-21" target="_blank" class="affiliate">Sony’s physical Bluetooth remote control</a></li>
  <li>Available on <a href="https://play.google.com/store/apps/details?id=org.staacks.alpharemote" target="_blank">Google Play</a> and <a href="https://f-droid.org/packages/org.staacks.alpharemote" target="_blank">F-Droid</a></li>
</ul>

<p><a href="https://f-droid.org/packages/org.staacks.alpharemote"><img src="/assets/images/2024-12-07/f-droid.png" alt="Get it on F-Droid" height="80" data-canonical-src="https://f-droid.org/badge/get-it-on.png" style="max-width: 100%;" /></a><a href="https://play.google.com/store/apps/details?id=org.staacks.alpharemote"><img src="/assets/images/2024-12-07/play.png" alt="Get it on Google Play" height="80" secured-asset-link="" style="max-width: 100%;" /></a></p>

<h2 id="what-the-app-is-for">What the app is for</h2>

<p>In 2014 I bought a Sony NEX-5T, my first camera with exchangable lens. Since then I have been locked into the Sony eco-system of cameras. Well “locked” might sound too negative. I have switched to an <a href="https://www.amazon.de/dp/B07MWDP1VD/ref=nosim?tag=oughtabe-21" target="_blank" class="affiliate">α6400</a> since then and while I am sticking to the cheaper and more portable world of the APS-C format, I have collected quite a few lenses and accessories, which makes switching to other brands expensive and complicated (actually, the formfactor of the <a href="https://www.amazon.de/dp/B0CGF84WQM/ref=nosim?tag=oughtabe-21" target="_blank" class="affiliate">a7C II</a> looks like a nice compromise to keep using light APS-C lenses on trips and use fullframe glory at home - but that price is not for me).</p>

<p>At the beginning Sony was one of few manufacturers who were taking mirror-less cameras seriously and definitely a solid choice. Now others have caught up and it seems like the differences are more in the details, habits of use and brand identity. I do not have an eye for which brand has the best color science<sup id="fnref:colors" role="doc-noteref"><a href="#fn:colors" class="footnote" rel="footnote">2</a></sup>, best IBIS, best autofocus or whatever. If I had to start over I would not mind looking at the other brands, but at the same time I am happy with what I have and what Sony delivers.</p>

<p>Except for one thing.</p>

<p>Their bloody software.</p>

<h3 id="the-problem-with-sonys-apps">The problem with Sony’s apps</h3>

<p>The α6400 has barely seen any firmware updates, its menus are terrible and several functions exclude each other<sup id="fnref:exclude" role="doc-noteref"><a href="#fn:exclude" class="footnote" rel="footnote">3</a></sup>, when I see no good reason why they should. But the worst of them all is its Wifi features. Even my old NEX-5T already had the option to transfer pictures via Wifi and remotly control the camera from my phone with a live view. Unfortunately, it sucked on the NEX-5T and it is still sucking on the α6400. It takes ages to connect to it (if connecting at all), the transfer speed is all over the place (usually slow with just enough short speed boosts to show what might have been possible) and the app feels like a patchwork of systems that barely holds together. It was bad when it was called “Play Memories”, it was bad when it was called “Imaging Edge” and frankly I cannot say if the new “Creators’ App” is any good, because for some reason my five year old α6400 is not even supported anymore - because of course it isn’t. Sony changes its Software ecosystem every few years and no need to support a camera that is still being produced.</p>

<figure>

<img src="/assets/resized/images/2024-12-07/1024/wifiremote.jpg" style="max-width: min(1096px, 50%)" alt="Photo of the backside of a Sony a6400. It shows the Wifi remote connection standby screen, which is dominated by a QR code to share the information required to connect to its Wifi hotspot." srcset="    /assets/resized/images/2024-12-07/640/wifiremote.jpg 640w,    /assets/resized/images/2024-12-07/768/wifiremote.jpg 768w,    /assets/resized/images/2024-12-07/1024/wifiremote.jpg 1024w, /assets/images/2024-12-07/wifiremote.jpg 1096w" sizes="(max-width: 60rem) calc(50/100 * 100vw), calc(50/100 * 60rem)" />

<figcaption>A well-known sight for most Sony users: The Wifi standby screen showing the access data for its local Wifi and awaiting a connection from Sony's app. Interesting detail: The a6400 shown here is still produced and sold, but it is asking you to connect with the PlayMemories app, which has been renamed to ImagingEdge, which in turn has been replaced by the new Creators' App. The latter does not even support the a6400, which has not seen a firmware update in many years.</figcaption>
</figure>

<p>But even if it was supported, I think that using WiFi as a remote control is not ideal. Yes, it has its benefits: The high bandwidth allows for a live preview and since it is based on a REST API, it is quite easy to control the camera through it like I did for my <a href="/a/photo-trap">squirrel photo trap</a>. But the big problem is that it takes a moment to set up and that it changes the networking setup of your phone. On older phones it would entirely replace whatever WiFi connection you have at the moment, but even on my Pixel 6, which can handle multiple WiFi connections at once, you can just tell how every app, including Imaging Edge itself, is confused by which network to use.</p>

<h3 id="use-bluetooth-instead">Use Bluetooth instead</h3>

<p>Because of this, I have been using a simple IR remote for a very long time, until recently I built a video booth for a friend’s wedding. He did not have the space for my massive <a href="/a/bullet-time-video-booth">bullet time video booth</a>, so I wanted a minimal setup using only my α6400, a Raspberry Pi and a preview screen. So, the idea was that the guests would trigger a countdown and decide if they want to keep the result. This means that the Raspberry Pi controls video recording of the camera to capture a 5 second clip and eventually somehow shows the result of the recording to the guests and stores it depending on their decision. So, I need control of the video recording state, while showing a preview during recording and the ability to play back the recorded video clips. Which turned out to be very tricky:</p>

<table>
<thead>
<tr>
<th></th><th>Grab video via clean HDMI</th><th>Stream via USB</th><th>Get recording via USB</th><th>Download via WiFi remote API</th><th>File transfer via WiFi-SD-Card</th>
</tr>
</thead>
<tbody>
<tr>
<th>USB control</th><td>No clean HDMI out with USB control during recording.</td><td>Preview stream has lower quality and rPi would have to handle preview rendering.</td><td>Video files can only be retrieved in mass storage mode.</td><td>The α6400 does not support video transfer via WiFi API and preview stream is low quality.</td><td>No clean HDMI out for preview (see grabbing clean HDMI).</td>
</tr>
<tr>
<th>WiFi API</th><td>Possible, but low quality compared to internal recording without a very expensive grabber.</td><td>See above, maybe not even possible with WiFi remote at the same time.</td><td>Requires manual switching to mass storage mode.</td><td>See above</td><td>Camera WiFi needs to be in AP mode and does not accept two WiFi devices, needs dual-WiFi setup.</td>
</tr>
<tr>
<th>Cable trigger</th><td>No recording state feedback making missed signals problematic. Also low quality as above.</td><td>See above</td><td>See above</td><td>See above</td><td>No recording state feedback making missed signals problematic.</td>
</tr>
<tr>
<th>IR remote</th><td>See above</td><td>See above</td><td>See above</td><td>See above</td><td>See above</td>
</tr>
<tr>
<th>Bluetooth remote</th><td>No extra hardware, but low quality as above.</td><td>See above</td><td>See above</td><td>See above</td><td><strong class="highlight">Hacky, but worked perfectly.</strong></td>
</tr>
</tbody>
</table>

<p>The table shows all the combinations of remote control and retrieving recordings I know of - and all the reasons why no combination worked. I was close to once again build a setup with a primitive cable trigger or a custom IR sender<sup id="fnref:irsender" role="doc-noteref"><a href="#fn:irsender" class="footnote" rel="footnote">4</a></sup> when I accidentally found that my α6400 had another method of remote control that I did not know of: Bluetooth.</p>

<p>The advantage for the video booth was small, but important. No need for extra hardware as the Pi already has Bluetooth support, but more importantly the camera sends its recording state, so my software could react to it. With a simple cable remote or IR sender I could only toggle the recording state and hope that no signal is ever missed. Otherwise the camera would record only when it is not supposed to - until another signal is missed.</p>

<p>But the ease of using the Bluetooth control made me realize another thing: Simple triggering from a smartphone should be done via Bluetooth. You cannot get a preview stream via Bluetooth and you cannot download the picture, but that is not the point. I just want to be able to set up the camera for a family picture, take out my phone and trigger with the press of a button without having to wait - and without letting everyone else wait. And this is what we all know from Bluetooth. Take out your Bluetooth headset and they connect to your phone pretty much on the way from the case to your ears. They literally reach your ears in time for you to hear the connect-chime.</p>

<p>So, that’s what I created. An app that connects to your camera and shows remote control buttons in your phone’s notification area within seconds of turning it on:</p>

<figure>

<video autoplay="" loop="" muted="" playsinline="">
  <source src="/assets/images/2024-12-07/demo-blog.mp4" type="video/mp4" />
</video>

<figcaption>Example of how the remote control works in Android's notification area.</figcaption>
</figure>

<p>Of course it also vanishes again as soon as you turn the camera off.</p>

<h2 id="controlling-sony-cameras-via-bluetooth">Controlling Sony cameras via Bluetooth</h2>

<p>How do you control the camera via Bluetooth? Is there a documented API by Sony? Unfortunately not. While Sony has documented its WiFi API<sup id="fnref:wifidoc" role="doc-noteref"><a href="#fn:wifidoc" class="footnote" rel="footnote">5</a></sup>, the Bluetooth API is not documented, but since it is based on Bluetooth Low Energy, it is not too hard to just look at which services and characteristics are exposed and see which characteristics can be subscribed to and what is sent by the camera. Figuring out commands to be sent to the camera can be trickier, but luckily, this work has already been done by <a href="https://github.com/coral/freemote" target="_blank">Coral</a>, <a href="https://gregleeds.com/reverse-engineering-sony-camera-bluetooth/" target="_blank">Greg Leeds</a> and <a href="https://gethypoxic.com/blogs/technical/sony-camera-ble-control-protocol-di-remote-control?srsltid=AfmBOoo9bOLHOZqLp0yAeUOamPNzfgljuiNszQWuB8CmNReazU0YLHZx" target="_blank">Mark Kirschenbaum</a>, who looked at the communication of their remote and documented what they found. Thanks a lot!</p>

<h3 id="a-few-lines-of-python">A few lines of Python</h3>

<p>And with that info, it was quite easy to trigger the camera for my simple video booth. The tricky part about proper handling of Bluetooth Low Energy is that everything happens asynchronously and you have to implement queues, take care of service discovery, handle callbacks and be prepared for failures at any point because it is a wireless connection after all. In case of the camera you also need to handle scanning, filtering of advertisement packets and pairing.</p>

<p>Luckily, for a simple video booth, you know your camera and you know the one thing it is supposed to do. Even better, you are the only user, so you can take a lot of shortcuts.</p>

<figure>

<img src="/assets/resized/images/2024-12-07/1024/pairing.png" style="max-width: min(1100px, 100%)" alt="Screenshot of the Bluetooth device list on Linux Mint. It shows a list of Bluetooth devices with their Mac addresses and next to icons representing the type of device like a phone, a speaker or a keyboard. Smaller icons are superimposed on thises device icons, representing the state of connection or pairing like a blue key or a green checkmark. One entry is highlighted, which is the paired and connected camera with the name label ILCE-6400." srcset="    /assets/resized/images/2024-12-07/640/pairing.png 640w,    /assets/resized/images/2024-12-07/768/pairing.png 768w,    /assets/resized/images/2024-12-07/1024/pairing.png 1024w, /assets/images/2024-12-07/pairing.png 1100w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>For a simple use case, the camera can just be paired with the operating system's tools (in this case Linux Mint), so a Python script only needs to handle the actual commands.</figcaption>
</figure>

<p>So, I paired the camera with system tools, hard coded its Bluetooth address as well as the relevant characteristic’s UUID and just blindly wrote the byte sequences from Coral, Greg and Mark to the command characteristics using the Python module “bleak”, which allows me to just wait for the Bluetooth communication via asyncio. The result is just a few lines of Python code to trigger the camera. In fact, most lines simply define addresses or payload data with barely any additional logic:</p>

<figure class="highlight"><pre><code class="language-py" data-lang="py"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
</pre></td><td class="code"><pre><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">from</span> <span class="nn">bleak</span> <span class="kn">import</span> <span class="n">BleakClient</span><span class="p">,</span> <span class="n">BleakGATTCharacteristic</span>

<span class="n">bleAddress</span> <span class="o">=</span> <span class="s">"AA:BB:CC:DD:EE:FF"</span> <span class="c1"># Bluetooth address of the camera
</span><span class="n">bleCmdUUID</span> <span class="o">=</span> <span class="s">"0000ff01-0000-1000-8000-00805f9b34fb"</span> <span class="c1"># UUID for command characteristic
</span>
<span class="c1"># Camera commands, see https://gregleeds.com/reverse-engineering-sony-camera-bluetooth/
</span><span class="n">blePayloadFocusDown</span> <span class="o">=</span> <span class="nb">bytearray</span><span class="p">([</span><span class="mh">0x01</span><span class="p">,</span><span class="mh">0x07</span><span class="p">])</span> 
<span class="n">blePayloadFocusUp</span> <span class="o">=</span> <span class="nb">bytearray</span><span class="p">([</span><span class="mh">0x01</span><span class="p">,</span><span class="mh">0x06</span><span class="p">])</span>
<span class="n">blePayloadTriggerDown</span> <span class="o">=</span> <span class="nb">bytearray</span><span class="p">([</span><span class="mh">0x01</span><span class="p">,</span><span class="mh">0x09</span><span class="p">])</span> 
<span class="n">blePayloadTriggerUp</span> <span class="o">=</span> <span class="nb">bytearray</span><span class="p">([</span><span class="mh">0x01</span><span class="p">,</span><span class="mh">0x08</span><span class="p">])</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">demo</span><span class="p">():</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">BleakClient</span><span class="p">(</span><span class="n">bleAddress</span><span class="p">)</span> <span class="k">as</span> <span class="n">client</span><span class="p">:</span>
        <span class="c1">#Press shutter button half and then full
</span>        <span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="n">write_gatt_char</span><span class="p">(</span><span class="n">bleCmdUUID</span><span class="p">,</span> <span class="n">blePayloadFocusDown</span><span class="p">)</span>
        <span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="n">write_gatt_char</span><span class="p">(</span><span class="n">bleCmdUUID</span><span class="p">,</span> <span class="n">blePayloadTriggerDown</span><span class="p">)</span>

        <span class="c1">#Short wait as we do not observe focus state. (You might want to observe focus and/or shutter state instead)
</span>        <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.2</span><span class="p">)</span>

        <span class="c1">#Release shutter button
</span>        <span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="n">write_gatt_char</span><span class="p">(</span><span class="n">bleCmdUUID</span><span class="p">,</span> <span class="n">blePayloadTriggerUp</span><span class="p">)</span>
        <span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="n">write_gatt_char</span><span class="p">(</span><span class="n">bleCmdUUID</span><span class="p">,</span> <span class="n">blePayloadFocusUp</span><span class="p">)</span>

        <span class="c1">#Disconnect, in most real applications you would stay connected
</span>        <span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="n">disconnect</span><span class="p">()</span>

<span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">demo</span><span class="p">())</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>A real application (and my video booth) should of course stay connected, automatically reconnect and maybe also subscribe to the status characteristic.</p>

<h3 id="companion-apps-and-android">Companion apps and Android</h3>

<p>Speaking of a real application, this became a bit more complicated when implementing these features as an Android app. Now the code runs without me anywhere nearby on devices that I have never personally seen in my life - and probably never will. So, I now have to handle scanning, pairing and any kind of error caused by software, hardware and wetware. I will not go into all the details of the implementation, which is <a href="https://github.com/Staacks/alpharemote" target="_blank">entirely open on github</a> and it suffices to know tha BLE on Android <a href="https://www.youtube.com/watch?v=bnyoTfro6w4&amp;ab_channel=AndroidMakers" target="_blank">sucks</a>.</p>

<p>However, there is one important consideration that I want you to be aware of, that is the concept of “companion apps” and “companion services”. If you look at Sony’s Imaging Edge app, you will find one Bluetooth feature for geotagging. If you enable it, the app will stay active all the time waiting for you camera and whenever it is turned on, Sony’s app will notify you (including an entirely pointless vibration), write the current location to the camera and after you turn off the camera, the app stays around in your notification area in case your camera reappears.</p>

<p>Beautiful. Not only do I spend 95% of my day with my camera being not even in the same room, most of the time when I use it I do not require an affirmative vibration from the phone in my pocket whenever I turn it on. The reason Sony does it like this, is that for old Android versions this was the only way to automatically connect to a device when it is turned on.</p>

<p>Luckily, Android 8 introduced an alternative in 2017. The so-called Companion Device Manager (CDM) is a system service that allows apps to associate themselves with a specific device (with the user’s permission), making paring and connecting much easier. But, more importantly, with Android 12 in 2021 it allowed apps to observe the device’s presence and to implement a Companion Device Service. What this means is that now the CDM keeps looking for the device and it launches the app’s service as soon as the device appears. If the device is turned off (or goes out of range) the CDM terminates the service again.</p>

<p>That’s why my app targets Android 12 or newer. It allows my app to stay entirely inactive when you are not using your camera. Unless you normally turn off Bluetooth entirely, your phone is keeping an ear open anyway and my app does not use any extra resources. My app will only be activated when your camera is nearby and turned on and the system kills it again as soon as the camera is gone.</p>

<h2 id="what-the-app-can-do">What the app can do</h2>

<p>Ok, great, so the app plays nice with my phone’s resources and is just there when it is needed. But what does it do?</p>

<p>It can do anything that <a href="https://www.amazon.de/dp/B07PLNHGT4/ref=nosim?tag=oughtabe-21" target="_blank" class="affiliate">Sony’s remote</a> can do, which is emulating button presses on the camera. It can press the shutter, the recording button, Zoom in/out, Focus in/out, the C1 button and the “AF On” button (that is the hold button in the AF/MF or AEL lock select switch or whatever that thing is called).</p>

<figure>

<img src="/assets/resized/images/2024-12-07/1280/appscreens.png" style="max-width: min(4380px, 100%)" alt="This image is a set of four vertical screenshots of the app aligned horizontally next to each other. From left to right the first one shows an interface with various remote control buttons. This is dominated by a large shutter button near the top center, accompanied with a focus and 3s self timer button and a red record button below. Further down are buttons labelled C1, AF On and pairs of Focus In/Out and Zoom In/Out controls. At the bottom is a row of additional controls (which can be defined by the user) and below that navigation buttons with labels Camera, Settings and About. Camera is highlighted. The next screenshot is similar to the first on, but the row of custom buttons has been pulled up to reveal controls for a bulb mode and an intervalometer function. Each features a start button and edit boxes to configure durations and/or number of exposures. The third screenshot shows various settings to add or remove a camera pairing, to add, edit or rearrange custom buttons in the notification area, a control for the size of these buttons and a switch to allow other apps to control this app. The last screenshot shows a dialog to customize one of the custom buttons, allowing to switch the associated action, currently offering a hold function with associated duration slider and a speed control." srcset="    /assets/resized/images/2024-12-07/640/appscreens.png 640w,    /assets/resized/images/2024-12-07/768/appscreens.png 768w,    /assets/resized/images/2024-12-07/1024/appscreens.png 1024w,    /assets/resized/images/2024-12-07/1280/appscreens.png 1280w, /assets/images/2024-12-07/appscreens.png 4380w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>Controls in the main app from left to right: 1. Main remote control interface as alternative to the buttons set for the notification area (which also appear as a row at the bottom of this screen). 2. The bottom part can be dragged upwards to reveal advanced functions like bulb mode and an intervalometer. 3. Settings page with options to define the custom buttons for the notification area. 4. Example dialog for one of the custom buttons. In this case, it is a zoom jog button with additional options like a hold duration and a zoom speed.</figcaption>
</figure>

<p>You can pick any of these buttons, add a self-timer or toggle mode if you like, and put them in your notification area. There is also a dedicated remote control and I have added some automations around these button presses, like a tool for bulb exposure and a simple intervalometer.</p>

<h3 id="three-kinds-of-shutter">Three kinds of shutter</h3>

<p>Unfortunately, these are just blind button presses. The camera does neither report the state of a button nor what function you might have mapped to it. My app has no idea what the “AF On” button does in your setup - it just presses it. The camera does not even report if it is in MF or any AF mode, so labels or behavior cannot be adapted accordingly. The only thing it reports is whether an autofocus has been acquired, whether the shutter is open and whether a video recording is active.</p>

<p>This leads to some problems even for simple triggering: The buttons in the notification area can only be tapped, but they do not distinguish how long you press them (for reasons<sup id="fnref:remoteviews" role="doc-noteref"><a href="#fn:remoteviews" class="footnote" rel="footnote">6</a></sup>). So, you cannot hold the trigger until the shutter clicks and the same is true for a self-timer. The app needs to figure out how long to press the shutter.</p>

<p>But how does it know when to release the shutter? Well, that is a problem and I have implemented a total of three kinds of shutter that are ideal for different camera settings:</p>
<ul>
  <li>The simple shutter button. If you are using the full remote view of the app, you can simply press and hold the trigger to your liking, which is the same as pressing the shutter button on your camera (except that half-pressing it is a separate icon). So, if you do not need the notification area and no self-timer, that’s the most natural one. You can also use it with a defined hold duration if you are in a situation where the autofocus is fast enough. However, if you hold it too short and the focus does not lock in time, your camera will not take a picture.</li>
  <li>The “Trigger Once” function holds the shutter button until the camera reports that the shutter has been opened. This is good for most self-timer needs as it guarantees to trigger the camera as long as it is able to trigger at all. It just presses that button until there is a result. The only problem is that the report of the shutter status is slower than the faster burst modes. So if your camera is set to burst, this will take multiple photos. In my opinion, this is still fine for most self-timer situations (probably even desired if you have burst mode on), but it can be annoying.</li>
  <li>The “Trigger on focus” function reliably takes a single photo even in burst mode. It half-presses the shutter button and waits for the camera to report that the autofocus has acquired a lock. Then it fully presses the shutter very shortly. The problem with this mode is that it will fail entirely when you are focussing manually, because then there is no autofocus lock.</li>
</ul>

<h3 id="what-the-app-cannot-do">What the app cannot do</h3>

<p>So, with all that in mind, what are the app’s limitations? Well, there is quite a list.</p>

<p>It lacks all the big features that cannot be done via Bluetooth: No preview image and no image transfer. It also lacks any kind of direct control over settings. It cannot set shutter time, ISO, whitebalance or anything like that, because it can only send button presses. It cannot send logical commands<sup id="fnref:future" role="doc-noteref"><a href="#fn:future" class="footnote" rel="footnote">7</a></sup>.</p>

<p>This also applies to anything it actually can control: For example, there is no feedback from the focus in/out buttons and the amount it moves the focus depends on the lens being used. Therefore, there is no good way to implement focus bracketing. The app could in theory press the right buttons, but it cannot reliably move the focus to specific positions or even return to its original position.</p>

<p>So, you should think of this app as a handy small remote trigger. Like the physical remote controls (Bluetooth or IR) that you can buy from Sony. You just don’t have to carry it.</p>

<h3 id="intents">Intents</h3>

<p>Well, there actually is one more interesting thing the app can do. In the settings view you can find an option to allow other apps to send commands via broadcasted intents. What is that? Intents are messages that apps can send to other apps. One app (or its part) might send a very specific intent to a specific different (part of an) app to trigger a function or transfer data. But an app can also listen for broadcasts that could come from any app and which may or may not be directed at a specific app. An example is your messaging app that tries to open an attached image when you tap on it. It will broadcast that it intends to open an image and any app that is able to open it may offer its service.</p>

<p>In case of my remote app, this will allow other apps to send an intent to trigger a command on the camera. This means that any other app may now be used as a remote trigger for your camera by going through my remote app. If you combine this with an app like <a href="https://tasker.joaoapps.com/" target="_blank">Tasker</a>, you can create any kind of automation and almost everything can become a remote for your camera. Tasker can use almost anything to start an automation and it can be configured to send a trigger intent to my app. Now anything can become a trigger. Your smartwatch, a smart motion detector, a text message to your phone - whatever you can think of. You can set Tasker to trigger your camera when your alarm clock rings on a rainy Tuesday if that’s what you want.</p>

<figure>

<img src="/assets/resized/images/2024-12-07/1280/watch.jpg" style="max-width: min(1412px, 100%)" alt="Photo of the back side of a Sony a6400 with active screen. In front of this is a wrist with a smartwatch with e-ink screen that shows the words Trigger, Selftimer and Record next to its physical buttons. A second hand is holding the watch between finger and thumb, resting the index finger on the button next to the word Trigger." srcset="    /assets/resized/images/2024-12-07/640/watch.jpg 640w,    /assets/resized/images/2024-12-07/768/watch.jpg 768w,    /assets/resized/images/2024-12-07/1024/watch.jpg 1024w,    /assets/resized/images/2024-12-07/1280/watch.jpg 1280w, /assets/images/2024-12-07/watch.jpg 1412w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>Control the α-Remote app via broadcasted intents allows to use all kinds of devices and automations as a trigger. Here it is a Fossil smartwatch via Gadgetbridge and Tasker.</figcaption>
</figure>

<p>The setup for tasker and any similar app should not be too complicated. You need to send an intent to the package <code class="language-plaintext highlighter-rouge">org.staacks.alpharemote</code> with the action <code class="language-plaintext highlighter-rouge">org.staacks.alpharemote.EXT_BUTTON</code>.</p>

<p>You also need to set an “extra” with the name <code class="language-plaintext highlighter-rouge">preset</code> to a value that represents one of the buttons (or functions). These are:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">STOP</code></li>
  <li><code class="language-plaintext highlighter-rouge">SHUTTER_HALF</code></li>
  <li><code class="language-plaintext highlighter-rouge">SHUTTER</code></li>
  <li><code class="language-plaintext highlighter-rouge">TRIGGER_ONCE</code></li>
  <li><code class="language-plaintext highlighter-rouge">TRIGGER_ON_FOCUS</code></li>
  <li><code class="language-plaintext highlighter-rouge">RECORD</code></li>
  <li><code class="language-plaintext highlighter-rouge">AF_ON</code></li>
  <li><code class="language-plaintext highlighter-rouge">C1</code></li>
  <li><code class="language-plaintext highlighter-rouge">ZOOM_IN</code></li>
  <li><code class="language-plaintext highlighter-rouge">ZOOM_OUT</code></li>
  <li><code class="language-plaintext highlighter-rouge">FOCUS_FAR</code></li>
  <li><code class="language-plaintext highlighter-rouge">FOCUS_NEAR</code></li>
</ul>

<p>(Note, that the app also accepts these in lower case.)</p>

<p>You can also set more “extras” to control optional additional parameters:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">toggle</code> should be a boolean extra that controls if the button is just pressed or toggled on/off.</li>
  <li><code class="language-plaintext highlighter-rouge">selftimer</code> should be a floating point extra that adds a self-timer in seconds.</li>
  <li><code class="language-plaintext highlighter-rouge">duration</code> should be a floating point extra that sets how long the button should be pressed in seconds.</li>
  <li><code class="language-plaintext highlighter-rouge">step</code> is a floating point extra in the range from 0.0 to 1.0 that determines the speed of jog controls like zoom or focus.</li>
  <li><code class="language-plaintext highlighter-rouge">down</code> is a boolean extra that determines if a “down” event should be part of the command. This defaults to “true” meaning that pressing the button down is part of it, but if you set it to false (and leave <code class="language-plaintext highlighter-rouge">up</code> as it is) you can send a key release only.</li>
  <li><code class="language-plaintext highlighter-rouge">up</code> is like down, just for the <code class="language-plaintext highlighter-rouge">up</code> event. It also defaults to true, so by default the intent would send a button press and release. But if you set <code class="language-plaintext highlighter-rouge">up</code> to false, you could send a button press without releasing it.</li>
</ul>

<figure>

<img src="/assets/resized/images/2024-12-07/1280/tasker.png" style="max-width: min(2220px, 100%)" alt="Two screenshot. The left screenshot is from the app Tasker, showing various settings for one of its actions. The right screenshot is a crop of the settings page in α-Remote, showing the toggle that enables control from other apps." srcset="    /assets/resized/images/2024-12-07/640/tasker.png 640w,    /assets/resized/images/2024-12-07/768/tasker.png 768w,    /assets/resized/images/2024-12-07/1024/tasker.png 1024w,    /assets/resized/images/2024-12-07/1280/tasker.png 1280w, /assets/images/2024-12-07/tasker.png 2220w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>Left: Settings in Tasker to allow sending an intent to α-Remote which would start a 3 second selftimer and then execute the “trigger once” action. Right: The toggle that needs to be enabled in α-Remote to allow other apps to control it.</figcaption>
</figure>

<p>Note that Tasker does not specifically distinguish between different data types for its “extras”, but it seems to infer them from the value. For example, in order to send a “trigger once” with a self-timer of three seconds, you would set one “extra” to <code class="language-plaintext highlighter-rouge">preset:trigger_once</code> and another “extra” to <code class="language-plaintext highlighter-rouge">selftimer:3.0</code> to get a string extra and floating point extra, respectively. (Not <code class="language-plaintext highlighter-rouge">selftimer:3</code> without the decimal point.)</p>

<h2 id="go-get-it">Go get it</h2>

<p>Ok, that’s it. Keep in mind that this is more like a handy little remote trigger than a full-featured remote control.</p>

<p>If you like that and your camera supports Bluetooth remotes, get the app on Google Play or F-Droid:</p>

<p><a href="https://f-droid.org/packages/org.staacks.alpharemote"><img src="/assets/images/2024-12-07/f-droid.png" alt="Get it on F-Droid" height="80" data-canonical-src="https://f-droid.org/badge/get-it-on.png" style="max-width: 100%;" /></a><a href="https://play.google.com/store/apps/details?id=org.staacks.alpharemote"><img src="/assets/images/2024-12-07/play.png" alt="Get it on Google Play" height="80" secured-asset-link="" style="max-width: 100%;" /></a></p>

<p>Also keep in mind that I only have one camera and very few phones to test it with, so bear with me if you encounter issues and help me to fix them. Check the <a href="https://github.com/Staacks/alpharemote" target="_blank">Readme on github</a> for common problems and report issues on <a href="https://github.com/Staacks/alpharemote/issues" target="_blank">the issue tracker</a>.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:android" role="doc-endnote">
      <p>Since I primarily wrote this app because I wanted this solution, I targeted the mobile OS that I use (Android 15) and checked how far I can extend it for others without too much additional work. So, no iOS<sup id="fnref:iOS" role="doc-noteref"><a href="#fn:iOS" class="footnote" rel="footnote">8</a></sup> and no Android 11 or below<sup id="fnref:oldandroid" role="doc-noteref"><a href="#fn:oldandroid" class="footnote" rel="footnote">9</a></sup>. <a href="#fnref:android" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:colors" role="doc-endnote">
      <p>I still don’t have a good eye for colors. Just like when I mix audio, I quickly adapt to what I am hearing/seeing and when I check what I did the next day, I really cannot believe how this sounded/looked good to me the day before. I have never been happy with the color grading of my videos and I cannot tell for the life of me where the difference in “Color science” are for those cameras. <a href="#fnref:colors" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:exclude" role="doc-endnote">
      <p>One example since we are on the topic of remotes: You can only use the Bluetooth remote, WiFi remote or IR remote and you have to manually enable one of them on entirely different settings screens. Oh, and you cannot use Bluetooth for Geotagging and as a remote control at the same time for some reason. You have to pick one. <a href="#fnref:exclude" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:irsender" role="doc-endnote">
      <p>Which actualy worked perfectly with the NEX-5T on my own wedding. <a href="#fnref:irsender" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:wifidoc" role="doc-endnote">
      <p>And deprecated, replaced and… Not entirely sure about the current state. I have a local copy of the documentation for the α6400 but could only find the <a href="https://support.d-imaging.sony.co.jp/app/sdk/en/index.html" target="_blank">new API version</a> for the newer cameras. It’s probably like their app support: Still selling the model is no reason not to drop support for it. <a href="#fnref:wifidoc" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:remoteviews" role="doc-endnote">
      <p>Damn, you are curious. Well, the reason is that the notification area is not drawn by the app, but by the system and the app hands over so-called remote views to the system, which it can draw. These remote views are much more limited than anything drawn by the app itself, but more importantly anything triggered by user interaction will not be executed in the app. Instead you also hand over a pending intent which allows the system to trigger an action in your app when a button is pressed. This does only supports a simple click (or tap), but not “down” or “up”. <a href="#fnref:remoteviews" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:future" role="doc-endnote">
      <p>Actually, there are more services on newer cameras that might allow for more control. But so far, there does not even seem to be a Sony remote that uses this and my α6400 does not have this characteristic, so I cannot even do wild guesses. <a href="#fnref:future" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:iOS" role="doc-endnote">
      <p>Some of you might know that I actually also develop an iOS app as part of my day job. But I do not use Apple devices in my free time<sup id="fnref:noapple" role="doc-noteref"><a href="#fn:noapple" class="footnote" rel="footnote">10</a></sup>. <a href="#fnref:iOS" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:oldandroid" role="doc-endnote">
      <p>When targeting older Androids, I cannot use the Companion Device Service, which was introduced with Android 12. It is possible to extend the app for older Android versions, but it will require some work and a lot of testing on older devices. If someone wants to look into it, I will be happy to see a pull request. <a href="#fnref:oldandroid" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:noapple" role="doc-endnote">
      <p>No, I really don’t like their devices. The “it just works” philosophy ends where Apple did not expect you to go, where it is replaced by a “not possible” philosophy. Besides, Apple’s stuff just seems to break for me even when I am only doing what they expected me to do. So, I only wrote this app for Android<sup id="fnref:reallynoiOS" role="doc-noteref"><a href="#fn:reallynoiOS" class="footnote" rel="footnote">11</a></sup>. <a href="#fnref:noapple" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:reallynoiOS" role="doc-endnote">
      <p>I know you would pay for that. Many Apple users have told me even before I fully released it. But it isn’t that easy<sup id="fnref:iOScost" role="doc-noteref"><a href="#fn:iOScost" class="footnote" rel="footnote">12</a></sup>. <a href="#fnref:reallynoiOS" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:iOScost" role="doc-endnote">
      <p>Ok, if you have to know: In order to publish an app for iOS, you need to pay for a developer account<sup id="fnref:noiOSsidechannel" role="doc-noteref"><a href="#fn:noiOSsidechannel" class="footnote" rel="footnote">13</a></sup>, a Mac<sup id="fnref:mac" role="doc-noteref"><a href="#fn:mac" class="footnote" rel="footnote">14</a></sup> and an iPhone. But most importantly, I only invest the little spare time that my family and my full job leave me and money isn’t giving me that time back. Speaking of time, I should quit that nonsense with these footnotes<sup id="fnref:footnotes" role="doc-noteref"><a href="#fn:footnotes" class="footnote" rel="footnote">15</a></sup> and continue with the article<sup id="fnref:stopreading" role="doc-noteref"><a href="#fn:stopreading" class="footnote" rel="footnote">16</a></sup>. <a href="#fnref:iOScost" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:noiOSsidechannel" role="doc-endnote">
      <p>There still is no other way to distribute apps. No reasonable way anyway. When the EU tried to force Apple to allow third-party stores they threw a tantrum and made up some insane rules that all the Apple believers now defend as if it was just to protect them. <a href="#fnref:noiOSsidechannel" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:mac" role="doc-endnote">
      <p>Because why should Apple allow developing apps for iOS on any other computer… <a href="#fnref:mac" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:footnotes" role="doc-endnote">
      <p>Or are these endnotes? It’s the same on a single page article, isn’t it? <a href="#fnref:footnotes" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:stopreading" role="doc-endnote">
      <p>But you also could just have stopped reading them long ago. Thanks for paying so much attention! <a href="#fnref:stopreading" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[Do you own a Sony camera? Then you probably know the pain of trying to use a phone to remotely trigger the camera. I gave up on using Wifi and the official Play Memories app for that. …or Imaging Edge or whatever it is called now. For quite a while I have been carrying a primitive IR remote, when I learned that my α6400 actually can be controlled via Bluetooth. So, I created the app that I wished Sony would provide. Of course it’s free and open source. Click the image to see the video on youtube.com. The video is mostly about my process of going through all the options to remotely trigger my camera and, yes, I took this as an excuse to create my first “Top 5” listicle. It is a good starting point if you want to know what problem this app solves and if it is for you. This article here will go into more detail of how the app works, how to control the camera via Python, what the app can do, what it cannot do and how to automate the app from other apps like Tasker.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://there.oughta.be/assets/images/2024-12-07/youtube.jpg" /><media:content medium="image" url="https://there.oughta.be/assets/images/2024-12-07/youtube.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">There oughta be a 37c3 talk and music</title><link href="https://there.oughta.be/a/37c3-talk" rel="alternate" type="text/html" title="There oughta be a 37c3 talk and music" /><published>2023-12-30T00:00:00+01:00</published><updated>2023-12-30T00:00:00+01:00</updated><id>https://there.oughta.be/a/37c3-and-music</id><content type="html" xml:base="https://there.oughta.be/a/37c3-talk"><![CDATA[<p>Just a quick update on two things that happened recently without being a new projekt:</p>

<ul>
  <li>I gave a talk at the 37th Chaos Communication Congress about how the GB Interceptor can reconstruct the Game Boy’s screen content by listening in on the memory bus. The recording is available at <a href="https://media.ccc.de/v/37c3-11928-reconstructing_game_footage_from_a_game_boy_s_memory_bus" target="_blank">media.ccc.de</a>.</li>
</ul>

<figure class="videolink">
<a href="https://media.ccc.de/v/37c3-11928-reconstructing_game_footage_from_a_game_boy_s_memory_bus" target="_blank">
<img src="/assets/resized/images/2023-12-30/1024/thumb.jpg" style="max-width: min(1267px, 100%)" alt="Screenshot from the talk recording, showing a slide and the speaker to the right. The slide shows a Game Boy along with a live image from a Game Boy." srcset="    /assets/resized/images/2023-12-30/640/thumb.jpg 640w,    /assets/resized/images/2023-12-30/768/thumb.jpg 768w,    /assets/resized/images/2023-12-30/1024/thumb.jpg 1024w, /assets/images/2023-12-30/thumb.jpg 1267w" sizes="(max-width: 60rem) 100vw, 60rem" />
</a>
<figcaption>Click the image to see the video on media.ccc.de.</figcaption>
</figure>

<ul>
  <li>Because some asked for the music from my recent video about the <a href="/a/wooden-game-boy">wooden Game Boy</a> (some as in a handful, I am not under the delusion that the world was waiting for my video soundtrack) I have uploaded it as a pure mp3 as well as making it available on common streaming services. You can find more details on <a href="/music">there.oughta.be/music</a>.</li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[Just a quick update on two things that happened recently without being a new projekt: I gave a talk at the 37th Chaos Communication Congress about how the GB Interceptor can reconstruct the Game Boy’s screen content by listening in on the memory bus. The recording is available at media.ccc.de. Click the image to see the video on media.ccc.de. Because some asked for the music from my recent video about the wooden Game Boy (some as in a handful, I am not under the delusion that the world was waiting for my video soundtrack) I have uploaded it as a pure mp3 as well as making it available on common streaming services. You can find more details on there.oughta.be/music.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://there.oughta.be/assets/images/2023-12-30/thumb.jpg" /><media:content medium="image" url="https://there.oughta.be/assets/images/2023-12-30/thumb.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">There oughta be a wooden Game Boy.</title><link href="https://there.oughta.be/a/wooden-game-boy" rel="alternate" type="text/html" title="There oughta be a wooden Game Boy." /><published>2023-11-03T00:00:00+01:00</published><updated>2023-11-03T00:00:00+01:00</updated><id>https://there.oughta.be/a/wooden-game-boy</id><content type="html" xml:base="https://there.oughta.be/a/wooden-game-boy"><![CDATA[<p>You made me create a wooden Game Boy shell. Yes, you made me do it. After I got myself a CNC machine a while back I just made a <a href="https://www.youtube.com/shorts/ePvpYVNEvzk" target="_blank">wooden Game Boy cartridge</a> to get to know the machine, but social media wanted more. So, I made a Game Boy out of walnut wood.</p>

<figure class="youtube">
<a href="https://youtu.be/rECMivhOat4" target="_blank">
<img src="/assets/resized/images/2023-11-03/1280/youtube.jpg" style="max-width: min(1920px, 100%)" alt="Thumbnail of the youtube video: The center shows a Game Boy with a dark wooden shell, white buttons and a white screen bezel. Its display shows the title screen of Tetris. Left of the Game Boy is a dark wooden Game Boy cartridge with a carved label reading Pokemon Walnut Version. To the right there is a brighter wooden cartridge with a label reading Pokemon Oak Version." srcset="    /assets/resized/images/2023-11-03/640/youtube.jpg 640w,    /assets/resized/images/2023-11-03/768/youtube.jpg 768w,    /assets/resized/images/2023-11-03/1024/youtube.jpg 1024w,    /assets/resized/images/2023-11-03/1280/youtube.jpg 1280w, /assets/images/2023-11-03/youtube.jpg 1920w" sizes="(max-width: 60rem) 100vw, 60rem" />
</a>
<figcaption>Click the image to see the video on youtube.com.</figcaption>
</figure>

<p>You can see the process in the video without much explanation. If you want to learn about the toolpaths, my reasoning behind a few things, lessons learned and what you should avoid if you try this yourself, read this blog post instead and check out my <a href="https://github.com/Staacks/wooden-game-boy" target="_blank">design files on GitHub</a>.</p>

<!--more-->

<p><em>Please note that this article contains affiliate links, i.e. links that tell the target site that I sent you there and that earn me a share of their revenue in return, i.e. as an Amazon Associate I earn from qualifying purchases. In contrast to <a href="https://youtube.com/c/thereoughtabe" target="_blank">regular external links</a>, such affiliate links are marked with a <a href="https://www.amazon.de/Genmitsu-PROVerXL-4030-3-Achsen-Graviermaschine-Kugelumlaufspindel/dp/B0C57YC46Q/ref=sr_1_2?crid=1U17F5MJP5IVT&amp;keywords=proverxl+4030+v2&amp;qid=1698789743&amp;sprefix=proverxl%252Caps%252C136&amp;sr=8-2&amp;_encoding=UTF8&amp;tag=oughtabe-21&amp;linkCode=ur2&amp;linkId=ab08ff80c388456f7dd1a0eac9b976af&amp;camp=1638&amp;creative=6742" target="_blank" class="affiliate">dollar sign</a> instead of a box.</em></p>

<h2 id="a-wooden-game-boy">A wooden Game Boy</h2>

<p>This project mostly began as a user request. When I got myself a CNC router a few months ago, one of the little exercises I did was a wooden Game Boy cartridge (see <a href="https://www.youtube.com/shorts/ePvpYVNEvzk" target="_blank">Youtube short clip</a> or <a href="https://github.com/Staacks/wooden-game-boy-cartridge" target="_blank">project files on GitHub</a>). It was a mix of “let’s see if I can do that” and “sure the retro gamers on social media will love this”, but I did not expect how much they would love it and of course they asked for more. So, I did some testing, spent some time and several weeks later I find myself in the possession of a Game Boy with a beautiful walnut wood shell.</p>

<figure>

<img src="/assets/resized/images/2023-11-03/1280/gameboys.jpg" style="max-width: min(3000px, 100%)" alt="Photo of the wooden Game Boy next to an original Game Boy." srcset="    /assets/resized/images/2023-11-03/640/gameboys.jpg 640w,    /assets/resized/images/2023-11-03/768/gameboys.jpg 768w,    /assets/resized/images/2023-11-03/1024/gameboys.jpg 1024w,    /assets/resized/images/2023-11-03/1280/gameboys.jpg 1280w, /assets/images/2023-11-03/gameboys.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>The wooden Game Boy next to its iconic ancestor.</figcaption>
</figure>

<p>I mean, look at that thing. Isn’t it gorgeous? So, if you want to learn how exactly it was built, read on. If you just want to see some CNC timelapses and detail shots of the wooden Game Boy, you should probably watch the <a href="https://youtu.be/rECMivhOat4" target="_blank">Youtube video</a> instead.</p>

<h2 id="important-notes">Important notes</h2>

<p>Before we go into details, there are some general things you should know to properly judge the results. At least if you plan to reproduce the Game Boy, you should read these carefully.</p>

<ul>
  <li>I am a total noob. Yeah, I know that Game Boy does not look like the work of a noob and I would like to believe that I have proven that I can use a CNC router, but the noobness does not necessarily refer to the complexity of the project or my ability to wield some 3d software. This is more about best practices and hardware suggestions. I document what I did and which tools I used, but you should be aware that what I did mostly uses the first method I learned and the tools are the first tools of this kind that I own. They worked to create that Game Boy, but I lack the experience to tell if they are the best or even good methods/tools for this job.</li>
  <li>Related to this, I barely have any experience with wood. I have applied what I learned from some videos, but I have no idea how long that Game Boy will last. Wood tends to grow, shrink and bend over time, especially if exposed to changing humidity. This Game Boy could become something that I pass along to my grand children or it could crack open tomorrow. I literally have no experience to make any assumptions about this.</li>
  <li>The project is far from perfect and many design choices were made as it went along. At the end I knew some things better than at the beginning and some later toolpaths are designed to fix problems of the earlier one. The Game Boy you see here is my successful first attempt, but this also means that the design files have not been polished or tested a second time.</li>
  <li>I designed this Game Boy around aftermarket parts and especially for using an IPS mod. It should also fit original displays, but I have not tried that.</li>
  <li>In the end I closed the case with wood glue, because the original screws did not hold it together properly. So, there currently is no way to open it for maintenance. Read the notes of the drilling toolpaths for more info and thoughts on the problem.</li>
</ul>

<h2 id="project-files-and-software">Project files and software</h2>

<p>Let’s start with the bad news for most of those who already have a CNC machine and just want to reproduce my work: I use the Blender CAM plugin. Yes, there is a CNC CAM plugin for Blender and it is free. Yes, it is quite versatile and powerful. Yes, using it is a pain in the ass even if you are used to Blender.</p>

<p>Still, I love it and the CAM alternatives that seem to allow me to do similar things are very expensive<sup id="fnref:linux" role="doc-noteref"><a href="#fn:linux" class="footnote" rel="footnote">1</a></sup>. So, I ended up using Blender CAM, which unfortunately means that many CNC users out there will cry before having to copy my models to their CAM to painstakingly recreate the toolpaths there.</p>

<p>Therefore, the project files are <em>blend</em> files <a href="https://github.com/Staacks/wooden-game-boy" target="_blank">on GitHub</a> and I release everything under the Creative Commons Attribution 4.0 license. Do what ever you like with it, just make sure to mention me as the source. …and maybe keep in mind that Nintendo might want to have a word about trademarks if you try to sell these with their logo on it.</p>

<p>In addition to the <em>blend</em> files I have also uploaded the gcode files that I actually used. They are for <a href="https://github.com/grbl/grbl" target="_blank">grbl-based machines</a>, but even if your CNC uses a grbl controller it is a bad idea to just feed them to your machine. You will have to adapt it to the equipment that you actually use and I only added them as reference to review and compare.</p>

<p>Speaking of equipment-specific things: The feeds and cutting depths set in the <em>blend</em> files are for the weak spindle on my machine. If your spindle is stronger, you can quite certainly go a lot faster. But also keep in mind that the <strong>speeds in the <em>blend</em> files do not have any meaning</strong>. My spindle is not digitally controlled and since it has little torque at lower speeds I pretty much used it at its maximum of 10,000 rpm. You will have to adapt this to your machine and your bits.</p>

<h2 id="the-hardware-i-used">The hardware I used</h2>

<p>Remember that I only got my CNC machine a few months ago, so do not expect comparative insights here. I will document what I use and try to explain a few things to people who have never used a CNC before. If you have a CNC and are just interested in the toolpaths, you can probably skip to the table with bits I used.</p>

<h3 id="the-cnc-machine">The CNC machine</h3>

<p>If you have never looked at CNC machines before, this probably looks like a very fancy and professional setup. If you know about CNC machines, you probably recognize immediately that it isn’t. My machine is a <a href="https://www.amazon.de/Genmitsu-PROVerXL-4030-3-Achsen-Graviermaschine-Kugelumlaufspindel/dp/B0C57YC46Q/ref=sr_1_2?crid=1U17F5MJP5IVT&amp;keywords=proverxl+4030+v2&amp;qid=1698789743&amp;sprefix=proverxl%252Caps%252C136&amp;sr=8-2&amp;_encoding=UTF8&amp;tag=oughtabe-21&amp;linkCode=ur2&amp;linkId=ab08ff80c388456f7dd1a0eac9b976af&amp;camp=1638&amp;creative=6742" target="_blank" class="affiliate">Genmitsu PROVerXL 4030 V2</a>, which is the toughest Genmitsu CNC router, but in the world of hobbyist CNC machines ranks somewhere in the middle. It is much more rigid than the small 3D printer like machines (that Genmitsu machines are often associated with), but much smaller and weaker than machines by more prestigious brands.</p>

<p>As a beginner I think that it is extremely capable for my needs, but its weak spot is its stock 400W spindle. Compared to stronger machines you will find that my toolpaths take much longer as I am doing significantly shallower cuts. Also the spindle is pretty much limited to its top speed of 10,000rpm as it seems to lack torque at lower settings. For the finer tools I would have wished for a higher speed and for some situations a reduced speed might have been beneficial as well. Luckily, this can be upgraded and I probably will do so in the future.</p>

<p>In the end, I mostly chose the 4030 V2 because of its rather small footprint. While others might consider this to be a disadvantage, it was the largest machine that I could fit into an enclosure onto my workbench. Since I do not have a proper wood workshop, the machine has to share a room with other hobbies in my basement, which means that I needed to address noise and dust, because CNC machines can deliver plenty of both.</p>

<figure>

<img src="/assets/resized/images/2023-11-03/1280/setup.jpg" style="max-width: min(3000px, 100%)" alt="Photo of my setup. On a workbench on the left sits an enclosure made out of aluminium extrusions and MDF sheets at the sides. The front is acrylic glass and through it the CNC machine and its dust extraction hose are visible. To the right is another workbench with tool holders and a screen through which the CNC can be controlled." srcset="    /assets/resized/images/2023-11-03/640/setup.jpg 640w,    /assets/resized/images/2023-11-03/768/setup.jpg 768w,    /assets/resized/images/2023-11-03/1024/setup.jpg 1024w,    /assets/resized/images/2023-11-03/1280/setup.jpg 1280w, /assets/images/2023-11-03/setup.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>My setup looks fancier than it actually is. The enclosure and dust extraction are necessary in a regular room while the machine itself is not that large or fancy after all.</figcaption>
</figure>

<p>I went for the <a href="https://openbuildspartstore.com/modular-enclosure-system/" target="_blank">OpenBuilds Enclosure Kit 510</a>, added acoustic foam and paired it with the <a href="https://www.amazon.de/Genmitsu-Staubschutzschuh-Durchmesser-Spindelmotoren-Au%25C3%259Fendurchmesser/dp/B09QM4DLKK/?_encoding=UTF8&amp;pd_rd_w=Hqi7Z&amp;content-id=amzn1.sym.414f57ed-562f-47a3-9f9d-2fc23abd569f%253Aamzn1.symc.adba8a53-36db-43df-a081-77d28e1b71e6&amp;pf_rd_p=414f57ed-562f-47a3-9f9d-2fc23abd569f&amp;pf_rd_r=ZQ52E38MHKKN5W61JYGM&amp;pd_rd_wg=lx81o&amp;pd_rd_r=4dbd95d4-7a8d-4270-89a8-6e8d66ac83d8&amp;ref_=pd_gw_ci_mcx_mr_hp_atf_m&amp;_encoding=UTF8&amp;tag=oughtabe-21&amp;linkCode=ur2&amp;linkId=73b06a2d3b45b4382ef6faf701724823&amp;camp=1638&amp;creative=6742" target="_blank" class="affiliate">Genmitsuo dust shoe</a> and a flexible hose. The vacuum hose then runs through a <a href="https://www.amazon.de/gp/product/B00HXDYL70/ref=ppx_yo_dt_b_asin_image_o08_s00?ie=UTF8&amp;psc=1&amp;_encoding=UTF8&amp;tag=oughtabe-21&amp;linkCode=ur2&amp;linkId=53a58215ae523681316ed7c26fa3ed14&amp;camp=1638&amp;creative=6742" target="_blank" class="affiliate">cyclonic separator</a> and ends in a <a href="https://www.amazon.de/gp/product/B01JP7BOTE/ref=ppx_yo_dt_b_asin_image_o00_s00?ie=UTF8&amp;psc=1" target="_blank" class="affiliate">Makita vacuum</a>. Add a workbench, an LED lamp inside the enclosure and a screen at the side (you might recognize this one from my <a href="/a/bullet-time-video-booth">bullet time rig</a>) and that’s my setup.</p>

<p>Just a few warnings if you are thinking about picking up CNC as a hobby: Do not underestimate the noise and the amount of dust such a machine produces. The enclosure and dust extraction in my case ate up a huge part of my budget for a good reason. If you are in a dirty workshop far from other people you can ignore the enclosure, but please still think about your hearing and your lungs. I highly recommend to use a dust extraction system anyway and if you are doing this in a room that you spend a lot of time in or that you use for other things in general, a simple shop vacuum will not be enough. Fine saw dust will escape regular vacuums and you should really look into ones with a filter for dust class M.</p>

<p>Oh, and one more warning: I do not have a problem to leave my 3D printer unattended as long as I can check in once in a while through a webcam. The typical 3D printer accidents turn the machine into a PLA spaghetti factory, but it is still ok if you stop it 15 minutes later. The more dangerous typical fail state of a 3D printer is a cable fire especially on cheap printers like mine, but if you place your printer in an environment of low flammability there is plenty of time to recognize the problem before severe consequences.</p>

<p>This is different for a CNC machine! If things go south with a CNC machine, they go south fast! The typical CNC accident means driving a sharp piece of metal rotating at 10,000 rpm into something that was not supposed to receive that piece of metal. You can break a lot of stuff within seconds and that is the harmless case. Imagine running that 10,000 rpm metal stick into a dry piece of wood and leaving it there because of a failure or maybe because that wood got loose and now follows your machine. Remember those adventure movies where they make fire with a rotating stick? Do this with a 400W motor covered by saw dust and maybe with a dust extraction system sucking everything into the big container with even more sawdust.</p>

<p>The point is that you should not leave a running CNC machine, have a fire extinguisher nearby and make sure to have an easily accessible emergency stop that actually cuts power to your machine (in case of an enclose, put something <a href="https://www.amazon.de/gp/product/B09FK8KHC9/ref=ppx_yo_dt_b_asin_image_o09_s00?ie=UTF8&amp;psc=1&amp;_encoding=UTF8&amp;tag=oughtabe-21&amp;linkCode=ur2&amp;linkId=6b2fbd57c48ea67cb2eacb8bbf65fee2&amp;camp=1638&amp;creative=6742" target="_blank" class="affiliate">like this</a> on the outside). Here is <a href="https://www.youtube.com/watch?v=4tpxgZUYGSI&amp;ab_channel=WinstonMoy" target="_blank">a video to encourage you to take this serious</a>.</p>

<h3 id="the-bits">The bits</h3>

<p>Like in the previous section, I will be talking to people new to CNC routers here. Someone who already knows this stuff will probably just have a glimpse at the table below anyway.</p>

<p>While CNC machining often looks like you are just pushing a common drill bit to some wood, this is not what is happening at all (unless maybe you are actually drilling holes with your CNC). What looks like a drill bit usually is a milling bit (a so-called end mill). It is not designed to plunge vertically into the material (although in many cases it has to be able to do this, too), but to carve away material horizontally.</p>

<p>One interesting aspect of this is that the spiral of the bit might go in different directions. Some bits have a right-handed spiral like a drilling bit, but others can be left-handed or not even have a spiral form at all. The reason is that the actual milling is done at the side and just requires sharp edges while the spiral mostly helps to move the material away from the cutting edge. Right-handed drills are common as you want to push material upwards out of the drilling hole and for the same reason you will find many end mills with that shape. They pull material upwards and away from your work piece, therefore they are called “upcut” bits. The downside of this is that the upwards motion can fray the edges of your work piece, which is why I also often use the counterpart with a left-handed spiral, the “downcut” bit. There are also some other forms and compromises, but I only have those two variants and so for each task I have to pick a suitable diameter for my end mill (obviously you need small ones for details and large one to remove much material fast) and decide between upcut and downcut as a trade-off between a nice finish and efficient material removal especially in tight spots.</p>

<figure>

<img src="/assets/resized/images/2023-11-03/1280/bits.jpg" style="max-width: min(3840px, 100%)" alt="Photos of the nine bits I used. Each one is shown from the side with indications of diameter, shape and cutting direction." srcset="    /assets/resized/images/2023-11-03/640/bits.jpg 640w,    /assets/resized/images/2023-11-03/768/bits.jpg 768w,    /assets/resized/images/2023-11-03/1024/bits.jpg 1024w,    /assets/resized/images/2023-11-03/1280/bits.jpg 1280w, /assets/images/2023-11-03/bits.jpg 3840w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>The nine bits I used in this project, roughly grouped by shape and size.</figcaption>
</figure>

<p>Another aspect is the shape of the tip of the bit. The most common one is the flat end mill, which is best suited to create a flat surface. However, if you want to create a curved 3D shape, this is not a good choice as it tends to create little steps that look similar to the layers of 3D printers. A ballnose bit with a circular tip can help as it can create a slope between one line to the next one.</p>

<p>A very important shape is the V-shape. On one hand, this is used to directly create angled edges (chamfers) in a single pass, but also they can be used to carve text or other designs. Imagine to just try and cut a small square. If you use a flat end mill with a 3mm diameter, you can quickly remove the material from the square, but the corners will always be rounded with a radius of 1.5mm. So, instead you use a smaller end mill, let’s say 1mm. Now it takes ages to remove the material<sup id="fnref:longsmallbit" role="doc-noteref"><a href="#fn:longsmallbit" class="footnote" rel="footnote">2</a></sup> and still have round corners, now with a radius of 0.5mm (which might be ok for many use cases). That’s where the V-bit shines: If you do not care about how deep you cut, but just want to have a 2D shape at the surface, a V-shaped bit can simply use different depths to have different diameters at the surface. Need a tiny bit for a sharp corner? Only use the very tip of the bit. Need a large diameter to clear most of that example square? Go deep for a large radius. The result is a typical angled V-carving look that many (including me) find quite pleasing. Just search for V-carving on Youtube to find a bunch of very satisfying videos.</p>

<p>Unfortunately, that still is not all that needs to be considered when picking the right tool for each detail. You also need to consider the cutting length and the overall clearance of your bits. The cutting length is how deep the bit can go so that its sharp bits are still cutting away material instead of its dull bits just rubbing the wood and heating it up. That’s not much of an issue for me as my weak spindle usually limits the depth of my cuts anyway. The clearance is a much bigger issue because overlooking it usually causes some kind of damage. Whenever you cut into the material (or when you use clamps to hold your stock<sup id="fnref:bluetape" role="doc-noteref"><a href="#fn:bluetape" class="footnote" rel="footnote">3</a></sup>) you need to have enough space for your machine and the rest of the bit. The machine itself mostly becomes a problem for deep pockets, but those small diameter end mills often still have a larger shaft. So, even if you observe the cutting length, you cannot cut at the side of a straight wall if you would run the shaft of your bit into it.</p>

<p>That should be enough of an overview. I am mostly writing down what I learned from others (mostly Youtube) over a few months, so I would recommend that, too, if you want to go deeper.</p>

<p>Now, finally, here is the list of bits I used in this project and the short name I use for them henceforth<sup id="fnref:henceforth" role="doc-noteref"><a href="#fn:henceforth" class="footnote" rel="footnote">4</a></sup>.</p>

<table>
  <thead>
    <tr>
      <th>Label</th>
      <th>Diameter</th>
      <th>Shape</th>
      <th>Direction</th>
      <th>Exact product</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>3mm upcut</td>
      <td>3.0 mm</td>
      <td>flat</td>
      <td>upcut</td>
      <td><a href="https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/1-8-werkzeuge/3-175----1-8---Fraeser/2-schneider-flach-sorotec-edition/" target="_blank">Sorotec L2SF.M.0300</a></td>
    </tr>
    <tr>
      <td>1.5mm upcut</td>
      <td>1.5 mm</td>
      <td>flat</td>
      <td>upcut</td>
      <td><a href="https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/1-8-werkzeuge/3-175----1-8---Fraeser/2-schneider-flach-sorotec-edition/" target="_blank">Sorotec L2SF.M.0150</a></td>
    </tr>
    <tr>
      <td>1mm upcut</td>
      <td>1.0 mm</td>
      <td>flat</td>
      <td>upcut</td>
      <td><a href="https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/1-8-werkzeuge/3-175----1-8---Fraeser/2-schneider-flach-sorotec-edition/" target="_blank">Sorotec L2SF.M.0100</a></td>
    </tr>
    <tr>
      <td>3mm downcut</td>
      <td>3.0 mm</td>
      <td>flat</td>
      <td>downcut</td>
      <td><a href="https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/1-8-werkzeuge/3-175----1-8---Fraeser/Linksspiralige/2-Schneider-366/" target="_blank">Sorotec L2SF.0300.LI</a></td>
    </tr>
    <tr>
      <td>1.5mm downcut</td>
      <td>1.5 mm</td>
      <td>flat</td>
      <td>downcut</td>
      <td><a href="https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/1-8-werkzeuge/3-175----1-8---Fraeser/Linksspiralige/2-Schneider-366/" target="_blank">Sorotec L2SF.0150.LI</a></td>
    </tr>
    <tr>
      <td>3mm ballnose</td>
      <td>3.0 mm</td>
      <td>ballnose</td>
      <td>upcut</td>
      <td><a href="https://www.sorotec.de/shop/VHM-Radienfraeser-3D--3-0mm-L24.html" target="_blank">Sorotec FR2612.0300.24</a></td>
    </tr>
    <tr>
      <td>30° V-engrave</td>
      <td>1.6 mm</td>
      <td>30° V-shaped</td>
      <td>upcut</td>
      <td><a href="https://www.sorotec.de/shop/VHM-Fr-sstichel-30-.html" target="_blank">Sorotec LGS.0030</a></td>
    </tr>
    <tr>
      <td>90° chamfer</td>
      <td>6.0 mm</td>
      <td>90° V-shaped</td>
      <td>none</td>
      <td><a href="https://www.amazon.de/gp/product/B099DR8P6V/ref=ppx_yo_dt_b_asin_title_o04_s00?ie=UTF8&amp;th=1&amp;_encoding=UTF8&amp;tag=oughtabe-21&amp;linkCode=ur2&amp;linkId=e324bb92f272df87ee1f9013a8a33a6a&amp;camp=1638&amp;creative=6742" target="_blank" class="affiliate">Speed Tiger 90°</a></td>
    </tr>
    <tr>
      <td>Surfacing bit</td>
      <td>25.4 mm</td>
      <td>flat / surfacing</td>
      <td>none</td>
      <td><a href="https://www.amazon.de/gp/product/B092QY34K9/ref=ppx_yo_dt_b_asin_title_o03_s02?ie=UTF8&amp;psc=1&amp;_encoding=UTF8&amp;tag=oughtabe-21&amp;linkCode=ur2&amp;linkId=7a0323d3207481185892397b265eabfe&amp;camp=1638&amp;creative=6742" target="_blank" class="affiliate">Baorder JYH0075</a></td>
    </tr>
  </tbody>
</table>

<p>Please keep in mind that I only got my first CNC router a few months ago, so these are mostly the first bits I ever used. I have no comparison to tell if they are good or bad, so take this as pure documentation and not as an endorsement.</p>

<h2 id="the-outside-of-the-bottom-shell-a">The outside of the bottom shell (A)</h2>

<p>Ok, it’s time to get serious. In the following I will go through all the toolpaths one by one and try to remember details that you should know about. For each one I will mention the bit I used, how long it took and the name of the gcode file I used (if available). As mentioned above it is unlikely that you can use the gcode file directly, but you should be able to view it in order to understand what the toolpath does.</p>

<p>The first step is to create the outside of the bottom half of the shell. For this you need a piece of wood with at least 160mm by 99.6mm and it should have a thickness of at least 23mm. Having a few more millimeters is highly recommended to allow for a few surfacing steps without becoming too thin.</p>

<hr />

<h3 id="a01">A01</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Surfacing</td>
      <td>Surfacing bit</td>
      <td>N/A</td>
      <td>17 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/a01.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath A01." srcset="    /assets/resized/images/2023-11-03/640/a01.jpg 640w,    /assets/resized/images/2023-11-03/768/a01.jpg 768w,    /assets/resized/images/2023-11-03/1024/a01.jpg 1024w,    /assets/resized/images/2023-11-03/1280/a01.jpg 1280w, /assets/images/2023-11-03/a01.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>And surfacing is exactly what we start with. The current surface will be the one visible on the back of the Game Boy shell. So, it should look nice and needs to be surfaced down to a clean even plane. Keep in mind that the remaining thickness needs to be at least 23mm even after possibly surfacing the other side too. The exact thickness does not matter yet, so just aim for a nice finish on this side.</p>

<hr style="clear:both" />

<h3 id="a02">A02</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Hole to allow deep dive of 1mm bit</td>
      <td>3mm upcut</td>
      <td>1-reference-frame-pockets-3mm-flat-UPCUT.gcode</td>
      <td>5 min</td>
    </tr>
    <tr>
      <td>Reference for flipping</td>
      <td>1mm upcut</td>
      <td>2-reference-frame-drill-1mm-flat-UPCUT.gcode</td>
      <td>4 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/a02.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath A02." srcset="    /assets/resized/images/2023-11-03/640/a02.jpg 640w,    /assets/resized/images/2023-11-03/768/a02.jpg 768w,    /assets/resized/images/2023-11-03/1024/a02.jpg 1024w,    /assets/resized/images/2023-11-03/1280/a02.jpg 1280w, /assets/images/2023-11-03/a02.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>This is my method of aligning the workpiece after flipping it. I drill small holes through to the backside that I can use to align it after turning it around. If you have a better way of doing this like a flip jig, a tool to measure distances along all three axes or even an additional axis, you can skip this toolpath.</p>

<p>I labeled this as a single path, but please notice that I have split it into two gcode files. The reason is that the working distance of my smallest bit is too short to go all the way through 23mm without hitting the wood with its wider 3mm shaft. So, there is a first step of drilling space for that shaft before creating the smaller and more precise holes on the backside.</p>

<p>If you use my solution, take note that zero is set to the corner of the Game Boy, so the hole in that corner will be drilled 5mm along negative x and y. So, when picking the start point to fit the 160mm by 99.6mm of the reference frame, keep in mind to offset zero by 5mm in both directions. This will be the zero for all subsequent steps, too.</p>

<p>Oh, and obviously, this step will drill through the entire workpiece and into your spoilboard.</p>

<hr style="clear:both" />

<h3 id="a03">A03</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Profile cut</td>
      <td>3mm downcut</td>
      <td>001-bottom-outside-1-profile-3.0mm-flat.gcode</td>
      <td>15 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/a03.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath A03." srcset="    /assets/resized/images/2023-11-03/640/a03.jpg 640w,    /assets/resized/images/2023-11-03/768/a03.jpg 768w,    /assets/resized/images/2023-11-03/1024/a03.jpg 1024w,    /assets/resized/images/2023-11-03/1280/a03.jpg 1280w, /assets/images/2023-11-03/a03.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>Next I do a profile cut that does not go all the way through. It will eventually meet with a profile cut from the other side (if aligned properly). At this point it also gives room for a few upcoming toolpaths that enter and leave the outer rim of the Game Boy shell.</p>

<hr style="clear:both" />

<h3 id="a04">A04</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Curved sides of the back</td>
      <td>3mm ballnose</td>
      <td>001-bottom-outside-2-3d-3mm-ball.gcode</td>
      <td>63 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/a04.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath A04." srcset="    /assets/resized/images/2023-11-03/640/a04.jpg 640w,    /assets/resized/images/2023-11-03/768/a04.jpg 768w,    /assets/resized/images/2023-11-03/1024/a04.jpg 1024w,    /assets/resized/images/2023-11-03/1280/a04.jpg 1280w, /assets/images/2023-11-03/a04.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>The curved sides on the back are created with a ballnose bit. However, I can still see distinct lines from the tool passes, so if you want to avoid that you need to set a higher overlap between passes or you need to do a better sanding<sup id="fnref:hate" role="doc-noteref"><a href="#fn:hate" class="footnote" rel="footnote">5</a></sup> job than me.</p>

<hr style="clear:both" />

<h3 id="a05">A05</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Pocketing of cartridge slot and battery compartment</td>
      <td>3mm downcut</td>
      <td>001-bottom-outside-3-pocket-3mm-flat.gcode</td>
      <td>84 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/a05.jpg" style="max-width: min(2999px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath A05." srcset="    /assets/resized/images/2023-11-03/640/a05.jpg 640w,    /assets/resized/images/2023-11-03/768/a05.jpg 768w,    /assets/resized/images/2023-11-03/1024/a05.jpg 1024w,    /assets/resized/images/2023-11-03/1280/a05.jpg 1280w, /assets/images/2023-11-03/a05.jpg 2999w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>This step carves the pockets for the cartridge slot and the battery compartment. Note, that it removes more material in the battery compartment than on the actual original Game Boy shell to make space for the upcoming toolpath using a 1.5mm downcut bit. My 1.5mm bit only has about 8mm of clearance before sloping into a 3mm shaft, so in order to create the finer details inside the battery compartment some room needs to be made. If you have a bit without such a limitation, you might want to optimize this towards the original.</p>

<hr style="clear:both" />

<h3 id="a06">A06</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Details of battery compartment and lines on backside</td>
      <td>1.5mm downcut</td>
      <td>001-bottom-outside-4-details-1.5mm-downcut.gcode</td>
      <td>40 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/a06.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath A06." srcset="    /assets/resized/images/2023-11-03/640/a06.jpg 640w,    /assets/resized/images/2023-11-03/768/a06.jpg 768w,    /assets/resized/images/2023-11-03/1024/a06.jpg 1024w,    /assets/resized/images/2023-11-03/1280/a06.jpg 1280w, /assets/images/2023-11-03/a06.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>As mentioned before, this adds some details in the battery compartment. Important ones: These are the slits that will hold the battery contacts. My design is made for some after market contacts (see assembly section), but it has too much room and I needed to put something behind the contacts to hold them in place. If you know the exact contacts you want to use, this is something that could be optimized.</p>

<p>Oh, and this path also creates the decorative lines on the back of the Game Boy.</p>

<hr style="clear:both" />

<h3 id="a07">A07</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Drilling screw holes</td>
      <td>1.5mm upcut</td>
      <td>001-bottom-outside-5-holes-1.5mm-upcut.gcode</td>
      <td>3 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/a07.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath A07." srcset="    /assets/resized/images/2023-11-03/640/a07.jpg 640w,    /assets/resized/images/2023-11-03/768/a07.jpg 768w,    /assets/resized/images/2023-11-03/1024/a07.jpg 1024w,    /assets/resized/images/2023-11-03/1280/a07.jpg 1280w, /assets/images/2023-11-03/a07.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>The last step on this side drills some holes for the screws. These are not the holes the screws tap into, but the holes that the screws go through first. I have not tested the diameter much because I ended up gluing the shell, but they seemed ok.</p>

<hr style="clear:both" />

<h2 id="the-inside-of-the-bottom-shell-b">The inside of the bottom shell (B)</h2>

<p>Now it is time to flip the workpiece to do the inside of the bottom half. To do so, align it with your favorite method.</p>

<p>If you use my reference drills, align your workpiece such that you can jog 160mm along x and 99.6mm along y and always end up on one of the small 1mm holes as precisely as possible. Remember that you cannot fix the rotation of your workpiece if you use the tape and glue method (like I do), in which case I would recommend using a carpenter’s square to get the orientation right while gluing it and then use the holes to verify and to set zero.</p>

<p>Speaking of zero on my reference frame: Remember that the hole is at -5mm and -5mm, so align your bit above the correct hole and then move it by 5mm along x and y <strong>before</strong> setting zero. Keep that zero for the rest of the steps for the bottom half.</p>

<hr />

<h3 id="b01">B01</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Surfacing</td>
      <td>Surfacing bit</td>
      <td>N/A</td>
      <td>depends on original thickness</td>
    </tr>
  </tbody>
</table>

<p>Once again, it is time for surfacing. However, this time you are aiming for a final thickness of exactly 23mm. Of course, if you need to remove a lot of material, you can use a different method first, but at the end of this step you should have a perfectly flat workpiece with a thickness of 23mm. I designed the two sides to allow for a little uncertainty here, but I would say that you should hit 23mm to within about half a millimeter.</p>

<hr style="clear:both" />

<h3 id="b02">B02</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Pocketing of most internal geometry</td>
      <td>3mm downcut</td>
      <td>002-bottom-inside-1-pocket.gcode</td>
      <td>211 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/b02.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath B02." srcset="    /assets/resized/images/2023-11-03/640/b02.jpg 640w,    /assets/resized/images/2023-11-03/768/b02.jpg 768w,    /assets/resized/images/2023-11-03/1024/b02.jpg 1024w,    /assets/resized/images/2023-11-03/1280/b02.jpg 1280w, /assets/images/2023-11-03/b02.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>This is the big workhorse step for this side. This removes most material to make room for the PCBs and it creates most of the relevant geometry inside. However, my gcode had a few problems here that you might want to improve on:</p>
<ul>
  <li>I manually created layers for the pocketing that are far from optimal. By now I know better methods to do this, but at that time this was the best I could do in Blender CAM. The current toolpaths are inefficient and imprecise in a few places. You might want to recreate them with your preferred software (and especially if you have a beefier machine that can handle deeper cuts).</li>
  <li>A big mistake here is that one or two of the lower toolpaths accidentally cut away the lip and part of the rim of the shell near the power switch as well as the screw post near that switch. I compensated for that with a larger lip on the top half of the shell, but that is far from perfect. The reason for this error seems to be a boolean operator that did not work properly and failed to subtract that part of the rim from the pocketing area. Whatever the reason, you should carefully look at what would happen to the rim at the top of your Game Boy.</li>
  <li>Some parts were a bit imprecise. The screw posts for the metal shield of the Game Boy come to mind or the lower end of the link cable cutout. If you want to be precise, check out my photos and adjust things that are not perfect.</li>
</ul>

<hr style="clear:both" />

<h3 id="b03">B03</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Finer details of internal geometry</td>
      <td>1.5mm upcut</td>
      <td>002-bottom-inside-2-fine-1.5mm-upcut.gcode</td>
      <td>48 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/b03.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath B03." srcset="    /assets/resized/images/2023-11-03/640/b03.jpg 640w,    /assets/resized/images/2023-11-03/768/b03.jpg 768w,    /assets/resized/images/2023-11-03/1024/b03.jpg 1024w,    /assets/resized/images/2023-11-03/1280/b03.jpg 1280w, /assets/images/2023-11-03/b03.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>First of all note that as far as I remember, the only reason for using an upcut bit here is that my 1.5mm upcut is a few millimeters longer than my 1.5mm downcut. If this is not a limitation for your tool, you should be fine with a downcut end mill, too, except maybe for some drilling for the screws.</p>

<p>This step adds some details to the power switch part, the battery contact cutouts for the main PCB and some small details like a little cutaway behind one of the screw posts to make space for the speaker cable. It also drills holes for the screws to tap in. In my experience the 1.5mm holes do not hold the thin original screws reliably. On the top half I will later drill 1mm holes, which unfortunately tended to break if a screw is tapped into a pole. So, some experimentation with alternative screws and drilling diameters might be in order here, especially if you plan to use a different kind of wood.</p>

<hr style="clear:both" />

<h3 id="b04">B04</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Profile cut</td>
      <td>3mm downcut</td>
      <td>002-bottom-inside-3-profile-3mm-downcut.gcode</td>
      <td>11 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/b04.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath B04." srcset="    /assets/resized/images/2023-11-03/640/b04.jpg 640w,    /assets/resized/images/2023-11-03/768/b04.jpg 768w,    /assets/resized/images/2023-11-03/1024/b04.jpg 1024w,    /assets/resized/images/2023-11-03/1280/b04.jpg 1280w, /assets/images/2023-11-03/b04.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>And here is the final profile cut that should meet the profile cut you did on the other side. Note that I use the tape and glue method and did not have to consider any clamps. If you use something else you have to think about bridges if required.</p>

<p>This is where you find out if you have aligned your workpiece properly.</p>

<hr style="clear:both" />

<figure>

<img src="/assets/resized/images/2023-11-03/1280/ac.jpg" style="max-width: min(3000px, 60%)" alt="Detail photo of hole for the AC adapter plug." srcset="    /assets/resized/images/2023-11-03/640/ac.jpg 640w,    /assets/resized/images/2023-11-03/768/ac.jpg 768w,    /assets/resized/images/2023-11-03/1024/ac.jpg 1024w,    /assets/resized/images/2023-11-03/1280/ac.jpg 1280w, /assets/images/2023-11-03/ac.jpg 3000w" sizes="(max-width: 60rem) calc(60/100 * 100vw), calc(60/100 * 60rem)" />

<figcaption>The hole for the AC adapter needs to be made by hand.</figcaption>
</figure>

<p>A little detail you should note here is that I did not figure out a reasonable way to create the hole for the AC adapter plug. I ended up manually creating it with a file after the bottom shell was complete.</p>

<h2 id="the-outside-of-the-top-shell-c">The outside of the top shell (C)</h2>

<p>Now it is time for the iconic front of the Game Boy. Please read steps C02 to C03 carefully and think about them. These are the common sign-maker’s method of doing a V-carve, painting everything and then cutting away the excessive paint. I have little experience here and this is the part of my wooden Game Boy that I am absolutely not happy with. My method (or choice of paint or choice of tools or whatever) lead to quite some smearing of the paint and letters that look soft and unfocused.</p>

<figure>

<img src="/assets/resized/images/2023-11-03/1280/buttons.jpg" style="max-width: min(3000px, 100%)" alt="Detail photo of the button area of the wooden Game Boy. The Nintendo logo as well as the labels next to the buttons suffer a bit from a smeared out look." srcset="    /assets/resized/images/2023-11-03/640/buttons.jpg 640w,    /assets/resized/images/2023-11-03/768/buttons.jpg 768w,    /assets/resized/images/2023-11-03/1024/buttons.jpg 1024w,    /assets/resized/images/2023-11-03/1280/buttons.jpg 1280w, /assets/images/2023-11-03/buttons.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>The white paint looks soft and some circular smearing from the surfacing bit is visible.</figcaption>
</figure>

<p>If making the entire wooden Game Boy was less time intensive, I would probably have another try with white epoxy instead of paint. I also notice that many hobby CNCists<sup id="fnref:aword" role="doc-noteref"><a href="#fn:aword" class="footnote" rel="footnote">6</a></sup> use a belt or disc sander for this instead of a surfacing bit, so maybe that’s part of the problem. If you recognize why this did not work out so nicely in my case, please let me know. Anyhow, below is what I did to get the Nintendo logo and labels onto my Game Boy and you might want to think about a different method.</p>

<p>You will also notice that I did not list a surfacing step. That’s because my method involves V-carving below zero and a later surfacing step anyway. Depending on what you want to do, you might want to surface first.</p>

<hr />

<h3 id="c01">C01</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Hole to allow deep dive of 1mm bit</td>
      <td>3mm upcut</td>
      <td>1-reference-frame-pockets-3mm-flat-UPCUT.gcode</td>
      <td>5 min</td>
    </tr>
    <tr>
      <td>Reference for flipping</td>
      <td>1mm upcut</td>
      <td>2-reference-frame-drill-1mm-flat-UPCUT.gcode</td>
      <td>4 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/c01.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath C01." srcset="    /assets/resized/images/2023-11-03/640/c01.jpg 640w,    /assets/resized/images/2023-11-03/768/c01.jpg 768w,    /assets/resized/images/2023-11-03/1024/c01.jpg 1024w,    /assets/resized/images/2023-11-03/1280/c01.jpg 1280w, /assets/images/2023-11-03/c01.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>Again, the reference for flipping. Again, skip if you prefer a different method. And again, if you use mine, remember to offset zero by 5mm along x and y.</p>

<p>But there is one <strong>important difference</strong>: If you use my method of painting you will have to remove the workpiece, paint it, replace it and realign it on the same side. That might not work with a flip jig if it cannot be fixed to your machine’s bed.</p>

<hr style="clear:both" />

<h3 id="c02">C02</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Engraving Nintento logo and button labels</td>
      <td>30° V-carving</td>
      <td>003-top-outside-1-label-30deg-v-carve.gcode</td>
      <td>16 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/c02.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath C02." srcset="    /assets/resized/images/2023-11-03/640/c02.jpg 640w,    /assets/resized/images/2023-11-03/768/c02.jpg 768w,    /assets/resized/images/2023-11-03/1024/c02.jpg 1024w,    /assets/resized/images/2023-11-03/1280/c02.jpg 1280w, /assets/images/2023-11-03/c02.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>Now, this engraves the Nintendo logo and button labels with a V-shaped bit. I actually ran that toolpath twice: Once starting from the surface (resulting in perfect letters at the surface) and then again starting 2mm below the surface. This is because I will later remove 2mm to return to the perfect letters and remove excessive paint. I did this in two steps because I was not sure if plunging a total of 4mm directly would be too much (now I think it wouldn’t). Also, 2mm might be a bit much, but I had made the observation in earlier tests that white paint enters the wood grain even with a clear coat primer, so I wanted to be safe.</p>

<hr style="clear:both" />

<h3 id="painting">Painting</h3>

<p><img src="/assets/resized/images/2023-11-03/1280/painting.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece covered in white paint." srcset="    /assets/resized/images/2023-11-03/640/painting.jpg 640w,    /assets/resized/images/2023-11-03/768/painting.jpg 768w,    /assets/resized/images/2023-11-03/1024/painting.jpg 1024w,    /assets/resized/images/2023-11-03/1280/painting.jpg 1280w, /assets/images/2023-11-03/painting.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>A step that is not a toolpath. Paint your workpiece such that the logo and labels are properly colored. I used spray paint for this and first applied several layers of clear coat as a primer. After it had dried completely, I added several layers of white spray paint. The point of the primer is mostly to prevent white paint from entering the wood grain and showing up several millimeters deeper where it should not be visible. Make sure to seal up every part that could get sprayed with white paint - not only the actual letters.</p>

<p>Any suggestions on improving this method are welcome.</p>

<p>And of course, I had to remove my workpiece for this step. If you apply paint with a brush, you might be able to do this in place.</p>

<hr style="clear:both" />

<h3 id="c03">C03</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Surfacing</td>
      <td>Surfacing bit</td>
      <td>N/A</td>
      <td>25 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/c03.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath C03." srcset="    /assets/resized/images/2023-11-03/640/c03.jpg 640w,    /assets/resized/images/2023-11-03/768/c03.jpg 768w,    /assets/resized/images/2023-11-03/1024/c03.jpg 1024w,    /assets/resized/images/2023-11-03/1280/c03.jpg 1280w, /assets/images/2023-11-03/c03.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>Here I now removed the 2mm that I dove too deep during V-engraving. This removes all the paint outside the carved area, theoretically leaving me with perfect and precise letters. However, as mentioned above I am not entirely happy with the result and you might want to consider alternative methods.</p>

<hr style="clear:both" />

<h3 id="c04">C04</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Profile cut</td>
      <td>3mm upcut</td>
      <td>003-top-outside-2-profile-3mm-upcut.gcode</td>
      <td>24 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/c04.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath C04." srcset="    /assets/resized/images/2023-11-03/640/c04.jpg 640w,    /assets/resized/images/2023-11-03/768/c04.jpg 768w,    /assets/resized/images/2023-11-03/1024/c04.jpg 1024w,    /assets/resized/images/2023-11-03/1280/c04.jpg 1280w, /assets/images/2023-11-03/c04.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>Next I did a profile cut and again not all the way through. This gives some room for some of the upcoming toolpaths at the edge. Also note that I will chamfer the outline of the Game Boy front, so there is no need for a downcut bit here.</p>

<hr style="clear:both" />

<h3 id="c05">C05</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Screen bezel and LED hole</td>
      <td>3mm downcut</td>
      <td>003-top-outside-3-pockets-3mm-downcut.gcode</td>
      <td>10 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/c05.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath C05." srcset="    /assets/resized/images/2023-11-03/640/c05.jpg 640w,    /assets/resized/images/2023-11-03/768/c05.jpg 768w,    /assets/resized/images/2023-11-03/1024/c05.jpg 1024w,    /assets/resized/images/2023-11-03/1280/c05.jpg 1280w, /assets/images/2023-11-03/c05.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>This is a simple shallow pocket for the screen bezel and a little drilling to create the hole for the power LED.</p>

<hr style="clear:both" />

<h3 id="c06">C06</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Pockets for buttons, screen and speaker grill</td>
      <td>1.5mm downcut</td>
      <td>003-top-outside-4-pockets-1.5mm-downcut.gcode</td>
      <td>34 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/c06.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath C06." srcset="    /assets/resized/images/2023-11-03/640/c06.jpg 640w,    /assets/resized/images/2023-11-03/768/c06.jpg 768w,    /assets/resized/images/2023-11-03/1024/c06.jpg 1024w,    /assets/resized/images/2023-11-03/1280/c06.jpg 1280w, /assets/images/2023-11-03/c06.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>The pocketing of the buttons, the actual screen and the speaker grill is done with a finer bit. In some cases because of thin lines and in some cases to achieve sharper corners. In some cases because it seemed like the buttons should be part of this package.</p>

<hr style="clear:both" />

<h3 id="c07">C07</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Rounded speaker corner and button indents</td>
      <td>3mm ballnose</td>
      <td>003-top-outside-5-indents-3mm-ball.gcode</td>
      <td>21 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/c07.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath C07." srcset="    /assets/resized/images/2023-11-03/640/c07.jpg 640w,    /assets/resized/images/2023-11-03/768/c07.jpg 768w,    /assets/resized/images/2023-11-03/1024/c07.jpg 1024w,    /assets/resized/images/2023-11-03/1280/c07.jpg 1280w, /assets/images/2023-11-03/c07.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>The Game Boy features a slope at the speaker corner and some smooth indentations around the buttons. These are done with a ballnose bit.</p>

<hr style="clear:both" />

<h3 id="c08">C08</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Outline chamfer</td>
      <td>90° V-carving</td>
      <td>003-top-outside-6-chamfer-90deg.gcode</td>
      <td>2 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/c08.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath C08." srcset="    /assets/resized/images/2023-11-03/640/c08.jpg 640w,    /assets/resized/images/2023-11-03/768/c08.jpg 768w,    /assets/resized/images/2023-11-03/1024/c08.jpg 1024w,    /assets/resized/images/2023-11-03/1280/c08.jpg 1280w, /assets/images/2023-11-03/c08.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>Finally, my favorite step of most CNC projects. Chamfering. So satisfying.</p>

<p>Unfortunately, there is a mistake in my toolpath. While just following the outline works perfectly for most parts of the Game Boy front, I underestimated the effect of the slope at the speaker corner. A 45° chamfer towards this slight incline resulted in a bit of overhand and a slightly weird look. I sanded<sup id="fnref:hate:1" role="doc-noteref"><a href="#fn:hate" class="footnote" rel="footnote">5</a></sup> most of the problem away, but you might want to consider skipping chamfering in this corner.</p>

<hr style="clear:both" />

<h3 id="c09">C09</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Detail decor lines</td>
      <td>1mm upcut</td>
      <td>003-top-outside-7-detail-1mm.gcode</td>
      <td>3 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/c09.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath C09." srcset="    /assets/resized/images/2023-11-03/640/c09.jpg 640w,    /assets/resized/images/2023-11-03/768/c09.jpg 768w,    /assets/resized/images/2023-11-03/1024/c09.jpg 1024w,    /assets/resized/images/2023-11-03/1280/c09.jpg 1280w, /assets/images/2023-11-03/c09.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>The last step on the outside of the top half just adds the decorative fine lines to the front. This is quick and easy, but note that the only reason for using an upcut bit here is that I simply don’t own a 1mm downcut bit.</p>

<hr style="clear:both" />

<h2 id="the-inside-of-the-top-shell-d">The inside of the top shell (D)</h2>

<p>Once again it is time to flip your workpiece and align it. And once again remember to offset by 5mm if you use my method.</p>

<hr />

<h3 id="d01">D01</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Surfacing</td>
      <td>Surfacing bit</td>
      <td>N/A</td>
      <td>81 min (depends on original thickness)</td>
    </tr>
  </tbody>
</table>

<p>If you use wood from the same board as the bottom half of the shell, you probably have to remove a lot of material here. You are aiming for a final thickness of 14.5mm. And this one needs to be very precise. On the bottom half a too thick shell might make it harder to get the battery contacts through to the battery compartment, but on the top half this impacts the top position of your buttons, how deep the screen is below the window and whether your speaker grill goes through (spoiler: mine didn’t).</p>

<p><img src="/assets/resized/images/2023-11-03/1280/d01.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath D01." srcset="    /assets/resized/images/2023-11-03/640/d01.jpg 640w,    /assets/resized/images/2023-11-03/768/d01.jpg 768w,    /assets/resized/images/2023-11-03/1024/d01.jpg 1024w,    /assets/resized/images/2023-11-03/1280/d01.jpg 1280w, /assets/images/2023-11-03/d01.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<hr style="clear:both" />

<h3 id="d02">D02</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Pocketing of most internal geometry</td>
      <td>3mm downcut</td>
      <td>004-top-inside-1-pockets-3mm-downcut.gcode</td>
      <td>179 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/d02.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath D02." srcset="    /assets/resized/images/2023-11-03/640/d02.jpg 640w,    /assets/resized/images/2023-11-03/768/d02.jpg 768w,    /assets/resized/images/2023-11-03/1024/d02.jpg 1024w,    /assets/resized/images/2023-11-03/1280/d02.jpg 1280w, /assets/images/2023-11-03/d02.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>This is the massive pocketing job for the inside, covering most parts. Note that I took some liberty in optimizing the shape for a subtractive process. The original injection mold shape obviously tried to save material while we would like to save on carving away material. So in many parts I left a lot of wood in there, where the original Game Boy just has pockets of air. This should also make it more rigid.</p>

<hr style="clear:both" />

<h3 id="d03">D03</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Details of internal geometry and speaker grill</td>
      <td>1.5mm downcut</td>
      <td>004-top-inside-2-details-1.5mm-downcut.gcode</td>
      <td>15 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/d03.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath D03." srcset="    /assets/resized/images/2023-11-03/640/d03.jpg 640w,    /assets/resized/images/2023-11-03/768/d03.jpg 768w,    /assets/resized/images/2023-11-03/1024/d03.jpg 1024w,    /assets/resized/images/2023-11-03/1280/d03.jpg 1280w, /assets/images/2023-11-03/d03.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>Adding details that could not be done with a 3mm bit in the previous step. Most notably some details around the buttons and the inside part of the speaker grill. Like on the original, the speaker grill is designed such that the lines on the outside just touch the ones on the inside to create openings. Unfortunately, this just did not work out on my build, but the sound can still be heard very well, so I am not attempting to tinker with it. However, you might want to keep an eye on the problem and maybe move paths closer together.</p>

<hr style="clear:both" />

<h3 id="d04">D04</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Headphone jack</td>
      <td>3mm ballnose</td>
      <td>004-top-inside-3-phone-3mm-ball.gcode</td>
      <td>3 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/d04.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath D04." srcset="    /assets/resized/images/2023-11-03/640/d04.jpg 640w,    /assets/resized/images/2023-11-03/768/d04.jpg 768w,    /assets/resized/images/2023-11-03/1024/d04.jpg 1024w,    /assets/resized/images/2023-11-03/1280/d04.jpg 1280w, /assets/images/2023-11-03/d04.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>Super short, super simple. The cutout for the headphone jack. I only used a ballnose because this is curved. If you want to save time, you could probably have just done it with a flat end mill in one of the previous steps.</p>

<hr style="clear:both" />

<h3 id="d05">D05</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Profile cut</td>
      <td>3mm downcut</td>
      <td>004-top-inside-4-profile-3mm-downcut.gcode</td>
      <td>5 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/d05.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath D05." srcset="    /assets/resized/images/2023-11-03/640/d05.jpg 640w,    /assets/resized/images/2023-11-03/768/d05.jpg 768w,    /assets/resized/images/2023-11-03/1024/d05.jpg 1024w,    /assets/resized/images/2023-11-03/1280/d05.jpg 1280w, /assets/images/2023-11-03/d05.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>Finally, the profile cut that meets the one from the front. Hopefully you aligned everything perfectly.</p>

<hr style="clear:both" />

<h3 id="d06">D06</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Screw drilling</td>
      <td>1mm upcut</td>
      <td>004-top-inside-5-drill-1mm.gcode</td>
      <td>4 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/d06.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath D06." srcset="    /assets/resized/images/2023-11-03/640/d06.jpg 640w,    /assets/resized/images/2023-11-03/768/d06.jpg 768w,    /assets/resized/images/2023-11-03/1024/d06.jpg 1024w,    /assets/resized/images/2023-11-03/1280/d06.jpg 1280w, /assets/images/2023-11-03/d06.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>This drills 1mm holes into the poles for the screws to tap in. As mentioned in step B03, the 1mm holes also did not work perfectly. In my opinion they are better than the 1.5mm holes I used on the bottom half, but the longer poles near the display were split by the screws in this case. If you come up with a better solution, I would like to know.</p>

<hr style="clear:both" />

<h2 id="battery-cover-e">Battery cover (E)</h2>

<p>Wait, we are not done yet. You still need a battery cover. However, since I did not see how the latch of the original Game Boy could be created with a CNC and survive more than one battery changes, I made a very simple design that uses magnets to hold the cover in place.</p>

<figure>

<img src="/assets/resized/images/2023-11-03/1280/batteries.jpg" style="max-width: min(3000px, 100%)" alt="Photo of the backside of the wooden Game Boy. The battery compartment is open with batteries inside and a hand is holding its cover next to it. Four small magnets are clearly visible in the back of that cover." srcset="    /assets/resized/images/2023-11-03/640/batteries.jpg 640w,    /assets/resized/images/2023-11-03/768/batteries.jpg 768w,    /assets/resized/images/2023-11-03/1024/batteries.jpg 1024w,    /assets/resized/images/2023-11-03/1280/batteries.jpg 1280w, /assets/images/2023-11-03/batteries.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>The battery cover is held in place by four small magnets.</figcaption>
</figure>

<p>So, this is a rather quick process and you only need a piece of wood with an even thickness of about 4mm to 5mm. The magnets should be <a href="https://www.amazon.de/gp/product/B08F6W28PL/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&amp;th=1&amp;_encoding=UTF8&amp;tag=oughtabe-21&amp;linkCode=ur2&amp;linkId=55357485dbc804300ac5a4e2829f4c85&amp;camp=1638&amp;creative=6742" target="_blank" class="affiliate">1mm thick discs with a diameter of 5mm</a> and I could simply press them into the pockets.</p>

<hr />

<h3 id="e01">E01</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Reference for flipping</td>
      <td>1.5mm downcut</td>
      <td>lid_outside_0_references_1.5mm.gcode</td>
      <td>1 min</td>
    </tr>
    <tr>
      <td>Decor lines</td>
      <td>1.5mm downcut</td>
      <td>lid_outside_1_grooves_1.5mm_downcut.gcode</td>
      <td>3 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/e01.jpg" style="max-width: min(2601px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath E01." srcset="    /assets/resized/images/2023-11-03/640/e01.jpg 640w,    /assets/resized/images/2023-11-03/768/e01.jpg 768w,    /assets/resized/images/2023-11-03/1024/e01.jpg 1024w,    /assets/resized/images/2023-11-03/1280/e01.jpg 1280w, /assets/images/2023-11-03/e01.jpg 2601w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>This step cuts references for flipping and adds the decorative lines on the outside of the battery cover. That’s it. Nothing more to see here and it is time to flip the wood piece to the other side.</p>

<hr style="clear:both" />

<h3 id="e02">E02</h3>

<table>
  <thead>
    <tr>
      <th>Purpose</th>
      <th>Tool</th>
      <th>Gcode</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Cut down everything but lip</td>
      <td>1.5mm downcut</td>
      <td>lid_bottom-1-1.5mm-start4.5mmabove-bottom.gcode</td>
      <td>42 min</td>
    </tr>
    <tr>
      <td>Flatten handle</td>
      <td>1.5mm downcut</td>
      <td>lid_bottom-2-1.5mm.gcode</td>
      <td>1 min</td>
    </tr>
    <tr>
      <td>Magnet pockets</td>
      <td>1.5mm downcut</td>
      <td>lid_bottom-3-1.5mm.gcode</td>
      <td>1 min</td>
    </tr>
    <tr>
      <td>Profile cut</td>
      <td>1.5mm downcut</td>
      <td>lid_bottom-4-profile-1.5mm.gcode</td>
      <td>2 min</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/resized/images/2023-11-03/1280/e02.jpg" style="max-width: min(3000px, 30%); float: right; margin: 0.5em; margin-right: 0" alt="Photo of the workpiece showing the state after toolpath E02." srcset="    /assets/resized/images/2023-11-03/640/e02.jpg 640w,    /assets/resized/images/2023-11-03/768/e02.jpg 768w,    /assets/resized/images/2023-11-03/1024/e02.jpg 1024w,    /assets/resized/images/2023-11-03/1280/e02.jpg 1280w, /assets/images/2023-11-03/e02.jpg 3000w" sizes="(max-width: 60rem) calc(30/100 * 100vw), calc(30/100 * 60rem)" /></p>

<p>This of course could have been one gcode file, but I couldn’t be bothered for this little part at the end of all the work. In this step you will flatten the cover to the desired thickness while leaving a lip that prevents it from sliding out. Here you also create the pockets for the magnets and do the profile cut, which will go below your workpiece into your spoil board.</p>

<hr style="clear:both" />

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

<p>That’s it, the hard part is done. You should sand<sup id="fnref:hate:2" role="doc-noteref"><a href="#fn:hate" class="footnote" rel="footnote">5</a></sup> any problematic or rough parts and apply wood finish. I use <a href="https://www.amazon.de/dp/B08TWRJST5?psc=1&amp;ref=ppx_yo2ov_dt_b_product_details&amp;_encoding=UTF8&amp;tag=oughtabe-21&amp;linkCode=ur2&amp;linkId=056f51304a598794d03e7c3cad6423f2&amp;camp=1638&amp;creative=6742" target="_blank" class="affiliate">boiled Linseed oil</a>, but there are many options<sup id="fnref:linseed" role="doc-noteref"><a href="#fn:linseed" class="footnote" rel="footnote">7</a></sup> out there and many people with more experience in selecting them.</p>

<p>After that, you should be able to simply disassemble a Game Boy and replace its shell with your new wooden one.</p>

<p>I actually only used the original mainboard of a broken Game Boy from ebay and aftermarket parts for everything else, so let me show you a few details.</p>

<figure>

<img src="/assets/resized/images/2023-11-03/1280/asm1.jpg" style="max-width: min(3000px, 100%)" alt="Photo of the mostly empty bottom half of the wooden Game Boy shell. A metal shield from an original Game Boy has been screwed into it." srcset="    /assets/resized/images/2023-11-03/640/asm1.jpg 640w,    /assets/resized/images/2023-11-03/768/asm1.jpg 768w,    /assets/resized/images/2023-11-03/1024/asm1.jpg 1024w,    /assets/resized/images/2023-11-03/1280/asm1.jpg 1280w, /assets/images/2023-11-03/asm1.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>The original metal shield between cartridge slot and mainboard can simply be screwed into the wooden shell with the original screws.</figcaption>
</figure>

<p>First of all, the metal shield between mainboard and cartridge slot can simply be screwed into the wooden shell with the original screws (ok, those are also original parts). You just might need to file away some edges to fit the bent parts around the screw posts.</p>

<figure>

<img src="/assets/resized/images/2023-11-03/1280/asm2.jpg" style="max-width: min(3000px, 100%)" alt="Photo of the bottom half of the wooden Game Boy shell. Large parts are covered by a green PCB, which is the main board from an original Game Boy." srcset="    /assets/resized/images/2023-11-03/640/asm2.jpg 640w,    /assets/resized/images/2023-11-03/768/asm2.jpg 768w,    /assets/resized/images/2023-11-03/1024/asm2.jpg 1024w,    /assets/resized/images/2023-11-03/1280/asm2.jpg 1280w, /assets/images/2023-11-03/asm2.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>Bottom half with a mainboard and attached PCBs from an original Game Boy in place.</figcaption>
</figure>

<p>The same is true for the PCB and the attached boards. However, here I already had some problems of screws not holding well in the 1.5mm drills I did on the bottom half. The 1mm drills on the top half worked better, but posts tended to break there, so be careful here.</p>

<figure>

<img src="/assets/resized/images/2023-11-03/1280/asm3.jpg" style="max-width: min(3000px, 100%)" alt="Photo of the battery compartment of the wooden Game Boy shell with contacts in place." srcset="    /assets/resized/images/2023-11-03/640/asm3.jpg 640w,    /assets/resized/images/2023-11-03/768/asm3.jpg 768w,    /assets/resized/images/2023-11-03/1024/asm3.jpg 1024w,    /assets/resized/images/2023-11-03/1280/asm3.jpg 1280w, /assets/images/2023-11-03/asm3.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>Contacts are simply pushed into the slits, but might need some padding.</figcaption>
</figure>

<p>In the battery compartment you just have to insert the additional contacts. The outer contacts are already there from the PCB. Note, that in contrast to the photo above I had to put a thin piece of cardboard behind the contacts to press them against the batteries.</p>

<figure>

<img src="/assets/resized/images/2023-11-03/1280/asm4.jpg" style="max-width: min(3000px, 100%)" alt="Photo of the top half of the wooden Game Boy shell. Only a white D-pad and A and B buttons have been placed in there." srcset="    /assets/resized/images/2023-11-03/640/asm4.jpg 640w,    /assets/resized/images/2023-11-03/768/asm4.jpg 768w,    /assets/resized/images/2023-11-03/1024/asm4.jpg 1024w,    /assets/resized/images/2023-11-03/1280/asm4.jpg 1280w, /assets/images/2023-11-03/asm4.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>The aftermarket plastic buttons fit perfectly.</figcaption>
</figure>

<p>The white aftermarket plastic buttons fit perfectly, but there are some tricky aspects to the silicone pads between the buttons and the PCB. First of all, there are very thin poles that help aligning the silicone pads. They do not have to hold against any force, but still you should be careful to not break them as they really help putting everything together. Second, the silicone pad of the A and B button has a somewhat complex line around the buttons that looks roughly like a figure eight with an additional outline. I did not reproduce the slit for the additional outline, which prevents it from being flush on the wood by default. You either have to add a carving for the outline or cut the outline from the silicone pad like I did with my aftermarket pads.</p>

<figure>

<img src="/assets/resized/images/2023-11-03/1280/asm5.jpg" style="max-width: min(3000px, 100%)" alt="Two photos of the inside of the top half. The left photo has white buttons and silicone pads in place and shows how the display is placed in the case. The right photo shows the display PCB screwed in place, covering most the shell." srcset="    /assets/resized/images/2023-11-03/640/asm5.jpg 640w,    /assets/resized/images/2023-11-03/768/asm5.jpg 768w,    /assets/resized/images/2023-11-03/1024/asm5.jpg 1024w,    /assets/resized/images/2023-11-03/1280/asm5.jpg 1280w, /assets/images/2023-11-03/asm5.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>Left: Placing the IPS mod display with buttons and silicone pads already in place. Right: The display PCB of the IPS mod screwed into the top half of the shell.</figcaption>
</figure>

<p>For the display I decided against using an original screen<sup id="fnref:screen" role="doc-noteref"><a href="#fn:screen" class="footnote" rel="footnote">8</a></sup> and picked up a modern <a href="https://retrohahn.com/collections/display/products/gameboy-classic-q5-osd" target="_blank">IPS mod kit</a> instead. Note, that I made a few simplifications and optimizations to my design to fit this display. In particular, the contrast wheel has an additional lip on the original Game Boy and there are additional posts at the top of the display which I did not model. I think that an original display also fits, but you might look into this if you want to use a different screen.</p>

<p>I did not find a good way to add a small pole right next to the large pole in the bottom left corner of the display (viewed from the inside). Unfortunately, the plastic holder for the display that comes with the kit uses this pole as one of four poles that hold it in place. However, it still seems to be very stable when using only three of those poles. The PCB of the kit can simply be screwed into the shell with original screws and holds very well with the 1mm holes I drilled here. Only problem is that the poles below the display split in the process. So,… well… I am not sure what to make of this. The PCB is held very securely, but I am not sure if I will eventually find cracks in the case from the other screws over time.</p>

<figure>

<img src="/assets/resized/images/2023-11-03/1280/asm6.jpg" style="max-width: min(3000px, 100%)" alt="Two photos of the final assembly steps. The left photo shows how bottom and top half are connected with a ribbon cable. The right photo shows how a glass cover with a white bezel is placed into the assembled Game Boy." srcset="    /assets/resized/images/2023-11-03/640/asm6.jpg 640w,    /assets/resized/images/2023-11-03/768/asm6.jpg 768w,    /assets/resized/images/2023-11-03/1024/asm6.jpg 1024w,    /assets/resized/images/2023-11-03/1280/asm6.jpg 1280w, /assets/images/2023-11-03/asm6.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>Left: Both halves have to be connected with a ribbon cable. Right: Finally the glass cover from the IPS mod screen finishes the build.</figcaption>
</figure>

<p>Finally, all you need to do is to connect the two halves and glue the display cover into the screen bezel. There is just one major catch: For me the two halves were too tight and the screws could not properly hold them together. I am not sure if filing and sanding some parts for a better fit will help and if tapping the original screws into 1mm drill holes instead of the 1.5mm ones I have in the bottom half might be enough, but I only found a somewhat nasty solution: Gluing the case with wood glue.</p>

<figure>

<img src="/assets/resized/images/2023-11-03/1280/glued.jpg" style="max-width: min(3000px, 100%)" alt="Detail photo of the right side of the wooden Game Boy near the volume control and link cable slot. A small gap is visible as well as residue from wood glue." srcset="    /assets/resized/images/2023-11-03/640/glued.jpg 640w,    /assets/resized/images/2023-11-03/768/glued.jpg 768w,    /assets/resized/images/2023-11-03/1024/glued.jpg 1024w,    /assets/resized/images/2023-11-03/1280/glued.jpg 1280w, /assets/images/2023-11-03/glued.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>Unfortunately, I had to glue both halves together, resulting in a slight gap and some glue residue.</figcaption>
</figure>

<p>While I still love the resulting Game Boy and while it feels very solid and reliable, it bugs me a little to have a slight gap on one side and a bit of glue residue. If I ever want to reach the parts inside, I probably have to crack the case open, but at least no original parts had to be glued and could simply be replaced in the original Game Boy. Still, it would be nicer if I could just open the shell in case I ever need to do maintenance inside.</p>

<h2 id="conclusion">Conclusion</h2>

<p>When I started this project, I somewhat underestimated the amount of effort and time that had to go into it. But I also totally underestimated how stunning the result would be. At the moment it just sits on a sideboard in my office and I have to grin and pick it up each time I walk by it.</p>

<figure>

<img src="/assets/resized/images/2023-11-03/1280/power.jpg" style="max-width: min(3000px, 100%)" alt="Detail photo of the power switch and decorative lines on the case next to the power switch." srcset="    /assets/resized/images/2023-11-03/640/power.jpg 640w,    /assets/resized/images/2023-11-03/768/power.jpg 768w,    /assets/resized/images/2023-11-03/1024/power.jpg 1024w,    /assets/resized/images/2023-11-03/1280/power.jpg 1280w, /assets/images/2023-11-03/power.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>I totally love how precisely some of the iconic designs of the original Game Boy could be reproduced.</figcaption>
</figure>

<p>Initially, I planned to also machine the buttons out of a brighter type of wood. However, I did not find a good way to create a transparent display cover with matching wood applications and eventually settled for white aftermarket buttons and a matching display cover. Now that I see the result, I think that this was a good decision as it helps to define the shape and look.</p>

<figure>

<img src="/assets/resized/images/2023-11-03/1280/speaker.jpg" style="max-width: min(3000px, 100%)" alt="Detail photo of the speaker grill with the headphone jack and start button in frame." srcset="    /assets/resized/images/2023-11-03/640/speaker.jpg 640w,    /assets/resized/images/2023-11-03/768/speaker.jpg 768w,    /assets/resized/images/2023-11-03/1024/speaker.jpg 1024w,    /assets/resized/images/2023-11-03/1280/speaker.jpg 1280w, /assets/images/2023-11-03/speaker.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>Even though the speaker grill did not go through in my build, the precision of those lines as well as the headphone jack cutout, totally blew me away. Too bad that the white painted labels did not turn out so well.</figcaption>
</figure>

<p>Also, the Game Boy plays quite well. If I am looking for any playability problem, maybe the buttons trigger a little bit below their tactile response point and I am not entirely certain if this is to be expected. But this Game Boy will not be my daily gaming platform anyways. This is meant to be presented somewhere. At the moment I am thinking of making an acrylic stand for it and use my <a href="/a/wifi-game-boy-cartridge">WiFi cartridge</a> to use it as a status display in a prominent location.</p>

<figure>

<img src="/assets/resized/images/2023-11-03/1280/cartridges.jpg" style="max-width: min(3000px, 100%)" alt="Photo of the wooden Game Boy showing the title screen of Tetris. On its left, there is a wooden cartridge with the label Pokemon Walnut version. On its right, there is a brighter wooden cartridge with the label Pokemon Oak version." srcset="    /assets/resized/images/2023-11-03/640/cartridges.jpg 640w,    /assets/resized/images/2023-11-03/768/cartridges.jpg 768w,    /assets/resized/images/2023-11-03/1024/cartridges.jpg 1024w,    /assets/resized/images/2023-11-03/1280/cartridges.jpg 1280w, /assets/images/2023-11-03/cartridges.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>The Game Boy plays well, but I still think it is meant to be a showpiece and not a travel companion.</figcaption>
</figure>

<p>Oh, and if you have been wondering about the wooden Pokemon cartridges: Those are based on the <a href="https://github.com/Staacks/wooden-game-boy-cartridge" target="_blank">cartridge I published earlier</a> with several improvements to the toolpaths (man, I learned so much from the Game Boy project) and the Pokemon logo added. These versions are also in the <a href="https://github.com/Staacks/wooden-game-boy" target="_blank">GitHub repository for the wooden Game Boy</a>, but I did not add more documentation for it. If you got to this point, I am sure you can figure it out… :)</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:linux" role="doc-endnote">
      <p>Even though I am a huge FOSS fan I am absolutely willing to pay for good software. But I am not willing to do so for software that does not even run on my OS of choice. I do not expect that CAMs are written for Linux, I just don’t want to pay several hundred bucks on software that then turns out to crash regularly due to emulation. <a href="#fnref:linux" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:longsmallbit" role="doc-endnote">
      <p>Much longer than you would expect. The paths have to be closer to still overlap, but you also need to reduce the feed rate of your machine as the smaller bit cannot take as much material per rotation. Even worse: If you cannot increase the rotation speed, the smaller diameter also means that the tangential velocity of the cutting edges at the smaller diameter is significantly reduced. I would say that going from a 3mm bit to a 1mm bit will require about 9 times as much time to clear the same amount of material. <a href="#fnref:longsmallbit" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:bluetape" role="doc-endnote">
      <p>Noticed the blue tape on my dust shoe? That’s where the machine drove the dust shoe directly down into a clamp from above. Did not stop or even slow down. After all, the motors are designed to press a mill into hard wood or even some metal, so it probably did not even notice the plastic cover of the dust shoe. <a href="#fnref:bluetape" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:henceforth" role="doc-endnote">
      <p>Never used that word before. Hope I applied it correctly. <a href="#fnref:henceforth" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:hate" role="doc-endnote">
      <p>I hate sanding. <a href="#fnref:hate" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:hate:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:hate:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:aword" role="doc-endnote">
      <p>Is that a word? <a href="#fnref:aword" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:linseed" role="doc-endnote">
      <p>My main reason for using Linseed oil is that I already used it as non-toxic option on things I made for my kids. I like the natural look of it compared to a clear coat, but that’s all the input I have here. I am not doing this long enough to say how well it protects my wooden Game Boy. <a href="#fnref:linseed" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:screen" role="doc-endnote">
      <p>In the past I made a point of using an unmodified original Game Boy to demonstrate that my hacks work with original hardware. No reason to that on a modded Game Boy like this. <a href="#fnref:screen" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[You made me create a wooden Game Boy shell. Yes, you made me do it. After I got myself a CNC machine a while back I just made a wooden Game Boy cartridge to get to know the machine, but social media wanted more. So, I made a Game Boy out of walnut wood. Click the image to see the video on youtube.com. You can see the process in the video without much explanation. If you want to learn about the toolpaths, my reasoning behind a few things, lessons learned and what you should avoid if you try this yourself, read this blog post instead and check out my design files on GitHub.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://there.oughta.be/assets/images/2023-11-03/youtube.jpg" /><media:content medium="image" url="https://there.oughta.be/assets/images/2023-11-03/youtube.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">There oughta be a bullet time video booth.</title><link href="https://there.oughta.be/a/bullet-time-video-booth" rel="alternate" type="text/html" title="There oughta be a bullet time video booth." /><published>2023-05-26T00:00:00+02:00</published><updated>2023-05-26T00:00:00+02:00</updated><id>https://there.oughta.be/a/bullet-time-video-booth</id><content type="html" xml:base="https://there.oughta.be/a/bullet-time-video-booth"><![CDATA[<p>For my cousin’s wedding I did not make a photo booth but a video booth. - With an array of DSLRs to create a bullet time effect.</p>

<figure class="youtube">
<a href="https://youtu.be/QeJlaTUe0T0" target="_blank">
<img src="/assets/resized/images/2023-05-26/1280/youtube.jpg" style="max-width: min(1920px, 100%)" alt="Thumbnail of the youtube video: A cardboard cutout of a wedding couple is surrounded by a curved array of DSLRs. The title Bullet Time Video Booth is floating next to the cardboard couple in big letter." srcset="    /assets/resized/images/2023-05-26/640/youtube.jpg 640w,    /assets/resized/images/2023-05-26/768/youtube.jpg 768w,    /assets/resized/images/2023-05-26/1024/youtube.jpg 1024w,    /assets/resized/images/2023-05-26/1280/youtube.jpg 1280w, /assets/images/2023-05-26/youtube.jpg 1920w" sizes="(max-width: 60rem) 100vw, 60rem" />
</a>
<figcaption>Click the image to see the video on youtube.com.</figcaption>
</figure>

<p>Since this is a very visual project, you should really watch the video. This blog post contains the same info with more details, but those details are probably only relevant if you really want to recreate this project, which is not an easy thing to do.</p>

<!--more-->

<h2 id="what-is-a-bullet-time-video-booth">What is a bullet time video booth?</h2>

<p>The story of this project actually begins in 2017 when I created a video booth for my own wedding. At many wedding receptions you can find photo booths, which are simple camera setups with a remote trigger to allow guests to create some memories of the special day. Usually this involves silly props like hats, wigs and giant glasses and it is as much about creating photos as a memory as it is about the fun of being creative while doing so. …and for my wedding I mixed it up a bit by putting my Sony NEX-5T on a tripod and allowing guests to take short 5 second clips<sup id="fnref:wifisd" role="doc-noteref"><a href="#fn:wifisd" class="footnote" rel="footnote">1</a></sup> instead of static photos. Later I cut all the clips into one long video with upbeat music and we still enjoy watching this memory today.</p>

<p>My family enjoyed it so much, that one of my cousins used the same setup a few years later at her wedding and when another cousin planned his wedding for this year, he also asked for a video booth. But this time I wanted to try something new by adding a bullet time effect.</p>

<figure>

<video autoplay="" loop="" muted="" playsinline="">
  <source src="/assets/images/2023-05-26/demo-blog.mp4" type="video/mp4" />
</video>

<figcaption>A very low-res demonstration of what the bullet time video booth creates. Please check the Youtube video above for a proper quality demo.</figcaption>
</figure>

<p>In case you never heard of it: The <a href="https://en.wikipedia.org/wiki/Bullet_time" target="_blank">bullet time effect</a> has been around for a while, but became really famous when it was popularized with the movie “The Matrix” from 1999. It really became a signature style for these movies and has been copied and reused so many times that it pretty much became a movie cliché. But while it can easily be recreated in purely rendered form like animated movies or video games, it is still quite involved and costly to achieve with real cameras: You set up an array of photo cameras, trigger them all simultaneously<sup id="fnref:simultaneous" role="doc-noteref"><a href="#fn:simultaneous" class="footnote" rel="footnote">2</a></sup> and play back the individual photos as frames of a video. The result is that the scene seems to be frozen in time while the camera moves around freely.</p>

<p>And here you see the problem for hobbyists. If you are not rendering the entire scene but need to take photos of real people, this can quickly become expensive. Bullet time-time costs one camera per frame. For someone shooting with the typical “European” 25 fps<sup id="fnref:european" role="doc-noteref"><a href="#fn:european" class="footnote" rel="footnote">3</a></sup> that’s 25 cameras per second. And that of course also explains why my bullet time video booth only uses this effect for the transitions instead of recording several seconds of bullet time footage. I simply wanted to keep the costs for this project in a reasonable range by only using twelve cameras.</p>

<figure>

<img src="/assets/resized/images/2023-05-26/1280/booth.jpg" style="max-width: min(3840px, 100%)" alt="Low wide angle shot of a quarter-circle rack with twelve black and one white camera mounted on top. Bright video lights are mounted above the cameras and a room decorated for a wedding reception is visible in the background." srcset="    /assets/resized/images/2023-05-26/640/booth.jpg 640w,    /assets/resized/images/2023-05-26/768/booth.jpg 768w,    /assets/resized/images/2023-05-26/1024/booth.jpg 1024w,    /assets/resized/images/2023-05-26/1280/booth.jpg 1280w, /assets/images/2023-05-26/booth.jpg 3840w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>This is the video booth as seen from the guest's perspective. (Well, if the guests were dwarfs.) You can clearly see that the cameras are placed at greater distances towards the wall to the left to achieve the impression of it accelerating to/from the wall when played back in sequence.</figcaption>
</figure>

<p>This means that I only have a bit less than half a second of bullet time footage per guest. But as you could see in the little example clip above (and more in the Youtube video) the effect is still nice and appears longer. To achieve this I used two tricks:</p>
<ul>
  <li>I trigger the cameras at the beginning and the end of each video recording and they are arranged in an arch towards the wall behind the guests. By mirroring every second guest clip and playing the bullet time photos in different order I can combine the two twelve photo sequences from the end of the previous guest clip and the beginning of the next guest clip to make it look like the camera spins through the wall to the next clip. An additional blurred frame at the location where the camera would be inside the wall and a non-equidistant placement of the cameras for an accelerating motion towards the wall help to sell the effect. This adds up to 25 frames and therefore one second of bullet time footage.</li>
  <li>I created the final video of all guests (and the example above) in DaVinci Resolve and used its Optical Flow Estimation to generate artifical additional frames. This allows to smoothly slow down the bullet time effect further and also gradually slow down the video recording after leaving a bullet time transition and before entering the next bullet time transition.</li>
</ul>

<p>So, I was able to pull this of with twelve cameras plus the one for the video recording. But this setup requires more than just the cameras…</p>

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

<p>In this section I will go through the important components of the bullet time video booth. Keep in mind that pretty much everything was selected to keep the costs as low as possible. Unfortunately, this also means that some components were a bad decision in hind sight and some components are simply more expensive or better equipment which I happened to already have. So, if you want to build something similar, be prepared to pick your own components and adapt the software to your needs.</p>

<h3 id="bullet-time-camera-array">Bullet time camera array</h3>

<p>Let’s start by looking at the part that obviously is the most expensive because that purchase comes with a factor of twelve: The cameras used for the bullet time effect.</p>

<p>When I said that the bullet time effect is still expensive, I have to admit that it has become much cheaper since 1999. Cameras have become so readily available and good today that a comparison to 1999 equipment would be ridiculous. Still, I set myself a limit of 50€ per camera, which means that the twelve cameras alone would cost 600€, which I find quite heavy for a fun gimmick to a video booth<sup id="fnref:price" role="doc-noteref"><a href="#fn:price" class="footnote" rel="footnote">4</a></sup>. And now think about what cameras you can get for 50€… There are some possibilities like webcams, used smartphones, used action cams, etc. But used ones for 50€ will have poor image quality and their small sensors are not ideal for this effect as they require more light for short shutter speeds or you will get a lot of motion blur. Raspberry Pi cams are also an interesting alternative in the maker world, but the cheap ones have very poor image quality and the better ones cost 50€ alone without a lens - and you need a few actual Raspberry Pis which are even more expensive.</p>

<p>Instead, I looked for old used DSLRs. They have large (albeit old<sup id="fnref:oldsensors" role="doc-noteref"><a href="#fn:oldsensors" class="footnote" rel="footnote">5</a></sup>) sensors, they support means to remotely trigger the shutter, they have plenty of resolution, their settings can be controlled precisely and nobody except me is interested in them anymore. The trick is that we are using the photo mode of these old DSLRs to create a video, which means that we are comparing old photo specs with modern video specs. So, I looked for the oldest mainstream beginner DSLR that has a decent resolution and picked the Canon EOS 400D<sup id="fnref:400d" role="doc-noteref"><a href="#fn:400d" class="footnote" rel="footnote">6</a></sup> from 2006.</p>

<figure>

<img src="/assets/resized/images/2023-05-26/1280/canons.jpg" style="max-width: min(4080px, 50%)" alt="Photo of twelve Canon EOS 400d arranged in a circle on the floor." srcset="    /assets/resized/images/2023-05-26/640/canons.jpg 640w,    /assets/resized/images/2023-05-26/768/canons.jpg 768w,    /assets/resized/images/2023-05-26/1024/canons.jpg 1024w,    /assets/resized/images/2023-05-26/1280/canons.jpg 1280w, /assets/images/2023-05-26/canons.jpg 4080w" sizes="(max-width: 60rem) calc(50/100 * 100vw), calc(50/100 * 60rem)" />

<figcaption>Now I am the proud owner of twelve Canon EOS 400d...</figcaption>
</figure>

<p>That camera is now almost 17 years old, which means that craigslist<sup id="fnref:craigslist" role="doc-noteref"><a href="#fn:craigslist" class="footnote" rel="footnote">7</a></sup> is full of those cameras. There are so many people trying to sell their old camera with almost nobody being interested in such outdated technology<sup id="fnref:hipsters" role="doc-noteref"><a href="#fn:hipsters" class="footnote" rel="footnote">8</a></sup> that I could simply work through the offers starting with the oldest listings. I offered my 50 bucks for the 400D and its mediocre kit lens, reminded the seller that nobody has even looked at the listing in six months, and either he accepted or I contacted the next one in the list.</p>

<p>But is it good enough? Well, that’s where we can now compare the photo specs to the video specs. The old 400D from 2006 has a photo resolution (it cannot even shoot video) of 3888x2592 pixels, which is more than our modern 4k (or actually UltraHD) video with 3840x2160.</p>

<h3 id="video-camera">Video camera</h3>

<p>But since the Canons cannot record video<sup id="fnref:canonvideo" role="doc-noteref"><a href="#fn:canonvideo" class="footnote" rel="footnote">9</a></sup> I need one more camera as the main camera that shoots the video. Initially, when I selected the 400Ds, I aimed for 4k video and planned to use my trusted Sony a6400 for this. Unfortunately, it turned out to be rather difficult to trigger the video recording precisely and transfer the resulting video file. Both can be done via USB, but one requires the camera to be in control mode and the other one requires it to be in mass storage mode. Video files apparently cannot be transferred in PTP mode, which seems to be an oversight by Sony.</p>

<figure>

<img src="/assets/resized/images/2023-05-26/1280/a5000.jpg" style="max-width: min(3840px, 100%)" alt="Detail shot of two cameras on the bullet time rack: A black Canon EOS 400d and a white Sony a5000." srcset="    /assets/resized/images/2023-05-26/640/a5000.jpg 640w,    /assets/resized/images/2023-05-26/768/a5000.jpg 768w,    /assets/resized/images/2023-05-26/1024/a5000.jpg 1024w,    /assets/resized/images/2023-05-26/1280/a5000.jpg 1280w, /assets/images/2023-05-26/a5000.jpg 3840w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>While the bullet time shots were done with Canon EOS 400d, the video was recorded on the Sony a5000 that some of you might know from an older post.</figcaption>
</figure>

<p>I either needed to use a Wifi SD card like I did in my old original video booth or record externally with an HDMI grabber. Since transferring 4k videos with a Wifi card (or maybe even recording to that old Wifi SD card) seemed like a bad idea, I dropped the 4k goal and went with one of my cheap 1080p HDMI grabbers instead. And since I did not need to tie up my good camera in this project if I only record 1080p footage through a mediocre HDMI grabber, I used my Sony a5000 instead. Some of you might remember it from an older post, because it requires <a href="/a/way-to-use-a-sony-alpha-as-a-webcam">a hack to get a clean HDMI signal</a>.</p>

<figure>

<img src="/assets/resized/images/2023-05-26/1280/resolution.png" style="max-width: min(2190px, 50%)" alt="Drawing of three rectangles, illustrating the resolution of the photos taken by the Canon EOS 400d, which just encompasses the rectangle representing a 4k resolution and a significantly smaller 1080p resolution of the HDMI grabber." srcset="    /assets/resized/images/2023-05-26/640/resolution.png 640w,    /assets/resized/images/2023-05-26/768/resolution.png 768w,    /assets/resized/images/2023-05-26/1024/resolution.png 1024w,    /assets/resized/images/2023-05-26/1280/resolution.png 1280w, /assets/images/2023-05-26/resolution.png 2190w" sizes="(max-width: 60rem) calc(50/100 * 100vw), calc(50/100 * 60rem)" />

<figcaption>Only the a5000 and the HDMI grabber limit the video booth's resolution to 1080p. And a rather low quality 1080p if I am honest.</figcaption>
</figure>

<p>So, we are down to FullHD, but as a plus using an old cheap camera gives me some valuable peace of mind when leaving the bullet time rig unattended at the wedding venue.</p>

<h3 id="quarter-circle-stand">Quarter circle stand</h3>

<p>At that point I thought that I had bought the most expensive part of my bullet time rig - until I faced the question of how to mount the cameras. Whichever solution comes to your mind right now: Remember to multiply its cost by twelve and think again.</p>

<p>Using twelve individual super-cheap tripods is impractical as someone will easily hit one of the tripod feet and misalign a camera from the other ones. So, I was sure that I need a common stand to which I mount all cameras. I first thought of traverse material as found on stage, but that is super expensive. The next idea was to build something myself out of plywood when I stumbled upon a B-stock e-drum rack. Specifically, it was a rack for the Alesis Strike Pro SE. On its own this is not ideal because its components are not enough to form a proper quarter circle and the horizontal bars are not mounted at the same heights. But I got that B-stock rack and a new one for a total of 93€ and combined them in a different arrangement. This even left a few spare parts that I could use to mount a screen to the setup.</p>

<figure>

<img src="/assets/resized/images/2023-05-26/1280/mount.jpg" style="max-width: min(3840px, 100%)" alt="Detail shot of a Canon EOS 400d, which is mounted to two threaded plates and a clamp, which in turn is attached to the rack. One of the plates shows a crack." srcset="    /assets/resized/images/2023-05-26/640/mount.jpg 640w,    /assets/resized/images/2023-05-26/768/mount.jpg 768w,    /assets/resized/images/2023-05-26/1024/mount.jpg 1024w,    /assets/resized/images/2023-05-26/1280/mount.jpg 1280w, /assets/images/2023-05-26/mount.jpg 3840w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>This is a rather bad mounting solution. You can also see the crack in one of the threaded plates.</figcaption>
</figure>

<p>Unfortunately, that was not the end of the problem, because I still needed to mount the cameras to the rack. That’s where I went for a solution that was too cheap. I got cheap clamps from aliexpress (which were fine) and attached them to the cameras with simple threaded rods and cheap threaded plates. My thought was that twelve cheap ballhead mounts are quite expensive in total and that instead I could rotate the clamp around the rack bar and adjust the second axis by tightening the threaded plates. Unfortunately, I ended up just tightening the clamps to the cameras until the threaded plates cracked, which worked ok that one time, but if I want to use the setup again, I need to replace all plates - and probably buy twelve of those cheap ballhead mounts after all.</p>

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

<p>Another bad solution that I cannot recommend is the power supplies. You obviously don’t want to rely on batteries, so a set of dummy batteries from aliexpress had to be purchased (again, factor twelve - you see how this adds up?). For some reason I thought that getting ones that plug into USB ports instead of AC outlets would be a good idea, although both need to convert the voltage to the camera’s 7.4V. The tempting idea was that I could just plug all cameras into two 8-port multi chargers.</p>

<p>That was until I did my first tests and the cameras reset instead of taking a picture. At least when I triggered all twelve cameras simultaneously. Because in that case the chargers could not keep up with the sudden surge current and were unable to provide the proper voltage for the cameras. I think the problem here is that these old DSLRs need next to no power in standby as they do not show a live view. But when triggered, they have to move the mirror as well as the shutter while suddenly reading and processing the sensor - and they were designed with a Li-Ion battery in mind and not a cheap USB-based converter. The chargers on the other hand expect a more or less constant high current draw from a device that actually buffers sudden power demands with its own battery.</p>

<figure>

<img src="/assets/resized/images/2023-05-26/1280/chargers.jpg" style="max-width: min(3840px, 75%)" alt="Photo of a stack of six white mutli-chargers. Each has six USB ports and a little display indicating voltage and current." srcset="    /assets/resized/images/2023-05-26/640/chargers.jpg 640w,    /assets/resized/images/2023-05-26/768/chargers.jpg 768w,    /assets/resized/images/2023-05-26/1024/chargers.jpg 1024w,    /assets/resized/images/2023-05-26/1280/chargers.jpg 1280w, /assets/images/2023-05-26/chargers.jpg 3840w" sizes="(max-width: 60rem) calc(75/100 * 100vw), calc(75/100 * 60rem)" />

<figcaption>In the end I had to get six of those charger, which certainly was a rather bad solution to the problem of powering the cameras.</figcaption>
</figure>

<p>Regardless of the cause of the problem I tried many chargers and most of them struggled with the triggering cameras. In the end the multi-charger was the most reliable one if only two cameras were connected, so I ended up buying six of these multi-chargers, which of course was way more expensive and inefficient than using dummy batteries with individual AC power supplies in the first place.</p>

<figure>

<img src="/assets/resized/images/2023-05-26/1280/spaghetti.jpg" style="max-width: min(3840px, 100%)" alt="Photo of a large gray storage box with half open lid. Inside you can see many cables, power outlets, power supplies and plugs." srcset="    /assets/resized/images/2023-05-26/640/spaghetti.jpg 640w,    /assets/resized/images/2023-05-26/768/spaghetti.jpg 768w,    /assets/resized/images/2023-05-26/1024/spaghetti.jpg 1024w,    /assets/resized/images/2023-05-26/1280/spaghetti.jpg 1280w, /assets/images/2023-05-26/spaghetti.jpg 3840w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>Most of the cables could simply be hidden in a storage box.</figcaption>
</figure>

<h3 id="usb-connections">USB connections</h3>

<p>And while we are talking about USB problems, the data transfer was not any easier. I connected the twelve cameras through two active USB hubs. I think this is close to the limit of what you want to manage as a large web of USB cables. Larger projects should instead use a few Raspberry Pis and connect via ethernet, but twelve cameras plus a Rapberry Pi Pico (see next section) plus the HDMI grabber (on its own USB bus) should be managable, right?</p>

<p>Well, it worked on a Raspberry Pi 3 which I first used as the brain of the bullet time rig. I had some electrical issues like the entire USB bus resetting when I turn on the fluorescent lights in my basement, but in principle I could control and read-out all cameras reliably. But since the Pi 3 was too slow to generate a preview in under a minute I replaced it with an old Dell XPS 12. And suddenly I could not use all USB devices at the same time.</p>

<p>After some research I learned that the Intel xHCI USB3 host controller in that device has a limit of 96 endpoints and that USB3 uses (at least) three endpoints per device. That’s still plenty? Well, don’t forget that each USB hub and all internal USB devices like the laptop’s Wifi module, Bluetooth module, webcam, sensor hub and internal USB hubs count towards that limit. In the end, after blacklisting some devices (no BIOS option to disable the webcam) I still had one device more than the controller could handle.</p>

<p>The solution? Connecting the USB3 hubs through a cheap USB2 cable. Seriously. That forced the devices into USB2 mode, reduced the number of endpoints and solved the problem.</p>

<h3 id="camera-trigger">Camera trigger</h3>

<p>Ok, so at that point we have all cameras mounted, they have power and they can be controlled via USB. That’s it, right? Not necessarily. You can trigger the cameras via USB and that’s what I tried first. But I found that this was not reliable and precise enough and that some cameras would trigger with a minor delay. Not much, barely noticable from the sound of the camera shutters, but if you play back the photos as a bullet time clip, you would notice that some photos were taken at slightly different moments in time - at least if there is fast motion in front of the camera.</p>

<p>So, instead I used the trigger inputs of the Canon cameras. These are fairly simple: It is a smaller TRS audio jack with a common ground and two contacts that control the autofocus and the shutter. These two contacts are pulled up to 3.3V by the camera and if you connect them to ground they signal the camera to focus or to trigger, respectively.</p>

<figure>

<img src="/assets/resized/images/2023-05-26/1280/trigger.jpg" style="max-width: min(3840px, 50%)" alt="Photo of a TRS audio connector with labels according to its use when triggering Canon cameras. The sleeve is labeled ground, the ring is labeled focus and the tip is labeled shutter." srcset="    /assets/resized/images/2023-05-26/640/trigger.jpg 640w,    /assets/resized/images/2023-05-26/768/trigger.jpg 768w,    /assets/resized/images/2023-05-26/1024/trigger.jpg 1024w,    /assets/resized/images/2023-05-26/1280/trigger.jpg 1280w, /assets/images/2023-05-26/trigger.jpg 3840w" sizes="(max-width: 60rem) calc(50/100 * 100vw), calc(50/100 * 60rem)" />

<figcaption>The trigger cable for the Canon DSLRs is a simple TRS audio connector.</figcaption>
</figure>

<p>If you want to build a proper solution, you would now design a little circuit for each camera that decouples this electrically via optocouplers which in turn you control through some simple transistors and a GPIO pin of the microcontroller of your choice. If you are lazy, you simply connect all trigger inputs in parallel directly to the GPIO pin, set it to open drain mode to avoid it fighting with the camera’s logic level and let it drive all twelve pins to ground through a common connection.</p>

<p>I chose the lazy solution.</p>

<figure>

<img src="/assets/resized/images/2023-05-26/1280/pi-pico.jpg" style="max-width: min(3840px, 100%)" alt="Photo of a Raspberry Pi Pico, held upright by a USB cable. The jack of an audio cable and a USB-A connector are draped next to the Pi Pico. In the back you can see several Dupont connectors attached to the Pi Pico's pins." srcset="    /assets/resized/images/2023-05-26/640/pi-pico.jpg 640w,    /assets/resized/images/2023-05-26/768/pi-pico.jpg 768w,    /assets/resized/images/2023-05-26/1024/pi-pico.jpg 1024w,    /assets/resized/images/2023-05-26/1280/pi-pico.jpg 1280w, /assets/images/2023-05-26/pi-pico.jpg 3840w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>The Raspberry Pi Pico basically offers a serial interface via USB, which allows to pull either of the two signal lines of the trigger cable to ground.</figcaption>
</figure>

<p>The advantage of this is that you can use cheap audio splitters and a bunch of cheap audio cables to connect all twelve cameras. Everything, including the GPIO connection, is just connected in parallel.</p>

<p>The disadvantage is electrical issues as all camera logic levels are connected through their pullups and all grounds are connected through the trigger cables as well as the USB spaghetti and dummy batteries with their voltage converters and USB chargers. You can tell that this is not how these triggers are meant to be used when you notice that all cameras trigger randomly if one of the cameras is turned off. This is an electrical nightmare and probably also part of the reason that the USB bus resets when I turn on the lights. I cannot really recommend doing this. …but it works.</p>

<p>Oh, the code for the Pi Pico of course is as simple as it can get, but if it helps you can get it from <a href="https://github.com/Staacks/there.oughta.be/tree/master/bullet-time-video-booth" target="_blank">my github repository</a>.</p>

<h3 id="bluetooth-push-buttons">Bluetooth push buttons</h3>

<p>A part that I am actually somewhat proud of are the big push buttons. Not because they are such an intricate design, but because they are mostly a spontaneous solution that worked perfectly. I left them for the end of the project thinking that I just need to add cables and connect them to one of the many GPIO pins of the Pi Pico that also controls the shutters. But when I got to that point I was uncertain on where the buttons would be placed at the wedding reception.</p>

<figure>

<img src="/assets/resized/images/2023-05-26/1280/buttons.jpg" style="max-width: min(3840px, 100%)" alt="Photo of two big push buttons as commonly used for emergency stops. The right one is red and the left one has been spray painted to be green. A hand is resting above the red button. The rack of the video booth is visible in the background." srcset="    /assets/resized/images/2023-05-26/640/buttons.jpg 640w,    /assets/resized/images/2023-05-26/768/buttons.jpg 768w,    /assets/resized/images/2023-05-26/1024/buttons.jpg 1024w,    /assets/resized/images/2023-05-26/1280/buttons.jpg 1280w, /assets/images/2023-05-26/buttons.jpg 3840w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>The green button is used to start the recording countdown and to confirm that a recording should be kept when the guests review their result. The red button discards a recording or aborts a countdown.</figcaption>
</figure>

<p>So, I spontaneously decided to make them wireless. And since I am not a fan of keeping a stock of Li-Ion batteries<sup id="fnref:liion" role="doc-noteref"><a href="#fn:liion" class="footnote" rel="footnote">10</a></sup> and also wanted to be able to have spare batteries available if necessary, I wanted to use good old AA batteries. So, I checked my stock of microcontrollers, ruled out the ESP32s which are a bad mix with AA batteries<sup id="fnref:aaesp" role="doc-noteref"><a href="#fn:aaesp" class="footnote" rel="footnote">11</a></sup> and found the Raspberry Pi Pico Ws that I bought with another electronics order and had not used yet.</p>

<p>These are cheap, can easily run on two AA batteries and have a Bluetooth module on board (like the ESP). They are also small and easily fit into the buttons together with the batteries. Only problem: Their Bluetooth module has only been implemented recently and it is still a bit tricky to get this set up. Also, Bluetooth is currently only available in the C SDK. But once I figured it out I had two big push buttons, running on AA batteries all night long. They connect to any device that supports Bluetooth Low Energy as a normal HID interface, i.e. they act as a normal Bluetooth keyboard that only has a single button.</p>

<figure>

<img src="/assets/resized/images/2023-05-26/1280/pico-w.jpg" style="max-width: min(3840px, 100%)" alt="Photo of an opened push button with the button part half removed from its base. A few cables and the end of a Raspberry Pi Pico W can be seen on the inside." srcset="    /assets/resized/images/2023-05-26/640/pico-w.jpg 640w,    /assets/resized/images/2023-05-26/768/pico-w.jpg 768w,    /assets/resized/images/2023-05-26/1024/pico-w.jpg 1024w,    /assets/resized/images/2023-05-26/1280/pico-w.jpg 1280w, /assets/images/2023-05-26/pico-w.jpg 3840w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>The Raspberry Pi Pico W fits easily into the push buttons together with two AA batteries.</figcaption>
</figure>

<p><del>Only bad news is that the licenses for the Bluetooth example that I used have not yet been adapted by the Raspberry Pi foundation. By that I mean that my code is based on <a href="https://github.com/bluekitchen/btstack/blob/master/example/hid_keyboard_demo.c" target="_blank">hid_keyboard_demo.c</a> of the Blue Kitchen Bluetooth stack, which is currently only referenced by the Raspberry Pi Foundation’s <a href="https://github.com/raspberrypi/pico-examples" target="_blank">pico-examples</a> repository. While <a href="https://github.com/raspberrypi/pico-sdk/issues/1164#issuecomment-1372677903" target="_blank">some rumors</a> speak of an upcoming maker-friendly license, the demo is currently only available under the Blue Kitchen license, which explicitly prohibits any redistribution for commercial purposes. Since I receive ad revenues for my projects (i.e. Youtube) I cannot claim that there is no commercial purpose and hence I am not allowed to share my code at this point.</del></p>

<p><del>If the code is eventually released under a more permissive license, please let me know and I will happily share the changes I made.</del></p>

<p><del>Until then, it is not too tricky to adapt <a href="https://github.com/bluekitchen/btstack/blob/master/example/hid_keyboard_demo.c" target="_blank">hid_keyboard_demo.c</a>. Connect the push button such that a GPIO pin is connected to ground by the button, enable the internal pullup of that GPIO pin and modify the demo such that a keypress is sent when the GPIO pin goes low.</del></p>

<p>Update from October 2024: The license is still pretty much the same, but I do not think that anyone would still consider my code to be commercial after this time. So, you can now find it on <a href="https://github.com/Staacks/there.oughta.be/tree/master/bullet-time-video-booth/bleButton">github</a>.</p>

<h3 id="lights">Lights</h3>

<p>Last but not least, you cannot neglect the lights. On one hand you want your shutter speed to be short enough to avoid motion blur from jumping guests. On the other hand you also do not want to use any auto-mode of the cameras. You want to avoid auto-focus, auto-exposure and auto-whitebalance, because the cameras would get different results and the pictures from each camera would be slightly different, which in turn would lead to flickering when playing them back in sequence.</p>

<p>So, you have to manually focus all lenses to a fixed spot and you either have to set the exposure and autobalance via USB for all cameras and automatically adapt it to changing light of the environment as the sun sets - or you bring controlled lights that dominate the other lights, so you can work with a fixed exposure and whitebalance through the entire day and night.</p>

<p>I already had a selection of video lights from the Aputure Amaran series and brought a 200d (200W COB LED light with a light dome attached), a 60d (60W COB LED light with an umbrella), an HR672C (larger LED panel) and two F7s (small LED panels). That won’t impress a professional videographer, but it’s a decent collection for a hobbyist and still I could notice some color shifts when the sun hit the windows of the room during sunset.</p>

<p>Oh, and don’t forget to think about the impact on the actual wedding reception. In this case I was lucky as I could set up the video booth in a separate room that was not used at that time and everyone became curious because of the bright white light that came from the door to the other room. But if you are in the same room as the dance floor, people will not be happy if you kill the mood by bringing your own personal sun without shielding it somehow. Just sayin’.</p>

<h2 id="software">Software</h2>

<p>Well, that was the difficult part. In contrast to some other projects, figuring out the cheap solution and getting all the hardware was indeed more time consuming than implementing the software. Besides the two minimal firmwares for the Raspberry Pi Picos (the camera trigger and the push buttons) the software only consists of a bit of Python code, which you can grab from <a href="https://github.com/Staacks/there.oughta.be/tree/master/bullet-time-video-booth" target="_blank">my github repository</a>.</p>

<p>As mentioned above, the brain of the bullet time video booth is an old Dell XPS 12. This is definitely not an ideal device here as it is an older convertible laptop without particularly impressive processing power. In fact, this might be one of the worst laptops I ever owned since I never really used it in tablet mode and it had a lot of shortcomings when you think of it as an expensive laptop<sup id="fnref:xps12" role="doc-noteref"><a href="#fn:xps12" class="footnote" rel="footnote">12</a></sup>. But it was not in use and had significantly more processing power than the Raspberry Pi 3 that I tried to use first.</p>

<p>The thing is that this device shows the interface to the guests, which is either a simple countdown or an idle loop of previously recorded bullet time clips from other guests. But it also has to retrieve the photos from the cameras, record the stream from the HDMI grabber and convert the photos and that stream into video clips for a preview and that idle loop. On the Pi 3 it took more than a minute to create a 720p preview while the guests waited to see what they just recorded. On the XPS 12 it takes 20 seconds, which could still be improved with a newer device, but which is bearable for the guests.</p>

<figure>

<img src="/assets/resized/images/2023-05-26/1280/idle.jpg" style="max-width: min(3840px, 100%)" alt="Photo of one end of the video booth where a larger screen is mounted above the cameras and a smaller screen below the cameras. The push buttons are visible in front of this part of the video booth slightly to the right." srcset="    /assets/resized/images/2023-05-26/640/idle.jpg 640w,    /assets/resized/images/2023-05-26/768/idle.jpg 768w,    /assets/resized/images/2023-05-26/1024/idle.jpg 1024w,    /assets/resized/images/2023-05-26/1280/idle.jpg 1280w, /assets/images/2023-05-26/idle.jpg 3840w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>The upper screen shows a countdown, a 720p preview version of a new recording or an idle loop of already processed 1080p recordings. The lower screen shows a live view from the video camera (the a5000).</figcaption>
</figure>

<p>The conversion to video clips is not a simple concatenation in ffmpeg. In order for the bullet time effect to look good, the cameras have to be aligned perfectly, which is nearly impossible with the cheap mount solution that I picked. Also, you have to expect that the alignment will not remain perfect throughout the entire wedding reception as guests might accidentially bump into cameras<sup id="fnref:guests" role="doc-noteref"><a href="#fn:guests" class="footnote" rel="footnote">13</a></sup>. So, instead of trying to achieve a perfect alignment, I made sure that the focal length of the bullet time cameras is a bit shorter (i.e. wider viewing angle), so that I can align and crop the resulting images later. This is the same as image stabilization for a video with the benefit that a misaligned frame does not come with additional motion blur. So, I used ffmpeg’s image stabilization for the clips that were generated on-site (preview and idle loop).</p>

<p>The filters in ffmpeg also came in handy to create what I call a “Pseudo in-wall frame”. That additional blurred frame at the location where the camera would be inside the wall, which I mentioned earlier. This is simply the photo from the camera closest to the wall, shifted slightly so that the wall is in the center of the image. A strong directional blur then masks the fact that the perspective is still far from the wall and only leaves a wall-colored smear that sells the idea of the motion blur that you might see if you could actually move through the wall.</p>

<p>The entire thing is implemented in Python and uses gphoto2 to communicate with the cameras via USB and an ffmpeg-wrapper module to generate the preview clips as well as the higher quality 1080p clips that are shown while the booth is not being used. The interface however is implemented in Flask and shown in Chromium. The reason for this is that the video players in webbrowser are among the best players in terms of performance and frame-precise playback. Since the clips are shown randomly during the idle loop they have to alternate between the mirrored and regular version and in order to sell the impression of the camera moving through the wall, the next clip has to start playback precisely on the last frame of the previous clip. This can be achieved with two HTML5 videos that alternate their playback state, so that one can preload the next clip while the other one is visible and playing. With one of the players being mirrored with a simple CSS transformation and without any noticable performance cost, the alternating flipping comes naturally in this system.</p>

<h2 id="post-processing">Post-processing</h2>

<p>Now, there is one last step missing to get the bullet time clips from the preview above. The ffmpeg version only generated a rather generic image stabilization by trying to stabilize the entire image, which includes countering the apparent rotating motion of the camera. Instead we want to stabilize the central point around which the camera moves. This is something I had to do manually in post using DaVinci Resolve. I used a simple tracker and a bullet time clip in which a person stood quite precisely in the place that should be the pivot point for the camera rotation. I tracked some features on the person and stabilized them while also zooming into the footage slightly to avoid black corners around the repositioned images.</p>

<p>There is no reason to do this tracking for every clip, as the solution of the reconstructed camera motion can simply be applied to all other bullet time shots as long as the cameras have not been moved. So, I only had to do this three times for the entire footage from the reception when a guest bumped into a camera and changed its alignment. In the end there were some cameras that were so misaligned that black borders were no longer avoidable and I masked this with a blurry scaled copy of the orignal image behind the stabilized one.</p>

<figure>

<img src="/assets/resized/images/2023-05-26/1280/resolve.jpg" style="max-width: min(3840px, 100%)" alt="Screenshot of the tracker stabilization in DaVinci Resolve. The lower part of the interface shows a node setup for the stabilization and a blurry background to mask black edges where the image had to be moved too far. The top left shows the original image sequence with tracking markers and the top right shows the transformed stabilized version." srcset="    /assets/resized/images/2023-05-26/640/resolve.jpg 640w,    /assets/resized/images/2023-05-26/768/resolve.jpg 768w,    /assets/resized/images/2023-05-26/1024/resolve.jpg 1024w,    /assets/resized/images/2023-05-26/1280/resolve.jpg 1280w, /assets/images/2023-05-26/resolve.jpg 3840w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>Stabilization with simple trackers in DaVinci Resolve.</figcaption>
</figure>

<p>Another important processing step in DaVinci Resolve is adding additional frames. The one second bullet time transition generated with ffmpeg on-site already looked quite nice, but with optical flow estimation it is possible for many clips to add almost perfect additional artificial frames. So, I set up a slowed down version with Resolve’s Time Warp feature, which does not work for every scene and sometimes creates obvious artifacts, but which helped to really bring out the bullet time effect for scenes on which it worked nicely.</p>

<p>In the end my cousin’s guests recorded more than 100 clips, which I had to edit into a longer video. Luckily, DaVinci Resolve can be automated with Python, so I set it up to automatically generate stabilized versions of all recordings with different amounts of additional artifical frames. This means that I had a collection with different variants of each clip, so I could pick which version looked best and also which had the right duration to match different music tracks. So, of course, not every clip ended with a bullet time transition as slow as shown in the demo above. In the end, nobody would want to sit through a video with a hundred of those, but it helped to showcase some really cool transitions with airborne kids, flowing hair and flying items<sup id="fnref:privacy" role="doc-noteref"><a href="#fn:privacy" class="footnote" rel="footnote">14</a></sup>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>At this point I have to say that the final video worked out even nicer than I had hoped for. In fact, the part that bugs me most in the end is that this ugly wood panel wall in the background really downgrades the final look, but that could not be helped. If there is a next time (as in another occasion, not as in that same cousin marrying again) I should prepare a backdrop. Before the actual wedding I was really worried that I would spend a lot of time debugging and fixing things instead of celebrating with my family, because there were so many things that failed during tests. In the end there are 13 cameras in a rather naive electrical setup and if any one of them got stuck or had any other type of problem, the shots would be ruined. I had to deal with loose memory card covers that turned off a camera, a lose contact in one of the dummy batteries, some cameras dropping out due to a bad USB cables and of course all the troubles I had until I had enough power supplies to get a somewhat stable voltage when the cameras are triggered.</p>

<p>I got really lucky that none of this happened on that particular day. I had included a simple recovery code that detects unresponsive cameras and tries to reconnect the USB hub with all the cameras while warning the guests to wait a minute or call me if the issue persists. According to the recordings and what I heard from the guests this happened once, but I did not have to personally tend to it even once.</p>

<p>Still the point is that you should not try to set this up for your own wedding. Someone with some spare time and the appropriate knowledge should be ready to spontaneously fix things, which usually does not apply to the bride or groom. If you want this for your wedding, ask a nerd friend to look after it and if you want to build this for a friend, make sure that you have time to fix things on site.</p>

<p>But if it works, this is a fantastic alternative to the traditional photo booth. You can be sure that guests have never seen something like this before (well, unless my Youtube video becomes more succesful than I would expect), they will have a lot of fun posing in front of the camera array and playing with the bullet time effect (especially later in the evening) and the happy couple really enjoys watching the result and sahring it with their guests after the wedding.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:wifisd" role="doc-endnote">
      <p>The NEX-5T has extremely limited interfaces to start recordings and retrieving the results. So, that video booth used a DIY infrared remote to control the recording and a Wifi SD card to retrieve the results and display them to the guests. <a href="#fnref:wifisd" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:simultaneous" role="doc-endnote">
      <p>Actually, not triggering the entire array simultaneously can also be interesting. Depending on the timing of the cameras and the order in which you play the individual frames a number of different effects can be achieved where time does not halt but only slow down, reverse or speed up. <a href="#fnref:simultaneous" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:european" role="doc-endnote">
      <p>Of course, with digital cameras and when publishing the footage to Youtube the technical reasons to pick one of the common framerates (24fps for cinema, 25fps for PAL, 30fps for NTSC) are so weak that many consider this to be an aesthetical choice. But after I came back from a trip to New York City to notice that all my recordings at night were flickering badly because of the street lights using the typical US 60Hz power grid, I have a strong preference to make sure that the local power grid frequency is a multiple of my frame rate. <a href="#fnref:european" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:price" role="doc-endnote">
      <p>For me this is a mixed calculation. My Youtube channel has grown enough that I can expect some returns although I would be surprised if the revenue could cover the entire project. The rest is attributed to a favor for my cousin and the child in me who always wanted to play with the bullet time effect. <a href="#fnref:price" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:oldsensors" role="doc-endnote">
      <p>In this case “old” has the consequence of significantly more noise at higher ISO settings. This is indeed a problem as we want fast shutter speeds. But in my experience the old large sensors are still better than any non-flagship smartphone camera with its tiny sensors. (And actually also the flagship ones, but since everyone just looks at the AI-enhaced photos on the tiny screens, not making that distinction will only provoke comments from users who refuse to believe that there is a difference between a 1000€ phone and a 1000€ camera.) <a href="#fnref:oldsensors" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:400d" role="doc-endnote">
      <p>Also known as Canon EOS Kiss Digital X in Japan and Canon EOS Rebel XTi in the US. I would like to talk to someone from Canon’s marketing department about this… <a href="#fnref:400d" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:craigslist" role="doc-endnote">
      <p>I probably consume too much US content, but it feels wrong to mention a different site while writing in English. Of course, it was not craigslist, because we do not have that in Germany. It was “ebay-kleinanzeigen”. (Which is no longer owned by ebay and just dropped the “ebay” in its name a few weeks ago.) <a href="#fnref:craigslist" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:hipsters" role="doc-endnote">
      <p>Also not outdated enough to be bought by Hipsters. <a href="#fnref:hipsters" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:canonvideo" role="doc-endnote">
      <p>Even if they could: That’s where their age would certainly show. <a href="#fnref:canonvideo" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:liion" role="doc-endnote">
      <p>It’s a personal habbit of trying to avoid old forgotten Li-Ion batteries after my wife’s laptop battery caught fire in the middle of the night. <a href="#fnref:liion" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:aaesp" role="doc-endnote">
      <p>The ESP32s are fine, but the common development boards don’t have a voltage converter that would be able to run on 3V. So, you have the choice of using three AA batteries and a dropout when they are half empty or four AA batteries with fairly high losses. <a href="#fnref:aaesp" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:xps12" role="doc-endnote">
      <p>Not to mention that shortly after I stopped using it the display leaked some goo that messed up the inside and keyboard and ruined the display itself. <a href="#fnref:xps12" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:guests" role="doc-endnote">
      <p>I noticed two times in post processing when the camera alignment changed and could actually find the exact clip of when it happened. After all there are ten or more people squeezing into that quarter circle trying to show some action for the recording, so I am surprised that it did not happen more than twice. <a href="#fnref:guests" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:privacy" role="doc-endnote">
      <p>Unfortunately, I cannot show any of those. I would not show a person without their consent and decided against asking random guests if they would want to appear on Youtube. So, you only get to see my closer family and only those who answered to an open “who volunteers their clip?” question without me pushing this on anybody. <a href="#fnref:privacy" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[For my cousin’s wedding I did not make a photo booth but a video booth. - With an array of DSLRs to create a bullet time effect. Click the image to see the video on youtube.com. Since this is a very visual project, you should really watch the video. This blog post contains the same info with more details, but those details are probably only relevant if you really want to recreate this project, which is not an easy thing to do.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://there.oughta.be/assets/images/2023-05-26/youtube.jpg" /><media:content medium="image" url="https://there.oughta.be/assets/images/2023-05-26/youtube.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">There oughta be a Q&amp;amp;A about the GB Interceptor.</title><link href="https://there.oughta.be/a/q-and-a/about-the-gb-interceptor" rel="alternate" type="text/html" title="There oughta be a Q&amp;amp;A about the GB Interceptor." /><published>2023-02-21T00:00:00+01:00</published><updated>2023-02-21T00:00:00+01:00</updated><id>https://there.oughta.be/a/q-and-a/q-and-a-gb-interceptor</id><content type="html" xml:base="https://there.oughta.be/a/q-and-a/about-the-gb-interceptor"><![CDATA[<p>I collected your questions about the GB Interceptor and made a very long video taking my time to answer them.</p>

<figure class="youtube">
<a href="https://youtu.be/e8fHzE0ywkw" target="_blank">
<img src="/assets/resized/images/2023-02-21/1280/youtube.jpg" style="max-width: min(1920px, 100%)" alt="Thumbnail of the youtube video: A photo of a Game Boy Color with a stack of cartridges that is several times longer than the Game Boy itsel. The stack consists of three different GB Interceptors plugged into each other and a Game Boy Camera plugged into the last one." srcset="    /assets/resized/images/2023-02-21/640/youtube.jpg 640w,    /assets/resized/images/2023-02-21/768/youtube.jpg 768w,    /assets/resized/images/2023-02-21/1024/youtube.jpg 1024w,    /assets/resized/images/2023-02-21/1280/youtube.jpg 1280w, /assets/images/2023-02-21/youtube.jpg 1920w" sizes="(max-width: 60rem) 100vw, 60rem" />
</a>
<figcaption>Click the image to see the video on youtube.com.</figcaption>
</figure>

<p>And sorry if this pops up in your RSS feed several months later. I simply forgot to add a blog entry for this and backdated it.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I collected your questions about the GB Interceptor and made a very long video taking my time to answer them. Click the image to see the video on youtube.com. And sorry if this pops up in your RSS feed several months later. I simply forgot to add a blog entry for this and backdated it.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://there.oughta.be/assets/images/2023-02-21/youtube.jpg" /><media:content medium="image" url="https://there.oughta.be/assets/images/2023-02-21/youtube.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">There oughta be a Game Boy capture cartridge.</title><link href="https://there.oughta.be/a/game-boy-capture-cartridge" rel="alternate" type="text/html" title="There oughta be a Game Boy capture cartridge." /><published>2022-12-20T00:00:00+01:00</published><updated>2022-12-20T00:00:00+01:00</updated><id>https://there.oughta.be/a/game-boy-capture-cartridge</id><content type="html" xml:base="https://there.oughta.be/a/game-boy-capture-cartridge"><![CDATA[<p>I present to you: The <strong>GB Interceptor</strong>. It is an adapter that goes between an unmodified Game Boy and the cartridge and offers a video stream of the game via USB.</p>

<figure class="youtube">
<a href="https://youtu.be/6mOJtrFnawk" target="_blank">
<img src="/assets/resized/images/2022-12-20/1280/youtube.jpg" style="max-width: min(1920px, 100%)" alt="Thumbnail of the youtube video: A render image of a Game Boy showing a game of Tetris in progress. From its top protrudes the PCB of the GB Interceptor with a Tetris cartridge on top. To the right is a label saying USB and an arrow pointing towards a screenshot of OBS showing the same image as the Game Boy." srcset="    /assets/resized/images/2022-12-20/640/youtube.jpg 640w,    /assets/resized/images/2022-12-20/768/youtube.jpg 768w,    /assets/resized/images/2022-12-20/1024/youtube.jpg 1024w,    /assets/resized/images/2022-12-20/1280/youtube.jpg 1280w, /assets/images/2022-12-20/youtube.jpg 1920w" sizes="(max-width: 60rem) 100vw, 60rem" />
</a>
<figcaption>Click the image to see the video on youtube.com.</figcaption>
</figure>

<p>The video above should give you a good overview of what it does, how it works and what its limitations are. This article here goes more into the technical details of how it works. If you are interested in how to order and build your own GB Interceptor, check out <a href="https://github.com/Staacks/gbinterceptor" target="_blank">github</a> and the <a href="https://youtu.be/Lg92tVkEE98" target="_blank">order and build video</a>.</p>

<!--more-->

<h2 id="why-do-we-need-this">Why do we need this?</h2>

<p>The best way to explain why<sup id="fnref:why" role="doc-noteref"><a href="#fn:why" class="footnote" rel="footnote">1</a></sup> I developed and built the GB Interceptor is to explain which problem I tried to solve with it. A few months ago a Tetris enthusiast got in touch with me about this problem: An online Tetris tournament during which the contestants stream their gameplay.</p>

<p>Today, there is nothing unusual about streaming footage from a Game Boy. Emulators can easily do it and modern Game Boy variants like the Analogue Pocket offer HDMI output that could be captured. There also are some mods to add HDMI out to original Game Boy hardware, so getting a video stream from a Game Boy is a challenge that has long been solved.</p>

<p>The unusual detail about doing it for a Tetris tournament is that the players have to rely on their muscle memory which they trained on their personal Game Boys. Switching them for an unfamiliar modern device or an emulator will significantly impede their ability to play competitively. Also, you can imagine that a tournament that asks each contestant to first mod the hell out of their beloved Game Boys just to stream a video would not be well received.</p>

<p>So, we need a way to get a video from unmodified Game Boys also without modifying the game that is being played. Ideally in a form that can be used by anyone without complicated software or additional hardware like an HDMI grabber.</p>

<h2 id="basic-concept-of-how-it-works">Basic concept of how it works</h2>

<p>Well, in the end, the only connector with game data that is accessible on a Game Boy without a mod is the cartridge slot<sup id="fnref:linkcable" role="doc-noteref"><a href="#fn:linkcable" class="footnote" rel="footnote">2</a></sup>. After all, the entire game data has to go through there. So, the idea is to create an adapter that connects the cartridge to the Game Boy directly and only adds the capability to intercept a copy of the transferred data.</p>

<figure>

<img src="/assets/resized/images/2022-12-20/1280/tetris.jpg" style="max-width: min(3840px, 100%)" alt="Photo of a classical Game Boy with the GB Interceptor sticking out from the top and a Tetris cartridge inserted. The Interceptor is connected to a laptop, showing the same scene of Tetris as the Game Boy's screen." srcset="    /assets/resized/images/2022-12-20/640/tetris.jpg 640w,    /assets/resized/images/2022-12-20/768/tetris.jpg 768w,    /assets/resized/images/2022-12-20/1024/tetris.jpg 1024w,    /assets/resized/images/2022-12-20/1280/tetris.jpg 1280w, /assets/images/2022-12-20/tetris.jpg 3840w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>The GB Interceptor connected to a laptop, which shows its video stream in VLC.</figcaption>
</figure>

<p>However, this means that we cannot randomly access data of interest and we cannot see the data in RAM that the Game Boy’s CPU put together from the raw instructions from the cartridge. Especially, we cannot see the Video RAM, which would have been very nice as it would contain everything<sup id="fnref:oam" role="doc-noteref"><a href="#fn:oam" class="footnote" rel="footnote">3</a></sup> required to draw the image on screen. Instead, we need to create our own copy of VRAM.</p>

<p>To do so, I had to write an emulator to which I feed the data from the cartridge memory bus. For this I use an rp2040 (the Raspberry Pi Pico’s microcontroller) and split its cores to the two main processing parts of the Game Boy. One core emulates the CPU to recreate a copy of VRAM and the other core emulates the Game Boy’s graphics unit, the PPU<sup id="fnref:ppu" role="doc-noteref"><a href="#fn:ppu" class="footnote" rel="footnote">4</a></sup>.</p>

<p>The CPU emulation actually is the trickiest part here, because it has to keep up with the memory bus that is pushing out events at a rate of about 1 MHz. If the PPU emulation falls behind, it would cause a short glitch like a flicker, but if the CPU emulation falls behind, it will eventually miss an event on the memory bus. Not only would the simulated copy of the RAM possibly get out of synch forever, but the emulator would not even be able to interpret the following instructions. An event on the bus is not always the next instruction, because the Game Boy’s CPU may take several cycles to execute some instructions while others are completed within a single cycle. So, the emulator has to keep track of how many cycles have to be ignored after a specific instruction before an event should be considered to be an instruction again. If we miss only one of them it becomes nearly impossible to get this right again.</p>

<p>This together with the overhead of emulating an 8-bit CPU on a 32-bit CPU made it necessary to overclock the rp2040 from its default 125 MHz<sup id="fnref:defaultclock" role="doc-noteref"><a href="#fn:defaultclock" class="footnote" rel="footnote">5</a></sup> to 225 MHz. The rp2040 can usually handle this without any problems, but still I would love to see if someone can improve the efficiency of my code to dial this back a bit.</p>

<p>Since the PPU emulation is not that critical and actually periodically gets some free time during the Game Boy’s vblank period when no image is being drawn, it also handles USB communication.</p>

<h2 id="the-hardware">The hardware</h2>

<p>The actual hardware to implement this is pretty much a Raspberry Pi Pico with some bus transceivers to connect its GPIO ports to the cartridge bus. From the 32 pins of this bus, two are used for +5V and ground, one is used for analog audio<sup id="fnref:audiopin" role="doc-noteref"><a href="#fn:audiopin" class="footnote" rel="footnote">6</a></sup> and one is used to control the reset state of the Game Boy. The other 28 pins are connected to the rp2040, which therefore gets access to 16 address pins, 8 data pins and the four bus control pins clock, read, write and chip select. Since these use 5V logic I use the same bus transceivers that already served me well in the <a href="/a/wifi-game-boy-cartridge">WiFi Game Boy cartridge</a> to convert the signals to 3.3V for the rp2040.</p>

<p>This leaves two GPIOs unused. One observes the voltage on the +5V line to check if the Game Boy is turned on or not and the other one controls a status LED and reads a mode button.</p>

<figure>

<img src="/assets/resized/images/2022-12-20/1280/pcb.jpg" style="max-width: min(3000px, 100%)" alt="Photo of the purple PCB for the GB Interceptor with brak-out board and rails." srcset="    /assets/resized/images/2022-12-20/640/pcb.jpg 640w,    /assets/resized/images/2022-12-20/768/pcb.jpg 768w,    /assets/resized/images/2022-12-20/1024/pcb.jpg 1024w,    /assets/resized/images/2022-12-20/1280/pcb.jpg 1280w, /assets/images/2022-12-20/pcb.jpg 3000w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>The GB Interceptor's PCB with rails and a break-out board that have to be removed using it.</figcaption>
</figure>

<p>The rest of the cartridge is based on the <a href="https://datasheets.raspberrypi.com/rp2040/hardware-design-with-rp2040.pdf" target="_blank">minimal hardware design example for the rp2040</a> by the Raspberry Pi Foundation. This includes an oscillator, flash memory, a voltage converter and a USB port, which I replaced by a Type C variant.</p>

<p>That’s pretty much it. A Raspberry Pi Pico in Game Boy cartridge format hooked up to the Game Boy’s memory bus. The schematics and PCB designs can of course be found in the project’s <a href="https://github.com/Staacks/gbinterceptor/tree/main/pcb" target="_blank">github repository</a>.</p>

<h2 id="implementation">Implementation</h2>

<p>What really let’s the GB Interceptor do what it does is its software, which can of course also be found on <a href="https://github.com/Staacks/gbinterceptor/tree/main/firmware" target="_blank">github</a>. In the following I will write about some of its details.</p>

<h3 id="usb-video-class">USB video class</h3>

<p>The GB Interceptor streams the resulting image using the USB video class implementation of <a href="https://github.com/hathach/tinyusb/" target="_blank">TinyUSB</a>, so in theory no drivers are needed and it should just show up as a webcam. Well, in theory. Unfortunately, this only works as expected on Linux, where I can directly use the GB Interceptor in VLC, OBS, Zoom or ffmpeg. On Windows and Android many apps seem to have trouble with the format of the video stream. On Windows, for example, VLC (despite working on Linux) complains that no suitable format could be found while OBS works perfectly fine without any settings or drivers required. On Windows this is good news because you can use OBS as a virtual webcam to forward the GB Interceptor stream to any software that is picky about the format. A <a href="https://github.com/Staacks/gbinterceptor/wiki/Host-software-compatibility" target="_blank">list of tested host software</a> can be found on github.</p>

<p>Unfortunately, at the time of this writing I was not able to get any video on MacOS and I am not yet sure why. For some reason it does not even trigger TinyUSB to enable the video stream, so I am not entirely convinced that it is the format. Keeping in mind that I have not yet done many tests on MacOS and that the video class implementation in TinyUSB is very recent and experimental, I hope that I can fix this in the future. Even if I could not get the video class to work here, it should be possible to pump the images through UART on the USB bus and use a simple Python script to convert it to a video stream on the system. You can check the current state of <a href="https://github.com/Staacks/gbinterceptor/issues/1" target="_blank">this issue on github</a>.</p>

<p>So, what is that unusual format? Well, obviously, this starts with the Game Boy’s resolution of 160x144 pixels, which I can imagine might surprise some software expecting a modern 1080p stream. But it gets a bit more complicated when we look at the limitations that arise from the rp2040’s Full Speed USB port and its implications for the isochronous transfer implemented by TinyUSB. This combination means that the maximum buffer size for this endpoint is 1023 bytes and since isochronous transfer happens every 1 ms, we get 1,023,000 bytes per second.</p>

<p>If we just look at the raw image from the Game Boy, this is more than enough. The Game Boy has a “color depth” of 2 bit, so one image frame is 5760 byte. With roughly 60 frames per second we only require 345,600 bytes, which is why I see a custom UART protocol as an interesting alternative on MacOS if all else fails.</p>

<p>However, we do not want to need a driver or additional software. We want something that just works and unfortunately there is no 2bit color format that is widely accepted. Instead, there are plenty of compressed formats for which we do not have enough computation power left<sup id="fnref:mjpeg" role="doc-noteref"><a href="#fn:mjpeg" class="footnote" rel="footnote">7</a></sup> and some uncompressed color formats that are considered to be widely supported, most of which use 16bit per pixels. Instead we use a supposedly also widely supported slightly more efficient format: NV12 with 12 bit per pixel. The 12 bit are comprised of 8 bit per pixel for luma (grayscale brightness) and 16 bit shared by four pixels (hence 4 more bit per pixel) for the color information.</p>

<p>The good news is that the color data of the entire frame is stored at the end, so we can set it to gray or green once and can ignore it. In fact, we can treat the data before as a simple 160x144 pixel buffer with 8bit grayscale data, which is more or less ideal for our purpose.</p>

<p>The bad news, of course, is that it still takes up 6 times as much data as the original 2bit image would have needed. With our 1,023,000 bytes per second we are now limited to 29fps.</p>

<p>So, overall we have a 29fps NV12 stream at a resolution of 160x144. Not exactly what all those video conference tools expect.</p>

<p>By the way, although the GB Interceptor therefore only pushes out 29fps, it still works internally with 60fps and blends these frames to emulate the latency of the old LCDs. It just pushes out the latest blended frame whenever the USB bus calls for it.</p>

<h3 id="programmable-ios">Programmable IOs</h3>

<p>Now after I explained how to get the result out from the GB Interceptor, let’s talk about the other end: How to get the communication on the cartridge bus to the rp2040.</p>

<p>Remember how much I struggled with my <a href="/a/wifi-game-boy-cartridge">WiFi Game Boy cartridge</a> when I tried to listen to a single event with an ESP8266? Interrupts were too slow<sup id="fnref:arduino" role="doc-noteref"><a href="#fn:arduino" class="footnote" rel="footnote">8</a></sup> and keeping the CPU in a tight loop observing the clock line was not an option. Well, the rp2040 has a trick up its sleeve: Programmable IOs. These are simple state machines that can directly access the GPIO pins as well as a FIFO buffer to/from the CPU. And these PIOs simply laugh at this task.</p>

<p>All we need to do is wait for the clock line to become low and then simultaneously read the remaining 27 GPIO pins that are connected to the Game Boy’s memory bus and write the result to the FIFO. For this we only need a single PIO and that executes only four instructions:</p>

<figure class="highlight"><pre><code class="language-asm" data-lang="asm"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="code"><pre>    wait 1 pin 28 ;Wait for CLK to go high
    wait 0 pin 28 ;Wait for falling flank of CLK
    mov isr pins  ;Read all GPIO pins to the input shift register
    push          ;Push the ISR to the FIFO
</pre></td></tr></tbody></table></code></pre></figure>

<p>From there, the CPU can just pick up one of these events packed into a single 32 bit integer from the FIFO whenever it is convenient.</p>

<h2 id="the-emulator-part">The emulator part</h2>

<p>Now it is time to talk about what these events look like. Or rather, how we need to deal with them. At this point I expect that you have a basic idea of how the Game Boy works. For those who are not familiar with Game Boy development I always recommend Michael Steil’s <a href="https://www.youtube.com/watch?v=HyzD8pNlpwI" target="_blank">“Ultimate Game Boy Talk”</a>.</p>

<p>As explained above, the basic idea is that one core of the rp2040 interprets the incoming bus events such that it follows the same instructions as the Game Boy’s CPU. That is, it emulates the Game Boy CPU in order to recreate an exact copy of VRAM (and OAM). The second core then acts as the PPU and renders an image from our VRAM copy. This is mostly just the implementation of a basic Game Boy emulator, but there are some differences that I would like to talk (or write) about.</p>

<h3 id="conditional-jumps-and-io">Conditional jumps and IO</h3>

<p>First of all, there are several things that become much simpler in this scenario. Think about the program counter and conditional jumps. We do not have to implement those. The real Game Boy fetches the next instruction anyways. It does not matter if it is the next instruction by incrementing the PC or if it jumps to an entirely different address. The real Game Boy will fetch the next instruction and we do not have to care about where the instruction came from.</p>

<p>This solves one of the seemingly biggest issues: We cannot see any of the hardware I/O registers. In particular, we do not see the input from the game pad! How should we ever emulate a game if we cannot see the player’s input? Well, almost every code in existence will compare the gamepad input to check which button was pressed and make a conditional jump to code that is triggered by the button. Our emulator will simply follow these same instructions and does not have to care whether it was triggered by a button press.</p>

<p>You could say that the GB Interceptor is an emulator on rails.<sup id="fnref:press" role="doc-noteref"><a href="#fn:press" class="footnote" rel="footnote">9</a></sup></p>

<p>This only becomes a problem if the data from the I/O registers eventually ends up in VRAM. Imagine that the value of the gamepad is added to a base address to calculate the tile index to an image that shows the current state of the D-Pad. The CPU would get the instruction to fetch the gamepad register value, add a number to it and our emulator would not know the correct result of that operation. This result is then written to VRAM and we have no idea what is in that location.</p>

<p>However, these should only amount for small visual differences. I do not know of any example where this is done with the gamepad I/O, but I have an example for the DIV register. In Tetris it is used as a source for random numbers and most of the time it branches the code through conditional jumps to pick different blocks that come next or to generate the initial pile of garbage blocks in game mode B. We do not see that random number, but when it triggers the code to logically pick the upcoming block, we will still get the same block as we get to execute the same code. This also goes for the decision whether a block of the garbage pile in mode B is empty or filled, so we also get the same layout for the garbage stack. But those garbage blocks also have a randomized visual style and that is not based on branching code, but just a random number added to a base tile index.</p>

<p>The result is that we see the same garbage stack layout on the GB Interceptor, but the individual blocks have a different look. This is harmless and you would only ever notice if you compare the image to the Game Boys screen.</p>

<figure>

<img src="/assets/resized/images/2022-12-20/1280/tetris-stack.jpg" style="max-width: min(1612px, 50%)" alt="Comparison of two photos. On the left, there is a photo of the Game Boy screen and on the right there is a photo of the video stream from the GB Interceptor. Both show the same scene and position of Tetris blocks, but the individual blocks have different designs." srcset="    /assets/resized/images/2022-12-20/640/tetris-stack.jpg 640w,    /assets/resized/images/2022-12-20/768/tetris-stack.jpg 768w,    /assets/resized/images/2022-12-20/1024/tetris-stack.jpg 1024w,    /assets/resized/images/2022-12-20/1280/tetris-stack.jpg 1280w, /assets/images/2022-12-20/tetris-stack.jpg 1612w" sizes="(max-width: 60rem) calc(50/100 * 100vw), calc(50/100 * 60rem)" />

<figcaption>Left: Photo of original Game Boy screen in Tetris mode B. Right: Same scene as rendered by the GB Interceptor. The layout of the garbage stack is identical, but the individual blocks have different designs.</figcaption>
</figure>

<p>We only get into real trouble when an entire stream of prepared data is written to VRAM from one of the I/O registers. The only example that I know of (and that I can think of) is the link cable. Here, we can look at the same example of the mode B garbage stack, but in two player mode of Tetris. The problem is that both players should have the same garbage stack. So, the Game Boy that starts the game first will generate that stack and send it to the second one via link cable. The second one writes the data directly to VRAM without any checks or conditional jumps and we cannot see anything.</p>

<figure>

<img src="/assets/resized/images/2022-12-20/1280/tetris_2player.jpg" style="max-width: min(3347px, 100%)" alt="Collage of photos of a Game Boy Color and the correspondig laptop screen showing the video stream from the Interceptor in 2 player mode. On the left, the laptop images mostly matches the Game Boy except for showing different designs for individual blocks. On the right, the entire garbage stack has been replaced with the zeros." srcset="    /assets/resized/images/2022-12-20/640/tetris_2player.jpg 640w,    /assets/resized/images/2022-12-20/768/tetris_2player.jpg 768w,    /assets/resized/images/2022-12-20/1024/tetris_2player.jpg 1024w,    /assets/resized/images/2022-12-20/1280/tetris_2player.jpg 1280w, /assets/images/2022-12-20/tetris_2player.jpg 3347w" sizes="(max-width: 60rem) 100vw, 60rem" />

<figcaption>Tetris in two player mode. Left: The Game Boy Color with the GB Interceptor started first and the garbage stack is rendered like in 1 player mode. Right: The other Game Boy started first and we cannot see the garbage stack as it has been received via link cable.</figcaption>
</figure>

<p>Therefore, in two player Tetris, the GB Interceptor works fine if it is in the Game Boy that starts the game first (except for the different visual style of individual blocks), but it produces unusable output if it is in the second Game Boy.</p>

<h3 id="clocks-the-div-register-and-the-halt-instruction">Clocks, the DIV register and the halt instruction</h3>

<p>Speaking of the DIV register, this is actually an I/O register that we might be able to emulate. Since we get the exact clock from the Game Boy we can count an emulated register in synch with the real one without any danger to diverge. There are only two problems:</p>

<ol>
  <li>The initial value is unknown - at least to me. When the code from the cartridge is executed, the state of the DIV register depends on the Game Boy model and in some cases it also depends on the user interaction during the boot sequence of that model. For example, if you change the color mode of the Game Boy Color during the boot sequence, the DIV register will have a different value at the beginning. I am not sure if the Interceptor sees enough action on the bus during the boot sequence to compensate for that, but I also would not rule this out entirely.</li>
  <li>We lose the reference clock when the Game Boy enters the halt state, which for most games happens at least once per frame. Here, the clock of the rp2040 has to take over precisely, which should be possible if we had a little bit more computational headroom. (i.e. if someone can optimize my code)</li>
</ol>

<p>The thing is that we actually measure how many rp2040 clock cycles occur for each Game Boy clock cycle during the boot sequence before the actual game starts. Here we can observe thousands of cycles and should be able to get a very precise substitute clock from our rp2040. Unfortunately, for performance reasons, I only use an integer ratio of both clocks, which typically is in the order of 225 rp2040 clocks per Game Boy clock. This means that just the rounding error will lead to an error of roughtly one cycle per 100 cycles during a halt state, which happens regularly.</p>

<p>So, maybe we can do a fractional clock count, but for now as it only affects the div register which I cannot properly initialize anyway, this is not implemented.</p>

<h3 id="synching-cpu-and-ppu">Synching CPU and PPU</h3>

<p>While we are on the subject of synchronizing our emulator to the real Game Boy… We of course also need to synchronize our PPU to the one of the real Game Boy. Otherwise any effect that requires changes in the VRAM midframe would lead to glitches and at least we would see some tearing effects as data is updated in VRAM randomly.</p>

<p>The problem is, that no trace of the PPU can be found on the memory bus. We have to deduce the state of the PPU by the behavior of the game, which has to synch to the PPU as well - at least to know when it may write to VRAM. The big problem here is that games can use many different ways to do so.</p>

<p>The most common method, is the vsync interrupt. Most games simply let the Game Boy trigger an interrupt when vsync is reached and we can see when the code of this interrupt is being executed, so we can simply adjust the timing of our own emulated PPU to enter vsync at that same moment.</p>

<p>Unfortunately, there are many other options to do this. Another common one for games that need to squeeze out a bit more access to VRAM (for example implemented in Donkey Kong Land) is to read the LY register in a tight loop and to periodically compare it to a specific line number. A conditional jump jumps back to the LY readout until the correct line is reached and the code simply goes beyond the conditional jump. Luckily, the developer can save a few cycles by jumping while it is not reached, so many games do it this way, which allows for a simple and naive detection of these tight loops in the Interceptor.</p>

<p>However, there will be games out there with a different approach (like my Wifi cartridge) and the output of the GB Interceptor will look glitchy until a detection for these other methods has been implemented.</p>

<h3 id="detecting-interrupts">Detecting interrupts</h3>

<p>Oh, and while interrupts are a blessing to synch the PPU, these are not exactly easy to detect in the first place. We need to keep track of every single instruction and how many cycle the Game Boy needs for each instruction to be sure which event on the memory bus would be the next instruction. The Game Boy jumping to a different point in the execution and taking a few extra cycles to do so is not exactly helpful here.</p>

<p>Have a look at the first vsync interrupt of “The Legend of Zelda - Link’s Awakening”<sup id="fnref:zelda" role="doc-noteref"><a href="#fn:zelda" class="footnote" rel="footnote">10</a></sup> on the original Game Boy:</p>

<figure class="highlight"><pre><code class="language-raw" data-lang="raw"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="code"><pre>  Address Data Instruction
   01a2    fb    EI 
   01a3    c3    JP a16    
   01a4    bd    
   01a5    03    
   81a5    71    
   03bd    3e    IRQ    
   82bd    01    
   82bd    01    
   dffe    81    
   dffd    a5 
   0040    c3    JP a16    
   0041    25    
   0042    05    
   8042    24    
</pre></td></tr></tbody></table></code></pre></figure>

<p>When ignoring interrupts, we would falsely interpret 0x3e in line 7 as an opcode. The only way to be more or less sure that we are seeing an interrupt is by implementing the GB Interceptor such that it reads ahead<sup id="fnref:ahead" role="doc-noteref"><a href="#fn:ahead" class="footnote" rel="footnote">11</a></sup> a few cycles to recognize an interrupt before the current event is misinterpreted as an instruction when in truth it is just garbage on the memory bus while the CPU takes a moment to enter the interrupt.</p>

<p>Luckily, the Game Boy jumps to few fixed addresses during an interrupt, so we look out for those. But since these addresses could theoretically also be called from regular code, we mix in a few more indicators, specifically the behavior of the stack pointer. During an interrupt call, the current PC is pushed onto the stack, so the SP register is decremented twice and the Game Boy writes to the two decremented addresses. Usually these do not point to an address belonging to the cartridge, but these addresses are still visible on the memory bus, so this adds to our confidence to detect an interrupt.</p>

<p>The only trouble with this is that the Game Boy is not really required to do this consistently and to show the SP address on the memory bus as no game cartridge cares about these operations. Therefore, it is not surprising that we can see a few differences here between different devices. Here is only the interrupt call for the original Game Boy (DMG), the Game Boy Color and the Analogue Pocket:</p>

<figure class="highlight"><pre><code class="language-raw" data-lang="raw"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="code"><pre>      DMG                 GBC                Pocket
  Address Data        Address Data        Address Data
   03bd    3e          03bd    3e          03bd    3e
   82bd    01          83be    00          dfff    00
   82bd    01          dfff    00          dffe    01
   dffe    81          dffe    80          dffd    9b
   dffd    a5          dffd    00          0040    c3
   0040    c3          0040    c3          0040    c3    &lt;   Next instruction
</pre></td></tr></tbody></table></code></pre></figure>

<p>If we look at this closely, we find some slight differences: The DMG also shows the SP address before decrementing it, the GBC only shows the two decremented addresses it actually writes to and the Pocket does this one cycle earlier. Taking all these cases into account, of course makes our interrupt detection less reliable and at the moment it does not work properly with the variant of the Pocket.</p>

<h2 id="schematics-and-build-instructions">Schematics and build instructions</h2>

<p>I think these are the most interesting parts of the implementation. Kudos to you, if you have read up to this point - you are a true 8bit geek!</p>

<p>If you want to see even more details, you now have to dive into the code on <a href="https://github.com/Staacks/gbinterceptor" target="_blank">github</a> where you can also find the hardware design files and material for cases. I hope that there will be some community contributions to both, the code and hardware design, so if a few months have passed since the publication of this article, this is also something that will mostly take place on github.</p>

<p>If you want to build your own GB Interceptor, you should also watch the <a href="https://youtu.be/Lg92tVkEE98" target="_blank">order and build video</a>.</p>

<p>I hope you enjoyed this project!</p>

<h2 id="acknowledgements">Acknowledgements</h2>

<p>This project would not exist without the work by many people who researched, tested and prodded the Game Boy before me and (most importantly and why I am writing these articles myself) documented their work. Here are some of my most important resources:</p>
<ul>
  <li><a href="https://gbdev.io/" target="_blank">gbdev.io</a> and especially its Pan Docs was my prime source for everything about how the Game Boy works.</li>
  <li>Many hardware details and some intricate specifics can be found on <a href="https://gekkio.fi/" target="_blank">Joonas Javanainen’s webpage</a>.</li>
  <li>While there are many site with opcode tables for the Game Boy, I found the one by <a href="https://meganesulli.com/blog/game-boy-opcodes/" target="_blank">Megan Sullivan</a> to be the most convenient one, which is why I had it open pretty much all the time.</li>
</ul>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:why" role="doc-endnote">
      <p>To be honest, my personal motivation was that I immensely enjoyed the challenge. The Game Boy just has the right amount of complexity to pose a challenge while still being simple enough to allow for an understanding of the whole system. <a href="#fnref:why" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:linkcable" role="doc-endnote">
      <p>One could argue that the link cable offers some data. But its bandwidth on a classical Game Boy is poor and it only provides some handpicked data if the game was programmed to send some data via link cable. Since we don’t want to modify the games, there is not much of interest on that port. <a href="#fnref:linkcable" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:oam" role="doc-endnote">
      <p>Ok, you also need the OAM, but the concept is the same. <a href="#fnref:oam" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:ppu" role="doc-endnote">
      <p>Pixel processing unit or picture processing unit - depending on whom you ask. <a href="#fnref:ppu" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:defaultclock" role="doc-endnote">
      <p>Some sources will say 133 MHz, for which the rp2040 is rated. I am referring to the reference implementation of the Raspberry Pi Pico and the Raspberry Pi Foundation’s minimal hardware example, which run at 125 MHz. <a href="#fnref:defaultclock" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:audiopin" role="doc-endnote">
      <p>The GB Interceptor does not implement audio at all, which can easily be captured from the headphone jack. <a href="#fnref:audiopin" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:mjpeg" role="doc-endnote">
      <p>I tried an mjpeg compression, but my implementation was way too slow to generate the frames just during the spare time during vblank. Maybe someone with more optimization experience or some additional hardware can help here in the future. <a href="#fnref:mjpeg" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:arduino" role="doc-endnote">
      <p>To be fair, those were Arduino interrupts with their typical overhead. <a href="#fnref:arduino" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:press" role="doc-endnote">
      <p>Yes, I use that phrase a lot. I like it. Wondering if the media will pick it up :) <a href="#fnref:press" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:zelda" role="doc-endnote">
      <p>Opps, I mislabeled this as “A Link to the Past” in the video twice, didn’t I? <a href="#fnref:zelda" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:ahead" role="doc-endnote">
      <p>Ahead is a question of perspective. Ahead from the point of view of the instruction that is currently emulated. Of course this means that the Interceptor lags behind a few cycles compared to the real Game Boy - but we are talking about microseconds here. <a href="#fnref:ahead" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[I present to you: The GB Interceptor. It is an adapter that goes between an unmodified Game Boy and the cartridge and offers a video stream of the game via USB. Click the image to see the video on youtube.com. The video above should give you a good overview of what it does, how it works and what its limitations are. This article here goes more into the technical details of how it works. If you are interested in how to order and build your own GB Interceptor, check out github and the order and build video.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://there.oughta.be/assets/images/2022-12-20/youtube.jpg" /><media:content medium="image" url="https://there.oughta.be/assets/images/2022-12-20/youtube.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">There oughta be a Live Q&amp;amp;A about the WiFi Game Boy Cartridge.</title><link href="https://there.oughta.be/a/live-q-and-a/about-the-wifi-game-boy-cartridge" rel="alternate" type="text/html" title="There oughta be a Live Q&amp;amp;A about the WiFi Game Boy Cartridge." /><published>2022-01-28T00:00:00+01:00</published><updated>2022-01-28T00:00:00+01:00</updated><id>https://there.oughta.be/a/live-q-and-a/live-q-and-a-wifi-game-boy-cartridge</id><content type="html" xml:base="https://there.oughta.be/a/live-q-and-a/about-the-wifi-game-boy-cartridge"><![CDATA[<p>There will be a Live Q&amp;A session about the Game Boy WiFi cartridge on Friday 9pm (CET). Ask anything!</p>

<figure class="youtube">
<a href="https://youtu.be/yCe_vuNdJOE" target="_blank">
<img src="/assets/resized/images/2022-01-28/1280/youtube.jpg" style="max-width: min(1920px, 100%)" alt="Thumbnail of the youtube video: A photo of a Game Boy Color, an original Game Boy and an Analogue Pocket with a picture of me showing on the Game Boy Color." srcset="    /assets/resized/images/2022-01-28/640/youtube.jpg 640w,    /assets/resized/images/2022-01-28/768/youtube.jpg 768w,    /assets/resized/images/2022-01-28/1024/youtube.jpg 1024w,    /assets/resized/images/2022-01-28/1280/youtube.jpg 1280w, /assets/images/2022-01-28/youtube.jpg 1920w" sizes="(max-width: 60rem) 100vw, 60rem" />
</a>
<figcaption>Click the image to see the video on youtube.com.</figcaption>
</figure>

<p>I have never done this before and am quite curious on how it will turn out and how many will attend. See you on Friday!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[There will be a Live Q&amp;A session about the Game Boy WiFi cartridge on Friday 9pm (CET). Ask anything! Click the image to see the video on youtube.com. I have never done this before and am quite curious on how it will turn out and how many will attend. See you on Friday!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://there.oughta.be/assets/images/2022-01-28/youtube.jpg" /><media:content medium="image" url="https://there.oughta.be/assets/images/2022-01-28/youtube.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>