<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Oddbit Retro]]></title><description><![CDATA[Our adventures into retro.]]></description><link>https://www.oddbit-retro.org/</link><image><url>https://www.oddbit-retro.org/favicon.png</url><title>Oddbit Retro</title><link>https://www.oddbit-retro.org/</link></image><generator>Ghost 5.32</generator><lastBuildDate>Sun, 03 May 2026 10:24:50 GMT</lastBuildDate><atom:link href="https://www.oddbit-retro.org/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Recreating the Iskra Delta Partner Motherboard]]></title><description><![CDATA[<p>In 2023, I completed a project that, at the start, seemed unlikely to succeed. I built an Iskra Delta Partner motherboard from scratch. I did not repair an old one. I recreated the entire thing, beginning with a damaged and unusable board and ending with a working machine.</p><p>This was</p>]]></description><link>https://www.oddbit-retro.org/recreating-the-iskra-delta-partner-motherboard/</link><guid isPermaLink="false">68b31432e9fee00001887245</guid><dc:creator><![CDATA[Miha Grčar]]></dc:creator><pubDate>Sat, 30 Aug 2025 15:12:52 GMT</pubDate><content:encoded><![CDATA[<p>In 2023, I completed a project that, at the start, seemed unlikely to succeed. I built an Iskra Delta Partner motherboard from scratch. I did not repair an old one. I recreated the entire thing, beginning with a damaged and unusable board and ending with a working machine.</p><p>This was not an engineering exercise. I have no real background in electronics. What I had was a broken board, enough tools, and a process that I believed could work.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2025/08/IMG_4496.JPG" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2025/08/IMG_4496.JPG 600w, https://www.oddbit-retro.org/content/images/size/w1000/2025/08/IMG_4496.JPG 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2025/08/IMG_4496.JPG 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2025/08/IMG_4496.JPG 2400w" sizes="(min-width: 720px) 720px"><figcaption>Original Iskra Delta Partner board. The full motherboard before any work began. While it may look mostly intact at first glance, the board is heavily oxidized.</figcaption></figure><h2 id="step-1-strip-the-board">Step 1: Strip the Board</h2><p>The original board was badly oxidized. I suspect this was the result of both battery leakage and moisture exposure. The damage was severe, and although I cannot say for certain that none of the components were usable, that felt like a fair assumption.</p><p>Desoldering oxidized joints turned out to be more difficult than I expected. The oxide layer blocked heat, so the solder would not melt. After trying several approaches, I found the most reliable method was to cut the joint from underneath, exposing clean solder. Once that was exposed, I could melt it and use a desoldering gun to remove the component. It was a slow process, and it took patience.</p><p>In the end, I was left with a bare board. The components were gone, but the layout was still visible.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2025/08/IMG_7573.jpg" class="kg-image" alt loading="lazy" width="2000" height="2667" srcset="https://www.oddbit-retro.org/content/images/size/w600/2025/08/IMG_7573.jpg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2025/08/IMG_7573.jpg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2025/08/IMG_7573.jpg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2025/08/IMG_7573.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Battery damage close-up. A detailed view of the corrosion beneath the battery area. Likely caused by a combination of battery leakage and moisture over time.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2025/08/ima_82ad995_2.jpg" class="kg-image" alt loading="lazy" width="2000" height="2661" srcset="https://www.oddbit-retro.org/content/images/size/w600/2025/08/ima_82ad995_2.jpg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2025/08/ima_82ad995_2.jpg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2025/08/ima_82ad995_2.jpg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2025/08/ima_82ad995_2.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Mask removed from beneath the battery. With the solder mask stripped away under the battery area, the extent of the damage becomes clearly visible. Several traces show corrosion and material loss.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2025/08/ima_dee8c3c.jpeg" class="kg-image" alt loading="lazy" width="2000" height="2667" srcset="https://www.oddbit-retro.org/content/images/size/w600/2025/08/ima_dee8c3c.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2025/08/ima_dee8c3c.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2025/08/ima_dee8c3c.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2025/08/ima_dee8c3c.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Desoldering in progress. The board with several components already removed. This photo captures an intermediate stage in the slow and careful desoldering process.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2025/08/ima_be9c653.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2025/08/ima_be9c653.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2025/08/ima_be9c653.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2025/08/ima_be9c653.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2025/08/ima_be9c653.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Box of desoldered components. Components collected after desoldering the original board. These were not intended for reuse but were kept as part of the disassembly process.</figcaption></figure><h2 id="step-2-scan-the-board">Step 2: Scan the Board</h2><p>Fortunately, the Partner board has only two layers. I scanned both sides at high resolution. The scans gave me a clear reference for every trace, pad, and via. That was enough to begin recreating the design.</p><h2 id="step-3-redraw-in-kicad">Step 3: Redraw in KiCad</h2><p>I used KiCad to build a new version of the board. To identify and place the vias, I created a tool specifically for this task. It used older AI technologies such as vector spaces and similarity measures. I have written about that process <a href="https://www.oddbit-retro.org/how-ai-helped-us-recreate-the-idp-motherboard/">elsewhere</a>.</p><p>Once the vias were in place, I manually added all the components and drew every trace on both layers. It was time-consuming but straightforward. I was not working from a schematic or simulation. I was just copying what I saw in the scan.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2025/08/vezje-final.png" class="kg-image" alt loading="lazy" width="2000" height="1072" srcset="https://www.oddbit-retro.org/content/images/size/w600/2025/08/vezje-final.png 600w, https://www.oddbit-retro.org/content/images/size/w1000/2025/08/vezje-final.png 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2025/08/vezje-final.png 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2025/08/vezje-final.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>KiCad PCB layout, top Layer. The top copper layer in KiCad, showing the full routing of traces as manually redrawn from the original board scans.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2025/08/vezje-final-3d.png" class="kg-image" alt loading="lazy" width="2000" height="1073" srcset="https://www.oddbit-retro.org/content/images/size/w600/2025/08/vezje-final-3d.png 600w, https://www.oddbit-retro.org/content/images/size/w1000/2025/08/vezje-final-3d.png 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2025/08/vezje-final-3d.png 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2025/08/vezje-final-3d.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>KiCad PCB layout, 3D front view. A rendered 3D view of the recreated board in KiCad, showing the front side with component placements and silkscreen labels.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2025/08/logo-vektorizacija.png" class="kg-image" alt loading="lazy" width="2000" height="1071" srcset="https://www.oddbit-retro.org/content/images/size/w600/2025/08/logo-vektorizacija.png 600w, https://www.oddbit-retro.org/content/images/size/w1000/2025/08/logo-vektorizacija.png 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2025/08/logo-vektorizacija.png 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2025/08/logo-vektorizacija.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Vectorized board logo in Inkscape. The Delta logo traced and vectorized in Inkscape, prepared for import into KiCad to accurately reproduce the original board markings.</figcaption></figure><h2 id="step-4-validate-the-layout">Step 4: Validate the Layout</h2><p>To make sure the KiCad layout matched the original design, I needed a second source of information. I had access to the original schematics, but they were not entirely reliable. Some were poorly scanned, and others contained mistakes. I could not depend on them completely.</p><p>Still, they were useful for cross-checking. My friend Sa&#x161;o and I transcribed all the network connections from the schematics into a text file. I also wrote a Python program to extract the network data from the KiCad layout using a flood fill algorithm.</p><p>I then compared the two sets of data. Whenever there was a mismatch, it was usually obvious whether the problem was in the schematic or in my layout. I fixed the layout or adjusted the network list until both matched exactly.</p><p>That process took longer than expected, but once it was complete, I was confident that the recreated layout was accurate.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2025/08/sch_annot_Page_02_Image_0001.jpg" class="kg-image" alt loading="lazy" width="2000" height="1499" srcset="https://www.oddbit-retro.org/content/images/size/w600/2025/08/sch_annot_Page_02_Image_0001.jpg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2025/08/sch_annot_Page_02_Image_0001.jpg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2025/08/sch_annot_Page_02_Image_0001.jpg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2025/08/sch_annot_Page_02_Image_0001.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Annotated schematic sheet. One of the original schematic pages used during validation. Several connection errors have been marked and corrected during the cross-checking process with the recreated layout.</figcaption></figure><h2 id="step-5-manufacture-the-board">Step 5: Manufacture the Board</h2><p>With validation complete, I exported the board as Gerber files and sent them to PCBWay.</p><p>The boards came back looking perfect. The fabrication quality was excellent. It was the first time in the project where I felt like something solid had been created.</p><h2 id="step-6-assembly">Step 6: Assembly</h2><p>I assembled the board using components from several sources. Most came from Mouser. A few came from stores here in Slovenia. Some I bought on eBay. Others were donated by members of the local retro computing community.</p><p>The process of soldering everything into place was uneventful. The board was well laid out, and with the components correctly sourced, there was nothing unusual.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2025/08/ima_a680f67.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2025/08/ima_a680f67.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2025/08/ima_a680f67.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2025/08/ima_a680f67.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2025/08/ima_a680f67.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Partially assembled new board. The newly manufactured board with many components already soldered in place.&#xA0;</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2025/08/ima_b114496.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2025/08/ima_b114496.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2025/08/ima_b114496.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2025/08/ima_b114496.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2025/08/ima_b114496.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Soldering work, back side of new board. A look at the back side of the new board after assembly.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2025/08/ima_7936256.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2025/08/ima_7936256.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2025/08/ima_7936256.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2025/08/ima_7936256.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2025/08/ima_7936256.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Fully assembled new Partner board. The completed and fully populated replica of the Iskra Delta Partner motherboard, almost ready for its first power-up.</figcaption></figure><h2 id="step-7-switching-it-on">Step 7: Switching It On</h2><p>The board worked on the first try.</p><p>That outcome might sound simple, but it was the result of many careful steps. The work had not been fast or elegant. But it had been systematic, and each stage had been checked before moving on. So when the moment came to power it up, it worked.</p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="200" height="113" src="https://www.youtube.com/embed/EJtMwxrfw-g?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="Reverse-Engineered IDP Motherboard Working"></iframe><figcaption>Powering on the rebuilt Partner. The moment the newly built Iskra Delta Partner board is switched on. The system successfully boots and loads a Tetris game.</figcaption></figure><h2 id="why-i-did-it">Why I Did It</h2><p>This was not a technical achievement. I started the project without knowing much about electronics, and I finished it in roughly the same state. But that was never the point.</p><p>I wanted to see if it was possible to rebuild the board by treating the damaged original as a kind of visual map. I trusted the process and was willing to do the manual work. That turned out to be enough.</p><p>There are many ways to experience retro computing. Most are easier than this. But this approach created something unusual. It was not an attempt to learn the system. It was just a decision to rebuild it, step by step, without skipping anything. And it worked.</p><h2 id="resources">Resources</h2><ul><li><a href="https://github.com/OddbitCoder/Partner40">Project GitHub repository</a></li><li><a href="https://github.com/OddbitCoder/Partner40/blob/master/ReadMe.md">Project Read-Me</a></li></ul><h2 id="read-also">Read Also</h2><ul><li><a href="https://www.oddbit-retro.org/how-ai-helped-us-recreate-the-idp-motherboard/">How AI Helped Us Recreate the IDP Motherboard</a></li></ul>]]></content:encoded></item><item><title><![CDATA[Inside SDCC: Part 1 – Mastering the New Z80 ABI]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h2 id="for-the-benefit-of-mr-kite">For the Benefit of Mr. Kite</h2>
<p>As of <strong>SDCC 4.2.0+</strong>, the <strong>Z80 ABI</strong> (Application Binary Interface) has changed. The new calling convention improves performance by passing the first few function arguments in <strong>registers</strong> rather than on the <strong>stack</strong>, significantly reducing overhead in small or frequently called functions.</p>
<p>I</p>]]></description><link>https://www.oddbit-retro.org/sdcc-z80-abi-new-register-based-calling-convention/</link><guid isPermaLink="false">68585c9ee9fee000018871cf</guid><dc:creator><![CDATA[Tomaz Stih]]></dc:creator><pubDate>Sun, 22 Jun 2025 20:17:49 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="for-the-benefit-of-mr-kite">For the Benefit of Mr. Kite</h2>
<p>As of <strong>SDCC 4.2.0+</strong>, the <strong>Z80 ABI</strong> (Application Binary Interface) has changed. The new calling convention improves performance by passing the first few function arguments in <strong>registers</strong> rather than on the <strong>stack</strong>, significantly reducing overhead in small or frequently called functions.</p>
<p>I discovered this the hard way when I recompiled my old Iskra Delta Partner code with SDCC 4.2.0+, only to find that everything broke &#x2014; silently. The new ABI is now the default, and it&#x2019;s <strong>not backward compatible</strong> with the previous stack-only model. I was both impressed by the optimization and frustrated by the fallout. My entire codebase &#x2014; Z80 assembly wrappers, graphics drivers, and system libraries &#x2014; suddenly needed revision.</p>
<p>I want to fully adopt the optimized ABI, so I started reviewing how arguments are passed by reading the documentation and compiling test cases. This article summarizes that information in a clearer, example-driven form &#x2014; for the benefit of all fellow SDCC ABI martyrs now facing the same migration pain.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="abi-overview">ABI overview</h2>
<p>In the <strong>new Z80 calling convention</strong>, SDCC passes up to <strong>two arguments</strong> in registers. This table explains the basic rules of how SDCC passes function arguments under the default ABI introduced in SDCC 4.2.0 and later.<br>
<br></p>
<table>
<thead>
<tr>
<th>Argument Type(s)</th>
<th>Register(s) Used</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td>First <code>uint8_t</code></td>
<td><code>A</code> <strong>or</strong> <code>L</code></td>
<td><code>A</code> is used if all args are 8-bit; otherwise <code>L</code> is used</td>
</tr>
<tr>
<td>Second <code>uint8_t</code></td>
<td><code>L</code> <strong>or</strong> <code>E</code></td>
<td><code>L</code> if first was in <code>A</code>; <code>E</code> if first arg was 16-bit (<code>HL</code>)</td>
</tr>
<tr>
<td>Third and later <code>uint8_t</code></td>
<td>Stack</td>
<td>No register usage; pushed right-to-left via <code>push</code></td>
</tr>
<tr>
<td>First <code>uint16_t</code></td>
<td><code>HL</code></td>
<td>Full 16-bit register pair</td>
</tr>
<tr>
<td>Second <code>uint16_t</code></td>
<td><code>DE</code></td>
<td>Full 16-bit register pair</td>
</tr>
<tr>
<td>Third and later <code>uint16_t</code></td>
<td>Stack</td>
<td>Pushed via <code>push</code></td>
</tr>
<tr>
<td>Mixed <code>uint8_t</code>, <code>uint16_t</code></td>
<td><code>L</code>, <code>DE</code></td>
<td><code>A</code> is skipped; byte goes into <code>L</code>, word into <code>DE</code></td>
</tr>
<tr>
<td>Mixed <code>uint16_t</code>, <code>uint8_t</code></td>
<td><code>HL</code>, <code>E</code></td>
<td>Word in <code>HL</code>, byte in low byte of <code>DE</code> (<code>E</code>)</td>
</tr>
</tbody>
</table>
<p><strong>Notes:</strong></p>
<ul>
<li>For 8-bit arguments, only the low byte of the 16-bit register is used (<code>A</code>, <code>L</code>).</li>
<li>Additional arguments are passed <strong>right to left</strong> on the stack.</li>
<li>Structs and pointers follow the same rules based on size.</li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="function-argument-examples">Function argument examples</h2>
<p>The following table shows how SDCC passes function arguments under the new default ABI. Function names are abbreviated:</p>
<ul>
<li><code>b</code> = 8-bit (<code>uint8_t</code>)</li>
<li><code>w</code> = 16-bit (<code>uint16_t</code>)</li>
<li><code>fn2b1w</code> means: function takes 2 bytes, then 1 word</li>
</ul>
<p>Each function is named according to its signature:</p>
<ul>
<li><code>fn1b</code> &#x2014; function accepts 1 byte (8 bit) argument</li>
<li><code>fn2w</code> &#x2014; function accepts 2 word (16 bit) arguments</li>
<li><code>fn1w1b</code> &#x2014; function accepts 1 word followed by 1 byte arguments</li>
</ul>
<p>Here is the mapping table:<br>
<br></p>
<table>
<thead>
<tr>
<th>Function Signature</th>
<th>Name Meaning</th>
<th>Register Assignment</th>
<th>Stack Usage</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>void fn1b(uint8_t a)</code></td>
<td>1 byte</td>
<td><code>A</code></td>
<td>&#x2014;</td>
<td>Byte in <code>A</code></td>
</tr>
<tr>
<td><code>void fn1w(uint16_t a)</code></td>
<td>1 word</td>
<td><code>HL</code></td>
<td>&#x2014;</td>
<td>Word in <code>HL</code></td>
</tr>
<tr>
<td><code>void fn2b(uint8_t a, uint8_t b)</code></td>
<td>2 bytes</td>
<td><code>A</code>, <code>L</code></td>
<td>&#x2014;</td>
<td>First in <code>A</code>, second in <code>L</code></td>
</tr>
<tr>
<td><code>void fn2w(uint16_t a, uint16_t b)</code></td>
<td>2 words</td>
<td><code>HL</code>, <code>DE</code></td>
<td>&#x2014;</td>
<td>First in <code>HL</code>, second in <code>DE</code></td>
</tr>
<tr>
<td><code>void fn1b1w(uint8_t a, uint16_t b)</code></td>
<td>1 byte, 1 word</td>
<td><code>L</code>, <code>DE</code></td>
<td>&#x2014;</td>
<td><code>A</code> is skipped, <code>a</code> in <code>L</code>, <code>b</code> in <code>DE</code></td>
</tr>
<tr>
<td><code>void fn1w1b(uint16_t a, uint8_t b)</code></td>
<td>1 word, 1 byte</td>
<td><code>HL</code>, <code>E</code></td>
<td>&#x2014;</td>
<td><code>a</code> in <code>HL</code>, <code>b</code> in low byte of <code>DE</code></td>
</tr>
<tr>
<td><code>void fn3b(uint8_t a, uint8_t b, uint8_t c)</code></td>
<td>3 bytes</td>
<td><code>A</code>, <code>L</code>; third on stack</td>
<td>Yes</td>
<td>Only first two in registers</td>
</tr>
<tr>
<td><code>void fn2b1w(uint8_t a, uint8_t b, uint16_t c)</code></td>
<td>2 bytes + 1 word</td>
<td><code>A</code>, <code>L</code>; word on stack</td>
<td>Yes</td>
<td>Word <code>c</code> on stack</td>
</tr>
<tr>
<td><code>void fn3w(uint16_t a, uint16_t b, uint16_t c)</code></td>
<td>3 words</td>
<td><code>HL</code>, <code>DE</code>; third on stack</td>
<td>Yes</td>
<td>Third word always on stack</td>
</tr>
<tr>
<td><code>void fn5b(uint8_t a, b, c, d, e)</code></td>
<td>5 bytes</td>
<td><code>A</code>, <code>L</code>; rest on stack</td>
<td>Yes</td>
<td>Only two 8-bit args in registers</td>
</tr>
</tbody>
</table>
<hr>
<h3 id="fn1b"><code>fn1b</code></h3>
<pre><code class="language-c">void fn1b(uint8_t a);
</code></pre>
<p><strong>Passing:</strong></p>
<ul>
<li><code>a</code> &#x2192; <code>A</code></li>
</ul>
<hr>
<h3 id="fn1w"><code>fn1w</code></h3>
<pre><code class="language-c">void fn1w(uint16_t a);
</code></pre>
<p><strong>Passing:</strong></p>
<ul>
<li><code>a</code> &#x2192; <code>HL</code></li>
</ul>
<hr>
<h3 id="fn2b"><code>fn2b</code></h3>
<pre><code class="language-c">void fn2b(uint8_t a, uint8_t b);
</code></pre>
<p><strong>Passing:</strong></p>
<ul>
<li><code>a</code> &#x2192; <code>A</code></li>
<li><code>b</code> &#x2192; <code>L</code></li>
</ul>
<hr>
<h3 id="fn2w"><code>fn2w</code></h3>
<pre><code class="language-c">void fn2w(uint16_t a, uint16_t b);
</code></pre>
<p><strong>Passing:</strong></p>
<ul>
<li><code>a</code> &#x2192; <code>HL</code></li>
<li><code>b</code> &#x2192; <code>DE</code></li>
</ul>
<hr>
<h3 id="fn1b1w"><code>fn1b1w</code></h3>
<pre><code class="language-c">void fn1b1w(uint8_t a, uint16_t b);
</code></pre>
<p><strong>Passing:</strong></p>
<ul>
<li><code>a</code> &#x2192; <code>L</code></li>
<li><code>b</code> &#x2192; <code>DE</code></li>
</ul>
<blockquote>
<p>Note: SDCC avoids using <code>A</code> when mixing 8-bit and 16-bit args. It uses <code>L</code> instead.</p>
</blockquote>
<hr>
<h3 id="fn1w1b"><code>fn1w1b</code></h3>
<pre><code class="language-c">void fn1w1b(uint16_t a, uint8_t b);
</code></pre>
<p><strong>Passing:</strong></p>
<ul>
<li><code>a</code> &#x2192; <code>HL</code></li>
<li><code>b</code> &#x2192; <code>E</code></li>
</ul>
<hr>
<h3 id="fn3b"><code>fn3b</code></h3>
<pre><code class="language-c">void fn3b(uint8_t a, uint8_t b, uint8_t c);
</code></pre>
<p><strong>Passing:</strong></p>
<ul>
<li><code>a</code> &#x2192; <code>A</code></li>
<li><code>b</code> &#x2192; <code>L</code></li>
<li><code>c</code> &#x2192; stack</li>
</ul>
<hr>
<h3 id="fn2b1w"><code>fn2b1w</code></h3>
<pre><code class="language-c">void fn2b1w(uint8_t a, uint8_t b, uint16_t c);
</code></pre>
<p><strong>Passing:</strong></p>
<ul>
<li><code>a</code> &#x2192; <code>A</code></li>
<li><code>b</code> &#x2192; <code>L</code></li>
<li><code>c</code> &#x2192; stack (pushed right-to-left)</li>
</ul>
<hr>
<h3 id="fn3w"><code>fn3w</code></h3>
<pre><code class="language-c">void fn3w(uint16_t a, uint16_t b, uint16_t c);
</code></pre>
<p><strong>Passing:</strong></p>
<ul>
<li><code>a</code> &#x2192; <code>HL</code></li>
<li><code>b</code> &#x2192; <code>DE</code></li>
<li><code>c</code> &#x2192; stack</li>
</ul>
<hr>
<h3 id="fn5b"><code>fn5b</code></h3>
<pre><code class="language-c">void fn5b(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e);
</code></pre>
<p><strong>Passing:</strong></p>
<ul>
<li><code>a</code> &#x2192; <code>A</code></li>
<li><code>b</code> &#x2192; <code>L</code></li>
<li><code>c</code>, <code>d</code>, <code>e</code> &#x2192; stack</li>
</ul>
<blockquote>
<p>SDCC uses <code>push af</code> + <code>inc sp</code> to simulate <code>push a</code> (Z80 has no <code>push a</code>).</p>
</blockquote>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="return-values">Return Values</h2>
<p>Unlike the calling convention for function arguments, the ABI for <strong>return values has not changed</strong> in SDCC 4.2.0+. Return values have always been returned using registers, and the convention remains consistent:</p>
<ul>
<li><strong>8-bit values</strong> are returned in the <code>L</code> register (low byte of <code>HL</code>)</li>
<li><strong>16-bit values</strong> are returned in the <code>HL</code> register</li>
<li><strong>32-bit values</strong> (e.g. <code>long</code>, <code>float</code>) are returned across <code>HL:DE</code> with <code>HL</code> holding the high word and <code>DE</code> the low word</li>
</ul>
<p>This applies uniformly to both the old and the new ABI.<br>
<br></p>
<table>
<thead>
<tr>
<th>Return Type</th>
<th>Return Method</th>
<th>Registers Used</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>uint8_t</code> / <code>char</code></td>
<td>Register</td>
<td><code>L</code></td>
<td>Returned in low byte of <code>HL</code><br>Only <code>L</code> is significant</td>
</tr>
<tr>
<td><code>uint16_t</code> / <code>int</code></td>
<td>Register</td>
<td><code>HL</code></td>
<td>Full 16-bit result in <code>HL</code></td>
</tr>
<tr>
<td><code>uint32_t</code> / <code>long</code></td>
<td>Register pair</td>
<td><code>DE</code> (low), <code>HL</code> (high)</td>
<td>32-bit value returned in <code>HL:DE</code><br>Big endian across registers</td>
</tr>
<tr>
<td><code>float</code></td>
<td>Register pair</td>
<td><code>DE</code> (low), <code>HL</code> (high)</td>
<td>IEEE 754 32-bit float<br>Returned same as <code>long</code></td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="notes-on-push-af-trick">Notes on <code>push af</code> trick</h2>
<p>SDCC can push 8 bit value on the stack. But since Z80 has no <code>push a</code>, SDCC may emit:</p>
<pre><code class="language-asm">push af     ; pushes A and F
inc sp      ; discard F, simulate push A
</code></pre>
<p>This ensures consistent behavior when passing extra 8-bit arguments on the stack.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="conclusion">Conclusion</h2>
<p>The SDCC Z80 calling convention is now more efficient and modern:</p>
<ul>
<li>Arguments passed via registers (<code>A/L/E</code> or <code>HL/DE</code>)</li>
<li>Reduced stack usage</li>
<li>Compatible with inlined or <code>__naked</code> functions (if ABI respected)</li>
<li>Stack is used only when arguments exceed available registers</li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Do the Haiku!]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h2 id="introduction">Introduction</h2>
<p>Haiku OS is an open-source successor to BeOS. If we had to describe it in one word, it would be <em>charming</em>. If you&apos;ve ever wanted to write a program for this niche and enthusiast-driven retro operating system but were intimidated by cross-compilers, debuggers, and other complexities, this</p>]]></description><link>https://www.oddbit-retro.org/hello-haiku-os/</link><guid isPermaLink="false">67cd9477e9fee00001887089</guid><dc:creator><![CDATA[Tomaz Stih]]></dc:creator><pubDate>Tue, 11 Mar 2025 21:09:03 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="introduction">Introduction</h2>
<p>Haiku OS is an open-source successor to BeOS. If we had to describe it in one word, it would be <em>charming</em>. If you&apos;ve ever wanted to write a program for this niche and enthusiast-driven retro operating system but were intimidated by cross-compilers, debuggers, and other complexities, this guide is for you.</p>
<p>Here, I will show you how to bypass much of that complexity and compile programs for Haiku from the comfort of your VSCode setup while debugging them on a Haiku VM.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="first-set-up-haiku-os">First Set Up Haiku OS</h2>
<p><a href="https://www.haiku-os.org/get-haiku/r1beta5/">Download the latest Haiku OS here</a> and install it.</p>
<blockquote>
<p>I am running Haiku OS Release 1 Beta 5 (64-bit) on VirtualBox on Linux, so I will describe that installation scenario. However, apart from VirtualBox-specific steps, the procedure is the same for any environment.</p>
</blockquote>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="virtualbox-installation">VirtualBox Installation</h2>
<p>Create a new virtual machine and select <strong>Type: Other</strong> and <strong>Version: Other (64-bit)</strong>. I recommend the following settings:</p>
<ul>
<li>2 CPUs</li>
<li>10GB hard disk</li>
<li>2GB RAM</li>
<li>128MB video memory</li>
<li>Bridged network adapter</li>
</ul>
<p><img src="https://www.oddbit-retro.org/content/images/2025/03/vbox5.png" alt="vbox5" loading="lazy"></p>
<p><img src="https://www.oddbit-retro.org/content/images/2025/03/vbox4.png" alt="vbox4" loading="lazy"></p>
<p><img src="https://www.oddbit-retro.org/content/images/2025/03/vbox3.png" alt="vbox3" loading="lazy"></p>
<p><img src="https://www.oddbit-retro.org/content/images/2025/03/vbox2.png" alt="vbox2" loading="lazy"></p>
<p><img src="https://www.oddbit-retro.org/content/images/2025/03/vbox1.png" alt="vbox1" loading="lazy"></p>
<p>For Haiku OS installation, create a <strong>Be File System (BFS)</strong> partition on your virtual hard disk and install Haiku on it.</p>
<p><img src="https://www.oddbit-retro.org/content/images/2025/03/vbox6.png" alt="vbox6" loading="lazy"></p>
<blockquote>
<p>Initial clicks inside the Haiku OS window will capture your mouse. To release it, press the <strong>Right Ctrl</strong> key on your keyboard.</p>
</blockquote>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="adding-virtualbox-guest-additions">Adding VirtualBox Guest Additions</h3>
<p>After starting Haiku OS, open <strong>Haiku Depot</strong>, select all packages, and enter <code>virtualbox</code> in the search box. After installing the additions, you will gain two very useful features: <strong>mouse integration</strong> and <strong>dynamic screen resolution</strong>.</p>
<p><img src="https://www.oddbit-retro.org/content/images/2025/03/haiku1.png" alt="haiku1" loading="lazy"></p>
<p><img src="https://www.oddbit-retro.org/content/images/2025/03/haiku2.png" alt="haiku2" loading="lazy"></p>
<p>Mouse integration works automatically, but dynamic resolution needs to be manually configured. Open <strong>Haiku&apos;s Desktop Applets</strong> and run <code>VBoxTray</code>. Then, go to the <strong>VirtualBox View menu</strong> and select <strong>Auto-resize Guest Display</strong>.</p>
<p><img src="https://www.oddbit-retro.org/content/images/2025/03/haiku3.png" alt="haiku3" loading="lazy"></p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="enabling-ssh-access">Enabling SSH Access</h2>
<p>To enable SSH access, you first need to determine the IP address of your Haiku system. Since Haiku&apos;s terminal runs <em>bash</em> and supports basic Unix commands, simply type:</p>
<pre><code class="language-bash">ifconfig
</code></pre>
<p>Next, install and start the SSH daemon.</p>
<h3 id="installing-and-running-sshd">Installing and Running <code>sshd</code></h3>
<pre><code class="language-bash">pkgman install openssh
/bin/sshd
</code></pre>
<p>The first command installs the SSH daemon into the /bin folder, and the second one starts it.</p>
<h3 id="configuring-sshd-for-password-login">Configuring <code>sshd</code> for Password Login</h3>
<p>You&#x2019;ll log in using Haiku&#x2019;s default user, named user. Since Haiku is a single-user system, this default account doesn&#x2019;t have a password. You&#x2019;ll need to create one by typing:</p>
<pre><code class="language-bash">passwd
</code></pre>
<p>After setting your password, you must enable password authentication for SSH login. Open the Haiku SSH configuration file:</p>
<pre><code class="language-bash">nano /system/settings/ssh/sshd_config
</code></pre>
<blockquote>
<p>Yes, Haiku OS comes with the beloved <em>nano</em> editor by default.</p>
</blockquote>
<p>Find and modify (or add) the following lines:</p>
<pre><code>PasswordAuthentication yes
ChallengeResponseAuthentication yes
PermitRootLogin yes
PubkeyAuthentication yes
</code></pre>
<p>Next, restart the SSH daemon. First, find its process ID (replace sshd-process-id with the actual process ID in the example below):</p>
<pre><code class="language-bash">ps | grep sshd
kill sshd-process_id
/bin/sshd
</code></pre>
<p>You can now test password login from your Linux PC (replace your-haiku-ip with the IP address of your Haiku machine):</p>
<pre><code class="language-bash">ssh user@your-haiku-ip
</code></pre>
<h3 id="logging-in-via-ssh-without-a-password">Logging in via SSH Without a Password</h3>
<p>Since you&#x2019;ll likely use your Haiku system for debugging, you probably don&#x2019;t want to enter your username and password every time. To set up SSH key-based authentication, follow these steps.</p>
<p>On your Linux PC, generate a new SSH key:</p>
<pre><code class="language-bash">ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa
</code></pre>
<p>When prompted for a passphrase, just press Enter twice to leave it empty. Then copy the public key to your Haiku system:</p>
<pre><code class="language-bash">ssh-copy-id -i ~/.ssh/id_rsa.pub user@your-haiku-ip
</code></pre>
<p>Now, edit the Haiku SSH configuration file again:</p>
<pre><code class="language-bash">nano /system/settings/ssh/sshd_config
</code></pre>
<p>Update it to:</p>
<pre><code>PasswordAuthentication no
ChallengeResponseAuthentication no
PermitRootLogin prohibit-password
PubkeyAuthentication yes
AuthorizedKeysFile boot/home/.ssh/authorized_keys
</code></pre>
<p>Restart sshd, and you should now be able to log in without a password.</p>
<h3 id="a-touch-of-elegance">A Touch of Elegance</h3>
<p>If you don&#x2019;t want to type the IP address every time, you can optionally create a name for your Haiku machine that SSH will recognize. You can set this up in two places.</p>
<p>Let&#x2019;s assume your IP address is 192.168.1.149.</p>
<p>You can either edit the <code>/etc/hosts</code> file and add a mapping for your IP address:</p>
<pre><code>192.168.1.149 haiku-vm
</code></pre>
<p>Or you can create a ~/.ssh/config file with the following content:</p>
<pre><code>Host haiku-vm
    HostName 192.168.1.149
    User user
    IdentityFile ~/.ssh/id_rsa
    IdentitiesOnly yes
    RequestTTY force
</code></pre>
<p>Both methods will allow you to use haiku-vm as the host name. You can now test the connection by running:</p>
<pre><code>ssh user@haiku-vm
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="installing-tools-on-haiku-os">Installing Tools on Haiku OS</h2>
<p>Haiku brings its own compilation suite including gcc and g++, but you will need to add gdb, cmake, and rsync on your. Go to your Haiku Terminal and type</p>
<pre><code class="language-bash">pkgman install cmake
pkgman install gdb
pkgman install rsync
</code></pre>
<blockquote>
<p><strong>pkgman</strong> is a command line version of the Haiku Depot for those of us who prefer using the command line.</p>
</blockquote>
<p>Congratulations! Your Haiku system is now ready for development.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="set-up-your-project-in-visual-studio-code">Set Up Your Project in Visual Studio Code</h2>
<p>Now it&apos;s time to prepare the project you will compile and debug on your Haiku machine. This will be a simple C++ program located inside the <code>src/</code> directory that displays a basic message in a window.</p>
<h3 id="hello-haiku">Hello Haiku</h3>
<p>Below is the C++ code for our main.cpp.</p>
<pre><code class="language-cpp">#include &lt;Application.h&gt;
#include &lt;Window.h&gt;
#include &lt;View.h&gt;
#include &lt;StringView.h&gt;

class HelloWorldWindow : public BWindow {
public:
    HelloWorldWindow()
        : BWindow(BRect(100, 100, 400, 200), &quot;Hello Haiku&quot;, B_TITLED_WINDOW, B_QUIT_ON_WINDOW_CLOSE) {
        BView* background = new BView(Bounds(), &quot;background&quot;, B_FOLLOW_ALL, B_WILL_DRAW);
        background-&gt;SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
        AddChild(background);
        
        BStringView* helloText = new BStringView(BRect(20, 20, 280, 40), &quot;hello&quot;, &quot;Hello Haiku!&quot;);
        background-&gt;AddChild(helloText);
    }
};

class HelloWorldApp : public BApplication {
public:
    HelloWorldApp() : BApplication(&quot;application/x-vnd.HelloHaiku&quot;) {}

    void ReadyToRun() {
        HelloWorldWindow* window = new HelloWorldWindow();
        window-&gt;Show();
    }
};

int main() {
    HelloWorldApp app;
    app.Run();
    return 0;
}
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="cmakeliststxt">CMakeLists.txt</h3>
<p>Now create the <code>CMakeLists.txt</code> file in the root directory to build the program.</p>
<pre><code class="language-CMake">cmake_minimum_required(VERSION 3.10)

# Project Name
project(HaikuSexProject)

# Set C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Target system (Haiku)
set(CMAKE_SYSTEM_NAME Haiku)

# Set Debug Build Type
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Debug)
endif()

# Define source files
set(SRC_FILES src/main.cpp)

# Add the executable
add_executable(HaikuSex ${SRC_FILES})

# Find Haiku system include directories and libraries
find_path(HAIKU_INCLUDE_DIR NAMES app/Application.h PATHS /boot/system/develop/headers/be)
find_library(HAIKU_LIB_BE NAMES be PATHS /boot/system/develop/lib)

# Include Haiku headers
target_include_directories(HaikuSex PRIVATE ${HAIKU_INCLUDE_DIR})

# Link against Haiku&apos;s BeAPI
target_link_libraries(HaikuSex PRIVATE ${HAIKU_LIB_BE})
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>It&apos;s time to create our <code>tasks.json</code> and <code>launch.json</code> files.</p>
<h3 id="tasksjson">tasks.json</h3>
<p>There are two tasks that need to be executed before we can start debugging our software:</p>
<ul>
<li>We need to copy all source code files to the Haiku machine.</li>
<li>We need to run <code>g++</code> on the Haiku machine and compile the code there.</li>
</ul>
<p>Here is the file that accomplishes this:</p>
<pre><code>{
    &quot;version&quot;: &quot;2.0.0&quot;,

    &quot;tasks&quot;: [
        {
            &quot;label&quot;: &quot;Sync to Haiku VM (rsync)&quot;,
            &quot;type&quot;: &quot;shell&quot;,
            &quot;command&quot;: &quot;rsync&quot;,
            &quot;args&quot;: [
                &quot;-avz&quot;,
                &quot;--rsync-path=/boot/system/bin/rsync&quot;,
                &quot;-e&quot;,
                &quot;ssh -T -o &apos;RemoteCommand=none&apos; -o &apos;RequestTTY=no&apos;&quot;,
                &quot;/media/tomaz/Work/sex/&quot;,
                &quot;user@haiku-vm:/boot/home/Projects/sex/&quot;
            ],
            &quot;problemMatcher&quot;: [],
            &quot;group&quot;: {
                &quot;kind&quot;: &quot;build&quot;,
                &quot;isDefault&quot;: false
            }
        },
        {
            &quot;label&quot;: &quot;CMake Configure (Haiku VM)&quot;,
            &quot;type&quot;: &quot;shell&quot;,
            &quot;command&quot;: &quot;ssh&quot;,
            &quot;args&quot;: [
                &quot;user@haiku-vm&quot;,
                &quot;cd /boot/home/Projects/sex/ &amp;&amp; cmake -B build -G \&quot;Unix Makefiles\&quot; -DCMAKE_BUILD_TYPE=Debug&quot;
            ],
            &quot;problemMatcher&quot;: [],
            &quot;dependsOn&quot;: [&quot;Sync to Haiku VM (rsync)&quot;]
        },
        {
            &quot;label&quot;: &quot;Build (Haiku VM)&quot;,
            &quot;type&quot;: &quot;process&quot;,
            &quot;command&quot;: &quot;bash&quot;,
            &quot;args&quot;: [
                &quot;-c&quot;,
                &quot;ssh user@haiku-vm &apos;cd /boot/home/Projects/sex/build &amp;&amp; make -j4&apos; | sed &apos;s|/boot/home/Projects/sex|/media/tomaz/Work/sex|g&apos;&quot;
            ],
            &quot;group&quot;: {
                &quot;kind&quot;: &quot;build&quot;,
                &quot;isDefault&quot;: true
            },
            &quot;problemMatcher&quot;: &quot;$gcc&quot;,
            &quot;dependsOn&quot;: [&quot;CMake Configure (Haiku VM)&quot;]
        }
    ]
}
</code></pre>
<p>The <code>Sync to Haiku VM (rsync)</code> task uses <code>rsync</code> to copy the the project to the Haiku machine.</p>
<blockquote>
<p>Only files that have changed are copied.</p>
</blockquote>
<p>The next task, called <code>CMake Configure (Haiku VM)</code>, runs <em>CMake</em> on the Haiku machine to generate a Makefile for the project.</p>
<p>Finally, the <code>Build (Haiku VM)</code> task executes <em>make</em> on the <code>Makefile</code> generated in the previous step and uses set to re-map paths from the Haiku station to your Linux PC.</p>
<p>All tasks are interdependent. After executing all three, you should have a built version of your software on the <code>haiku-vm</code> machine.</p>
<blockquote>
<p>You need to change the folders in the <code>tasks.json</code> and create an empty directory on Haiku. The rsync will not do that for you.</p>
</blockquote>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="launchjson">launch.json</h3>
<p>After defining the VSCode tasks, we need to create a launch configuration to run and debug the software on the Haiku machine. Before proceeding, make sure you have the <strong>Native Debug</strong> extension installed in your VSCode.</p>
<p>This configuration assumes your program is located in the <code>/boot/home/Projects/first</code> directory, with the executable inside the <code>build/myprogram</code> subdirectory.</p>
<p>It maps the source directories in the debug information inside the executable to the corresponding directory on the Haiku machine. All <code>gdb</code> traffic is routed through an SSH channel to the integrated terminal in VSCode.</p>
<p>Finally, it compiles your software before starting the debugging session.</p>
<pre><code>{
    &quot;version&quot;: &quot;0.2.0&quot;,
    &quot;configurations&quot;: [
        {
            &quot;name&quot;: &quot;Debug on Haiku VM&quot;,
            &quot;type&quot;: &quot;cppdbg&quot;,
            &quot;request&quot;: &quot;launch&quot;, // You can switch this to &quot;attach&quot; later
            &quot;program&quot;: &quot;/boot/home/Projects/sex/build/src/core/HaikuSex&quot;,
            &quot;cwd&quot;: &quot;/boot/home/Projects/sex/build/&quot;,
            &quot;MIMode&quot;: &quot;gdb&quot;,
            &quot;ssh&quot;: {
                &quot;host&quot;: &quot;haiku-vm&quot;,
                &quot;user&quot;: &quot;user&quot;
            },
            &quot;pipeTransport&quot;: {
                &quot;pipeProgram&quot;: &quot;ssh&quot;,
                &quot;pipeArgs&quot;: [
                    &quot;-T&quot;,
                    &quot;user@haiku-vm&quot;
                ],
                &quot;debuggerPath&quot;: &quot;/bin/gdb&quot;
            },
            &quot;setupCommands&quot;: [
                {
                    &quot;text&quot;: &quot;-exec set pagination off&quot;,
                    &quot;description&quot;: &quot;Disable pagination&quot;,
                    &quot;ignoreFailures&quot;: true
                },
                {
                    &quot;text&quot;: &quot;-gdb-set scheduler-locking on&quot;,
                    &quot;description&quot;: &quot;Lock scheduler for single thread stepping&quot;,
                    &quot;ignoreFailures&quot;: true
                }
            ],
            &quot;sourceFileMap&quot;: {
                &quot;/boot/home/Projects/sex/&quot;: &quot;${workspaceFolder}&quot;
            },
            &quot;preLaunchTask&quot;: &quot;Build (Haiku VM)&quot;,
            &quot;console&quot;: &quot;integratedTerminal&quot;,
            &quot;externalConsole&quot;: false,
            &quot;stopAtEntry&quot;: true
        }
    ]
}

</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="finale">Finale</h2>
<p>Believe it or not, we&apos;re done! You can now use this rather unconventional method to debug Haiku software.</p>
<blockquote>
<p>The conventional approach would be to use <code>gdbserver</code>, but it is not stable at the moment.</p>
</blockquote>
<p>All that&#x2019;s left is to start your Haiku virtual machine, open VSCode, and run the program.</p>
<p><img src="https://www.oddbit-retro.org/content/images/2025/03/run.png" alt="run" loading="lazy"></p>
<p>Finally, <a href="https://github.com/wischner/sex">here is a link to the GitHub repository</a> containing a working version.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Fixing a ZX Spectrum Issue 2: The Good, the Bad, and the Ugly]]></title><description><![CDATA[<p>I recently got my hands on a ZX Spectrum Issue 2. It was housed in a keyboard casing made in the former Yugoslavia, with keys produced by a Slovenian company (IEVT). The excitement of finding it quickly turned into a mix of curiosity and dread once I opened it up.</p>]]></description><link>https://www.oddbit-retro.org/fixing-a-zx-spectrum-issue-2-the-good-the-bad-and-the-ugly/</link><guid isPermaLink="false">673a1566e9fee00001886fec</guid><dc:creator><![CDATA[Miha Grčar]]></dc:creator><pubDate>Sun, 17 Nov 2024 16:56:02 GMT</pubDate><content:encoded><![CDATA[<p>I recently got my hands on a ZX Spectrum Issue 2. It was housed in a keyboard casing made in the former Yugoslavia, with keys produced by a Slovenian company (IEVT). The excitement of finding it quickly turned into a mix of curiosity and dread once I opened it up.</p><p>Here&#x2019;s how the repair went.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2024/11/ima_5230281.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2024/11/ima_5230281.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2024/11/ima_5230281.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2024/11/ima_5230281.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2024/11/ima_5230281.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>ZX Spectrum Issue 2.</figcaption></figure><h2 id="what-i-found">What I Found</h2><p>It was obvious that this Spectrum had been worked on before&#x2014;several times, by different people. The PCB had a ton of flux residue, and the soldering quality was all over the place. There were factory-original joints, some relatively clean repairs, and some truly messy ones.</p><p>The worst soldering jobs were on:</p><ul><li>DRAM chips</li><li>One of the upper RAM chips</li><li>The ULA</li><li>The CPU</li></ul><p>And TR4 was missing altogether.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2024/11/ima_f426684.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2024/11/ima_f426684.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2024/11/ima_f426684.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2024/11/ima_f426684.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2024/11/ima_f426684.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Not great, not terrible. Well, quite terrible.</figcaption></figure><h3 id="power-problems"><strong>Power Problems</strong></h3><p>To see what I was dealing with, I plugged in a diagnostics module. It immediately flagged missing 12V and -5V power rails. With TR4 missing, this wasn&#x2019;t a shock. After soldering in a replacement TR4, the 12V rail came back, but the -5V rail was still reading -1.5V. There was a high-pitched whining sound coming from the board, most likely from the coil.</p><p>I checked the schematics and replaced two capacitors (C46 and C47), then tested the diodes D11 and D12. D11 turned out to be shorted. Replacing it fixed the -5V rail, and the whining stopped.</p><h3 id="ram-troubles"><strong>RAM Troubles</strong></h3><p>The diagnostics now reported issues with the lower DRAM. I started removing the lower RAM chips, and that&#x2019;s where things got ugly. The traces under the chips were already damaged, and my desoldering work made it worse, especially on the last chip. I ended up having to rebuild the damaged traces with thin copper wire. Once that was done, I soldered IC sockets over the repaired area, which went fine, but the damage was done. My heart was broken.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2024/11/ima_d90f96e.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2024/11/ima_d90f96e.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2024/11/ima_d90f96e.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2024/11/ima_d90f96e.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2024/11/ima_d90f96e.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Recreating broken traces.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2024/11/ima_aeff7b3.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2024/11/ima_aeff7b3.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2024/11/ima_aeff7b3.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2024/11/ima_aeff7b3.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2024/11/ima_aeff7b3.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>My heart was broken, but I had to carry on.</figcaption></figure><p>I inserted a DRAM module into the sockets and ran the diagnostics again. The lower RAM test passed, but the upper RAM test flagged errors. The screen was scrambled.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2024/11/ima_4bc511c.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2024/11/ima_4bc511c.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2024/11/ima_4bc511c.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2024/11/ima_4bc511c.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2024/11/ima_4bc511c.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>The lower RAM test passed, the upper RAM test flagged errors. The screen was scrambled.</figcaption></figure><p>I clipped the legs off the upper RAM chips and removed them without causing any additional damage. At this point, the board was effectively running as a 16K Spectrum.</p><h3 id="scrambled-screen-and-ula-issues"><strong>Scrambled Screen and ULA Issues</strong></h3><p>With the upper RAM out of the equation, I was now working with a 16K Spectrum. The diagnostics passed the lower RAM tests, but the display was a mess. This suggested the CPU could access the RAM without issues, but the ULA could not.</p><p>I also noticed something strange: if I swapped the DRAM module for individual 4116 chips, they were all flagged as broken. This didn&#x2019;t provide any additional clues, but my money was still on the ULA. I didn&#x2019;t pretend to fully understand how a broken ULA affects the board.</p><p>So, I desoldered the ULA and replaced it with a socket (thankfully without causing any damage this time). After swapping in a known-good ULA, the scrambled screen problem disappeared, and the Spectrum was finally working properly.</p><h3 id="what-was-fixed"><strong>What Was Fixed</strong></h3><p>Here&#x2019;s the rundown of everything that needed repair on this ZX Spectrum Issue 2:</p><ul><li><strong>TR4</strong>: Missing, replaced.</li><li><strong>D11</strong>: Shorted, replaced.</li><li><strong>Lower RAM</strong>: Rebuilt damaged traces, added IC sockets, replaced chips.</li><li><strong>ULA</strong>: Replaced.</li><li><strong>Upper RAM</strong>: Removed for now; further work needed.</li><li><strong>My heart</strong>: Broken. Unable to fix.</li></ul><p>I haven&#x2019;t restored the upper RAM yet, but at this point, I&#x2019;m confident it&#x2019;ll be straightforward. I tested the DRAM chips I removed earlier&#x2014;half of them were dead. While that&#x2019;s some consolation, I regret not clipping their legs in the first place. </p><h3 id="takeaways"><strong>Takeaways</strong></h3><ol><li><strong>Diagnose First</strong>: A diagnostics module makes troubleshooting much easier.</li><li><strong>Be Careful with RAM Chips</strong>: Clipping the legs before desoldering can save you a lot of headaches.</li><li><strong>Watch Out for the ULA</strong>: It can cause some weird issues that aren&#x2019;t immediately obvious.</li><li><strong>Fixing Old Tech Isn&#x2019;t Always Pretty</strong>: Sometimes it&#x2019;s a battle, but it&#x2019;s worth it.</li></ol><p>This repair wasn&#x2019;t perfect, and it wasn&#x2019;t pretty, but the Spectrum works again. If you&#x2019;re restoring one of these old machines, just take it slow, learn from your mistakes, and don&#x2019;t beat yourself up too much if things don&#x2019;t go perfectly.</p>]]></content:encoded></item><item><title><![CDATA[Retro Game Programming Tips, Part 2: Double Buffering with Page Switching]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h1 id="introduction">Introduction</h1>
<p>In this second part of our retro game programming series, we&apos;ll look at implementing double buffering with page switching. Double buffering is a technique where you draw your game screen into a buffer, and then quickly copy the entire buffer onto the screen to prevent flickering. If</p>]]></description><link>https://www.oddbit-retro.org/retro-game-programming-tips-part-2-page-switching/</link><guid isPermaLink="false">65ede2b8e9fee00001886e41</guid><dc:creator><![CDATA[Tomaz Stih]]></dc:creator><pubDate>Sun, 10 Mar 2024 17:53:37 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h1 id="introduction">Introduction</h1>
<p>In this second part of our retro game programming series, we&apos;ll look at implementing double buffering with page switching. Double buffering is a technique where you draw your game screen into a buffer, and then quickly copy the entire buffer onto the screen to prevent flickering. If the computer supports multiple video memory pages then your buffer is the next page and the switch is implemented in hardware, costs no additional CPU cycles, and is very fast.</p>
<p>In this article, we&apos;ll assume two pages and the ability to tell the system which page is currently being drawn to and which page is currently displayed. We&apos;ll then alternate between the drawing page and the display page to implement the switching.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h1 id="platform-specifics">Platform Specifics</h1>
<p>Let&apos;s start our journey by defining our platform-specific functions.</p>
<p>The function <code>gsetpage(pg_type, pg_no)</code> sets the current page. Valid values for <code>pg_type</code> are <code>PG_DISPLAY</code> for the page currently displayed, and <code>PG_WRITE</code> for the page to which all graphical operations go. These two values are bitmasks, so you can use them together. In other words, you can call the function like this: <code>gsetpage(PG_DISPLAY|PG_WRITE, 0)</code>. Since we&apos;re assuming we only have two pages, the valid values for <code>pg_no</code> are 0 and 1.</p>
<p>Function <code>gsetcolor(color)</code> accepts only two values: <code>CO_FORE</code> for foreground color, and <code>CO_BACK</code> for background color. Drawing with the background color effectively erases the content. Finally, function <code>gcls()</code> clears the write page.</p>
<p>Now let us use all of them to write the clear screen function.<br>
<br></p>
<pre lang="c">
void clear_screen()
{
    gsetpage(PG_WRITE,0);
    gcls();
    gsetpage(PG_WRITE,1);
    gcls();
    gsetpage(PG_DISPLAY,0);
    gsetcolor(CO_FORE);
}
</pre><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h1 id="the-game-state">The Game State</h1>
<p>We need a structure to store our game state. For simplicity, let&apos;s assume that we are only going to draw one sprite. Our game state structure, therefore, needs at least three members: the current display page, the current sprite position, and the previous sprite position.</p>
<p>Why do we need the previous sprite position? Because we will be drawing to a different page each cycle. And when we draw the next page, we will need to erase the previous content. The previous coordinates will help us with this task.</p>
<p>Our game structure will look like this:<br>
<br></p>
<pre lang="c">
typedef struct game_s {
    uint8_t page;
    int x;
    int y;
    int prevx;
    int prevy;
}
</pre>
<p>Following figure demonstrates our algorithm.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://www.oddbit-retro.org/content/images/2024/03/paging-steps.png" class="kg-image" alt loading="lazy" width="666" height="501" srcset="https://www.oddbit-retro.org/content/images/size/w600/2024/03/paging-steps.png 600w, https://www.oddbit-retro.org/content/images/2024/03/paging-steps.png 666w"></figure><!--kg-card-begin: markdown--><h1 id="implementation">Implementation</h1>
<p>We are now finally ready to implement our game loop. We will start by initializing the game structure. Then we will enter the loop and alternately display the current page and prepare the next page for displaying. We will use data from the previous page for content removal and data from the current page for calculating the next sprite position.<br>
<br></p>
<pre lang="c">
void game_loop() {

    /* remember our clear screen? */
    clear_screen();

    /* initialize game at position 0,0 */
    game_t g = { 0, 0, 0, 0, 0 };

    /* first display page to 1, but write page to 0 */
    gsetpage(PG_DISPLAY,1);
    gsetpage(PG_WRITE,g.page);

    /* is there previous game state? */
    bool has_prev_state = false;

    while(1) {

        /* set color to foreground */
        gsetcolor(CO_FORE);
        
        /* you need to provide function draw_sprite to draw sprite
           at position x,y */
        draw_sprite(g.x, g.y);
        
        /* show drawn page */
        gsetpage(PG_DISPLAY,g.page);

        /* set next page as drawing page */
        g.page = g.page ? 0 : 1;
        gsetpage(PG_WRITE,g.page);

        /* delete previous lines? */
        if (has_prev_state) {
            /* setting color to CO_BACK will delete everything
               drawn previously. */
            gsetcolor(CO_BACK);
            /* and erase sprite using previous coordinates */
            draw_sprite(g.prevx, g.prevy);
        } else 
            has_prev_state=true;

        /* store prev. state  */
        g.prevx=g.x; g.prevy=g.y;
        
        /* your function to calculate next sprite position
           and set g.x and g.y */
        calc_next_sprite_position(&amp;g);

        /* game over? */
        if (end_of_game()) break;
    }
}
</pre><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p><a href="https://www.oddbit-retro.org/retro-game-programming-tips-part-1-the-game-clock/">Retro Game Programming Tips, Part 1: The Game Clock</a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Iskra Delta Partner Graphics Programming, Part 1: Embracing the Art of Line Drawing]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h1 id="introduction">Introduction</h1>
<p>In today&apos;s world of high-resolution graphics and advanced GPUs, it&apos;s easy to forget the humble beginnings of computer graphics. One such piece of history is the <em>Iskra Delta Partner</em>, a 40-year-old computer with a <em>Thomson EF9367</em> Graphical Display Processor (GDP) that can only draw lines.</p>]]></description><link>https://www.oddbit-retro.org/iskra-delta-partner-graphics/</link><guid isPermaLink="false">64439948c5ce8400019250b1</guid><dc:creator><![CDATA[Tomaz Stih]]></dc:creator><pubDate>Sun, 12 Nov 2023 07:08:26 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h1 id="introduction">Introduction</h1>
<p>In today&apos;s world of high-resolution graphics and advanced GPUs, it&apos;s easy to forget the humble beginnings of computer graphics. One such piece of history is the <em>Iskra Delta Partner</em>, a 40-year-old computer with a <em>Thomson EF9367</em> Graphical Display Processor (GDP) that can only draw lines. The constraints of this system make programming for it an exotic and intriguing challenge, as there is no raster support. It&apos;s almost like working with a vector display, where everything must be drawn using lines only.</p>
<blockquote>
<p>The EF9367 chip supports raster operations and text. However, in the Iskra Delta Partner, raster functionality is disabled. Furthermore, the chip is limited to a single 5x7 font, which it renders internally using lines alone. This approach lacks clipping capabilities, rendering it unsuitable for windowing graphical user interfaces.</p>
</blockquote>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/04/image.png" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/04/image.png 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/04/image.png 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/04/image.png 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/04/image.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>You begin to admire the skill involved in creating games for the IDP when you realize that this image, which displays in mere seconds, would take minutes to render if drawn using lines of single-pixel length.udp</figcaption></figure><!--kg-card-begin: markdown--><p>In this series we will develop a 2D graphics library and support tools for the Iskra Delta Partner.</p>
<p>Our initial task for the Part 1 of the series is to develop a tool that, when provided with a PNG image, converts it into lines, essentially vectorizing the image.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h1 id="prerequisites">Prerequisites</h1>
<p>To fully grasp the concepts presented, you should have a basic understanding of the graph theory and know C++, <em>CMake</em> and <em>git</em>.</p>
<p>We will develop on Linux using modern C++ and the CMake build system. Therefore, you will need the latest version of either g++ or clang, with support for the C++20 standard.</p>
<p>We will use <a href="https://github.com/pboettch/cxx_argp">Patrick Boettcher&apos;s header only command line argument parser library</a>.</p>
<p>The <em>Standard C++ library</em> lacks images processing capabilities, so we will use <a href="https://github.com/nothings/stb">Sean Barrett&apos;s header-only libraries</a> to address this limitation. These libraries enables us to read a PNG file and convert it into a raw RGB array, which can then be traversed and processed as needed.</p>
<blockquote>
<p>Our objective is to examine algorithms more closely, which will involve some raw programming. For professional development, using <em>OpenCV</em> would be a more suitable option, as it already has most of the algorithms implemented.</p>
</blockquote>
<p>The URL for our Git repository is <a href="https://github.com/tstih/gpxtools">https://github.com/tstih/gpxtools</a>. If you clone it using the <code>--recursive</code> switch, the mentioned libraries will be downloaded as submodules</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h1 id="problem-analysis">Problem analysis</h1>
<p>Let us examine the process of rendering a mouse cursor on a raster single bit-per-pixel display. Typically, this involves two bitmaps: the cursor and the mask. First, the mask is transferred to the screen using an <code>OR</code> operation, which sets all mouse pixels to black. Next, the cursor is copied using a <code>NAND</code> operation, which clears the interior of the mouse cursor. The figure below illustrates the resulting image.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/04/sub-5.svg" class="kg-image" alt loading="lazy" width="797" height="239"><figcaption>Cursor masking on a raster display</figcaption></figure><!--kg-card-begin: markdown--><p>There are several methods we can employ to draw this cursor using only lines.</p>
<p>First, we could represent each pixel as a horizontal line with a width of one pixel. To draw the mouse cursor in this manner, from left to right and top to bottom, we would need to create 45 lines, change the color 17 times, and reposition the cursor without drawing the line 11 times. Clearly, sending 73 commands to the Graphics Display Processor (GDP) is a costly approach for drawing a simple 7x10 pixel mouse cursor.</p>
<p>Alternatively, we could attempt to draw all pixels of the same color using a single horizontal line. For instance, instead of drawing three white pixels individually, we would draw one line spanning three pixels in length. This approach would decrease the number of drawing commands sent to the Graphics Display Processor (GDP) from 45 to 29. However, it would not affect the number of times we need to change the color or reposition the cursor without drawing. Using 57 commands is still very expensive.</p>
<p>As we delve into the proposed algorithm, it becomes clear that these solutions aren&apos;t the best. We&apos;ve come to this realization because, honestly, we can draw the mouse cursor ourselves in under 20 strokes, which is way more efficient. So, in this part of the article, let&apos;s try to come up with better solution to tackle this problem. And in the next part, we&apos;re going to take it up a notch and optimize the solution by diving into the world of graphs. Exciting stuff, right?</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="blob-detector">Blob detector</h2>
<p>Points that don&apos;t &quot;adhere together&quot; cannot form lines. To narrow down the search space for line detection, our initial step is to identify pixels that &quot;cling together.&quot; We accomplish this by performing a simple <a href="https://www.robotix.in/tutorial/imageprocessing/blob_detection/">blob detection process</a>.</p>
<p>The subsequent code loads the PNG image utilizing the STB library.</p>
<pre lang="c">
image::image(std::string path) {
    int channels;
    data_ = ::stbi_load(
        path.c_str(), 
        &amp;width_, 
        &amp;height_, 
        &amp;channels, 
        1); // This will convert any image to grayscale!

    if (data_ == nullptr)
        throw exception(&quot;Unable to read image.&quot;);
}
</pre>
<p>Before processing, the image is converted to grayscale. After loading the image, we identify groups of pixels that &quot;adhere together&quot; and return a vector of offsets from the beginning of the image in memory using the provided code.</p>
<pre lang="c">
std::vector &lt; std::vector &lt; int &gt; &gt; blobs(
    const image&amp; img, 
    uint8_t threshold);
{
    // First create data structure to store bulk pixels.
    uint16_t w = img.width(), h = img.height();
    int len = w * h;
    uint8_t pixels[len];
    union_t &lt; uint8_t &gt; id;

    // Pass 1: Loop through pixels.
    int index = 0;
    uint8_t *pimg = img.data(), *ppix = pixels;
    for (uint16_t y = 0; y &lt; h; y++)
        for (uint16_t x = 0; x &lt; w; x++)
        {
            // Default is white...
            *ppix = 0;
            if (is_black(pimg, threshold))
            {
                // Get left top, top, right top and left values
                uint8_t
                    lt = y &amp;&amp; x ? *(ppix - w - 1) : 0,
                    t = y ? *(ppix - w) : 0,
                    rt = y &amp;&amp; (x &lt; w) ? *(ppix - w + 1) : 0,
                    l = x ? *(ppix - 1) : 0;
                // Set the value!
                if (lt)
                    *ppix = lt;
                else if (t)
                    *ppix = t;
                else if (rt)
                    *ppix = rt;
                else if (l)
                    *ppix = l;
                else
                    *ppix = ++index;
                // And create equiv, if any detected.
                uint8_t c = *ppix;
                if (c != t &amp;&amp; t)
                    id.merge(c, t); // Top check.
                if (c != rt &amp;&amp; rt)
                    id.merge(c, rt); // Right top check.
                if (c != l &amp;&amp; l)
                    id.merge(c, l); // Left check.
            }
            pimg++;
            ppix++;
        }

    // Pass 2: Resolve identities and generate the result.
    ppix = pixels;
    uint8_t c, root;
    std::map &lt; uint8_t,std::vector &lt; int &gt; &gt; group;
    while (len--)
    {
        // Is there a pixel?
        if (c = *ppix) {
            if ((root = id.find(c)) != c &amp;&amp; root)
                *ppix = root;
            // Add offset to map.
            group[*ppix].push_back(ppix-pixels);
        }
        ppix++;
    }

    // Finally, restructure the result.
    std::vector &lt; std::vector &lt; int &gt; &gt; result;
    for (auto&amp; kv : group) {
        result.push_back(kv.second);
    }
    return result;
}
</pre>
<blockquote>
<p>You can view the complete source code on GitHub. This implementation utilizes the <a href="https://en.wikipedia.org/wiki/Disjoint-set_data_structure">union-find (disjoint-set) data structure</a>.</p>
</blockquote>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>After running our blob detector on an image to extract individual features...</p>
<p><img src="https://www.oddbit-retro.org/content/images/2023/05/gamepad-top.png" alt="gamepad-top" loading="lazy"></p>
<p>...we can focus our efforts on drawing each feature individually.</p>
<p><img src="https://www.oddbit-retro.org/content/images/2023/05/test-2.png" alt="test-2" loading="lazy"></p>
<blockquote>
<p>Notice that numerous features in this image exhibit similarity, albeit being drawn at different positions. For specific images showcasing repetitive patterns, this allows for <strong>feature compression</strong>.</p>
</blockquote>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="line-detector">Line detector</h2>
<p>We will employ a custom graph algorithm specifically designed for line detection in this task. Here&apos;s a step-by-step breakdown of the algorithm:</p>
<p>For each blob...</p>
<ol>
<li>Get first unused pixel</li>
<li>Find the point that is farthest away from pixel A by calculating the distance in pixels, considering the shorter of the horizontal and vertical distances.</li>
<li>Employ the Bresenham line algorithm to iterate from pixel A to pixel B. Check if all the pixels between A and B are present in the pixel set. If they are, a line has been detected.</li>
<li>If the pixels between A and B are not found, proceed to find the next farthest pixel and repeat the process.</li>
<li>If the distance between A and B is 2, then this constitutes a line.</li>
<li>If A==B then detect a pixel as the line.</li>
</ol>
<p>And the implementation in C.</p>
<pre lang="c">
std::vector &lt; std::pair &lt; int,int &gt; &gt; vectorize(
    const image &amp;img,           // The image.
    std::vector &lt; int &gt; pixels,    // The stroke.
    uint8_t threshold){         // Black pixel threshold.

    // Result is a vector of pair of points.
    std::vector &lt; std::pair &lt; int,int &gt; &gt; lines;

    // Vector should already be sorted if coming from blobs.
    std::sort(pixels.begin(), pixels.end());

    // Used, unused and visited pixels. Initialize unset to pixels.
    std::set &lt; int &gt; used, visited, unused(pixels.begin(),pixels.end());

    // Get first element.
    auto pstart=*unused.begin();
    while (!unused.empty()) { // While there are any pixels inside.
        auto pend=furthest_point(pstart, img, unused, threshold);
        if (pstart==pend) {
            // If not already used...
            std::pair &lt; int,int &gt; line={pstart,pend};
            lines.push_back(line);
            // Restore all visited nodes.
            move_elem(visited, unused);
            // Unused to used.
            move_elem(unused, used, pstart);
            // Take next.
            if (!unused.empty()) pstart=*(unused.begin());
        } else {
            // Try to match the line.
            auto line=match_line(pstart,pend, img, unused, threshold);
            if (line.empty()) { // No match -&gt; remove end and retry.
                move_elem(unused, visited, pend);
            } else { // Yuuperoo. A match.
                // Push line to lines.
                lines.push_back(std::make_pair(pstart,pend));
                // Restore all visited nodes.
                move_elem(visited, unused);
                // Move all matched pixels to used.
                move_elem(unused,used,line);
                // Take next.
                if (!unused.empty()) pstart=*(unused.begin());
            }
        }
    }
    // Return result.
    return lines;
}
</pre><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h1 id="conclusion">Conclusion</h1>
<p>Our vectorization algorithms are now complete. To convert an image into lines, we can utilize both algorithms sequentially.</p>
<pre lang="c">
// Source image.
ga::image src=ga::image(&quot;my-image.png&quot;);
// Extract blobs.
auto result=ga::blobs(src, 32);
// Now convert each blob to a vector of lines.
int i=0;
for(auto blob:result) {
    // NOTE: each line is represented by two offsets (integers)
    //       into the raw bpp image.
    auto lines=ga::vectorize(src,blob,32);
}
</pre>
<p>While this approach is functional, it is not currently optimized. Let&apos;s conclude this article by highlighting two weaknesses that we will address in the next part, where we will delve into graph theory to generate optimal drawing paths for glyphs on the Iskra Delta Partner.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="connect-detected-lines">Connect detected lines</h2>
<p>Consider the following scenario: on the left, you have the original image, while on the right, you can see the result of our vectorization process, which has detected two lines.</p>
<p><img src="https://www.oddbit-retro.org/content/images/2023/11/cont.png" alt="cont" loading="lazy"></p>
<p>Our vectorization correctly identified two lines, but drawing them involves four graphical commands. The first command moves to the position (1,1), the next one draws a line to (1,6), followed by a move to (2,6), and finally, another command to draw the line to (6,6). Since we already know that the line continues from (1,6) to (6,6), the move operation to (2,6) is unnecessary. We could simply extend the horizontal line by one pixel, saving one graphical command.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="optimize-intersections">Optimize intersections</h2>
<p>Because our algorithm keeps track of previously visited pixels, it does not handle line intersections correctly, as demonstrated in the images below:</p>
<p><img src="https://www.oddbit-retro.org/content/images/2023/11/cross.png" alt="cross" loading="lazy"></p>
<p>The first image illustrates two intersecting lines. In the second image, you can observe the output of our algorithm: three non-intersecting lines. The final image displays the optimal result.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Building a Galaksija Computer]]></title><description><![CDATA[<p>I recently got my hands on a Galaksija kit from the official website (<a href="https://galaksija.org.rs/">https://galaksija.org.rs</a>). Since I was in Serbia soon after, I met with Aleksandar in Novi Sad to pick it up in person. This coincided with our trip to Belgrade, where we were visiting old friends.</p>]]></description><link>https://www.oddbit-retro.org/building-a-galaksija-computer/</link><guid isPermaLink="false">653d26c2c5ce840001925b5e</guid><dc:creator><![CDATA[Miha Grčar]]></dc:creator><pubDate>Sat, 28 Oct 2023 15:52:01 GMT</pubDate><content:encoded><![CDATA[<p>I recently got my hands on a Galaksija kit from the official website (<a href="https://galaksija.org.rs/">https://galaksija.org.rs</a>). Since I was in Serbia soon after, I met with Aleksandar in Novi Sad to pick it up in person. This coincided with our trip to Belgrade, where we were visiting old friends.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/10/galaksija-kit.jpg" class="kg-image" alt loading="lazy" width="1536" height="2048" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/10/galaksija-kit.jpg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/10/galaksija-kit.jpg 1000w, https://www.oddbit-retro.org/content/images/2023/10/galaksija-kit.jpg 1536w" sizes="(min-width: 720px) 720px"><figcaption>Galaksija kit.</figcaption></figure><p>Surprisingly, I managed to persuade my daughter to help me assemble the kit. She was even more pumped about it than I was. The assembly itself was fairly straightforward, taking just a couple of hours, thanks to the well-structured instructions. However, our Galaksija didn&apos;t show up on the TV screen when we turned it on for the first time.</p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="200" height="113" src="https://www.youtube.com/embed/wQM24qHlzP4?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Building a Galaksija, Part 1"></iframe><figcaption>Building our Galaksija, part 1.</figcaption></figure><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="200" height="113" src="https://www.youtube.com/embed/WOB8BlCrENE?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Building a Galaksija, Part 2"></iframe><figcaption>Building our Galaksija, part 2.</figcaption></figure><p>As with any DIY project, things don&apos;t always go smoothly. After some troubleshooting and tinkering, I figured out that I needed to tweak resistor R9 and swap out integrated circuits U2 (74LS04) and U3 (74LS74). The reasons behind these modifications remained a puzzle for me, and we left it at that, at least for the time being. Nevertheless, a closer look at the original building instructions (<a href="http://www.voja.rs/galaksija/0102.htm">http://www.voja.rs/galaksija/0102.htm</a>) does reveal a passage connecting R9 to image synchronization.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/10/IMG_6235.jpg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/10/IMG_6235.jpg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/10/IMG_6235.jpg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/10/IMG_6235.jpg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/10/IMG_6235.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Almost there...</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/10/IMG_6240.jpg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/10/IMG_6240.jpg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/10/IMG_6240.jpg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/10/IMG_6240.jpg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/10/IMG_6240.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>There!</figcaption></figure><p>The Galaksija kit offered a mix of nostalgia, technical challenges, and a chance to explore together with my daughter. While the modifications remain a puzzle, it&apos;s a challenge I&apos;m open to revisiting in the future. If you&apos;re looking for a hands-on journey into the world of retro computing, a project like this could be just what you need.</p>]]></content:encoded></item><item><title><![CDATA[How AI Helped Us Recreate the Iskra Delta Partner Motherboard]]></title><description><![CDATA[<p>There is a certain kind of magic in old computers. Not just nostalgia, but something more fundamental. It is like peeling back the layers of a time when computing still felt like exploration.</p><p>A while ago, we decided to bring back the Iskra Delta Partner, a Slovene computer from the</p>]]></description><link>https://www.oddbit-retro.org/how-ai-helped-us-recreate-the-idp-motherboard/</link><guid isPermaLink="false">68b32cf8e9fee00001887278</guid><dc:creator><![CDATA[Miha Grčar]]></dc:creator><pubDate>Sun, 13 Aug 2023 12:00:00 GMT</pubDate><content:encoded><![CDATA[<p>There is a certain kind of magic in old computers. Not just nostalgia, but something more fundamental. It is like peeling back the layers of a time when computing still felt like exploration.</p><p>A while ago, we decided to bring back the Iskra Delta Partner, a Slovene computer from the 1980s. Most people have never heard of it, and that is partly the point. It was not famous. But it was ours. And like all forgotten machines, it had something to teach us.</p><p>Our goal was ambitious. We wanted to replicate the motherboard exactly. Not emulate, not approximate. Recreate it, down to the last via. That turned out to be more work than it sounds. The board had more than a thousand vias, which are the tiny copper bridges that connect different layers of the circuit. Placing them manually in <a href="https://www.kicad.org/">KiCad</a> quickly became a grind. The kind of work that makes you question why you started in the first place.</p><p>So we did what any curious programmer would do. We automated it.</p><h2 id="the-usual-way">The Usual Way</h2><p>Normally, you take a scan of the board, load it into KiCad, and begin tracing. You connect the pads, draw the tracks, and slowly add each via by hand. It is tedious in the way that sanding wood with a toothbrush is tedious. Not difficult, just slow and repetitive. And when you are doing it more than a thousand times, it starts to feel less like engineering and more like penance.</p><p>After a few hours, I realized I was spending all my time searching for tiny, identical holes and placing them one by one. It was detailed work, but not intellectually demanding. My focus would drift while my hand kept moving. It felt inefficient to do something so purely visual by hand, especially when I had a computer nearby that could probably be taught to do it better.</p><h2 id="using-computer-vision">Using Computer Vision</h2><p>We started with <a href="https://opencv.org/">OpenCV</a>. It is a solid library filled with useful tools for image processing. On our first scanned image, it worked surprisingly well. The algorithm identified the vias with decent accuracy. It felt like a small miracle.</p><p>Then we tried a different image. And it stopped working.</p><p>The problem was simple but hard to avoid. The lighting, contrast, and resolution change from one image to another. What worked in one case completely failed in another. So we set OpenCV aside and built our own solution.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2025/08/image--16-.png" class="kg-image" alt loading="lazy" width="720" height="598" srcset="https://www.oddbit-retro.org/content/images/size/w600/2025/08/image--16-.png 600w, https://www.oddbit-retro.org/content/images/2025/08/image--16-.png 720w" sizes="(min-width: 720px) 720px"><figcaption>Initial OpenCV-based detection attempt. Unfortunately, this initial scan was too distorted to fit onto the KiCad grid. As a result, we had to create a different type of scan, which did not work as effectively with the via recognition algorithm. (Credit: Toma&#x17E; &#x160;tih)</figcaption></figure><h2 id="the-algorithm-it-is-simpler-than-you-think">The Algorithm (It Is Simpler Than You Think)</h2><p>At the core, our algorithm is a moving window. We drag a small square across the entire image and ask, &quot;Does this square contain a via?&quot;</p><p>To answer that, we describe the contents of each square using a feature vector with histogram-like features. It is like turning the square into a list of numbers that summarize what it looks like. Then we compare that list to examples of known vias. We only needed to mark around ten of them by hand. That was enough.</p><p>We measure the similarity using cosine similarity. If the square looks enough like the examples, we mark it as a via. It worked. In one run, we found around 970 out of 1000 vias. Automatically. In minutes.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2025/08/front-300dpi-cv.png" class="kg-image" alt loading="lazy" width="2000" height="1626" srcset="https://www.oddbit-retro.org/content/images/size/w600/2025/08/front-300dpi-cv.png 600w, https://www.oddbit-retro.org/content/images/size/w1000/2025/08/front-300dpi-cv.png 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2025/08/front-300dpi-cv.png 1600w, https://www.oddbit-retro.org/content/images/2025/08/front-300dpi-cv.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Grid-aligned scan with recognized vias. A cleaner, grid-aligned scan allowed the custom algorithm to detect and place around 970 vias accurately.</figcaption></figure><h2 id="the-unexpected-improvement">The Unexpected Improvement</h2><p>The biggest boost did not come from tweaking the similarity metric or adding more training data. It came from doing something much simpler.</p><p>We told the algorithm where not to look.</p><p>We created what we call a tabu mask. It is a kind of map that marks off areas where vias cannot be. We built it from the traces in the image. When we excluded those areas from the search, the accuracy improved dramatically. The process became faster too.</p><p>Removing noise is just as important as finding signal.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2025/08/mask-300dpi.png" class="kg-image" alt loading="lazy" width="2000" height="1626" srcset="https://www.oddbit-retro.org/content/images/size/w600/2025/08/mask-300dpi.png 600w, https://www.oddbit-retro.org/content/images/size/w1000/2025/08/mask-300dpi.png 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2025/08/mask-300dpi.png 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2025/08/mask-300dpi.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Tabu mask used for the final result. This mask was created by extracting the traces from the scan and converting them into a single one-bit image. It told the algorithm where not to search for vias, improving accuracy and speed.</figcaption></figure><h2 id="why-this-matters">Why This Matters</h2><p>This project was never about publishing a paper or building a product. It was about solving an interesting problem. We wanted to bring a computer back to life. And along the way, we found ourselves solving a tiny computer vision problem that no one else cared about. That is what made it fun.</p><p>In the end, the code worked. It helped us finish the board faster, with less frustration. And we felt something that is increasingly rare in modern tech: satisfaction. Not from likes or retweets or investor interest. Just from the quiet feeling of making something work.</p><h2 id="try-it-yourself">Try It Yourself</h2><p>We have published <a href="https://github.com/OddbitRetro/IdpViaAI">the code</a>. It is all in <a href="https://github.com/OddbitRetro/IdpViaAI/blob/master/ViaCV/Program.cs">one file</a>. If you know basic machine learning, you will understand it. It is not fancy. It is not the latest thing. But it works.</p><p>If you are interested in more advanced methods, there are neural networks that can detect anything in any image with remarkable accuracy. But if you just want to find a thousand dots on a circuit board scan, this is probably enough.</p><p>And most of the time, enough is exactly what you need.</p><h2 id="resources">Resources</h2><ul><li><a href="https://github.com/OddbitCoder/IdpViaAI">Project GitHub repository</a></li></ul><h2 id="read-also">Read Also</h2><ul><li><a href="https://www.oddbit-retro.org/recreating-the-iskra-delta-partner-motherboard/">Recreating the IDP Motherboard</a></li></ul>]]></content:encoded></item><item><title><![CDATA[Transforming Full-Colored Sprites into 1-Bit Per Pixel Sprites]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>The Iskra Delta Partner computer supports vector graphics and has the capability to draw green lines on a black background.</p>
<p>When porting a game from an 8-bit microcomputer with full colors to the Iskra Delta Partner, you have the option to manually create sprites based on color sprites. Nevertheless, there</p>]]></description><link>https://www.oddbit-retro.org/from/</link><guid isPermaLink="false">64c14726c5ce840001925af7</guid><dc:creator><![CDATA[Tomaz Stih]]></dc:creator><pubDate>Wed, 26 Jul 2023 16:51:53 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>The Iskra Delta Partner computer supports vector graphics and has the capability to draw green lines on a black background.</p>
<p>When porting a game from an 8-bit microcomputer with full colors to the Iskra Delta Partner, you have the option to manually create sprites based on color sprites. Nevertheless, there are instances where it&apos;s feasible to automatically or semi-automatically convert them. This text elaborates on one such technique.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>In this project, we will be working with a sprite sheet obtained from the internet, featuring various sprites from the game of Karate. These sprites showcase several dominant colors, including both light and dark shades, such as red, black, white, gray, and two skin tones.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://www.oddbit-retro.org/content/images/2023/07/karate-original.png" class="kg-image" alt loading="lazy" width="648" height="632" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/07/karate-original.png 600w, https://www.oddbit-retro.org/content/images/2023/07/karate-original.png 648w"></figure><!--kg-card-begin: markdown--><p>Our goal is to simplify the image by creating two distinct groups of colors: one for the light colors, which includes white and skin tones, and the other for the darker colors, encompassing red and black. Through a filtering process, we will selectively remove all colors except those we group into the white and black categories. As a result, we will obtain multiple images, each containing only the elements corresponding to the colors in the selected group. All other colors will be removed from these images.</p>
<p>Let us now commence with our transformation process.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="1-outline-sprites">1. Outline Sprites</h2>
<p>The initial step involves locating the bounds of each sprite and outlining it. We achieve this by employing blob detection and subsequently determining the sprite polygon.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://www.oddbit-retro.org/content/images/2023/07/karate-1.png" class="kg-image" alt loading="lazy" width="648" height="632" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/07/karate-1.png 600w, https://www.oddbit-retro.org/content/images/2023/07/karate-1.png 648w"></figure><!--kg-card-begin: markdown--><h2 id="2-isolating-the-black-group">2. Isolating the Black Group</h2>
<p>To proceed, we will now isolate the black color group (comprising red and black colors) as polygons and proceed to fill them.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://www.oddbit-retro.org/content/images/2023/07/karate-overlay.png" class="kg-image" alt loading="lazy" width="648" height="632" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/07/karate-overlay.png 600w, https://www.oddbit-retro.org/content/images/2023/07/karate-overlay.png 648w"></figure><!--kg-card-begin: markdown--><h2 id="3-isolating-the-white-group">3. Isolating the White Group</h2>
<p>We will proceed to isolate the polygons belonging to the white color group and then outline them.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://www.oddbit-retro.org/content/images/2023/07/karate-overlay-2-1.png" class="kg-image" alt loading="lazy" width="648" height="632" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/07/karate-overlay-2-1.png 600w, https://www.oddbit-retro.org/content/images/2023/07/karate-overlay-2-1.png 648w"></figure><!--kg-card-begin: markdown--><h2 id="4-merge-image-and-overlays">4. Merge Image and Overlays</h2>
<p>Now, let&apos;s merge the outline and the two overlay images to create the final image.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://www.oddbit-retro.org/content/images/2023/07/karate-full-1.png" class="kg-image" alt loading="lazy" width="648" height="632" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/07/karate-full-1.png 600w, https://www.oddbit-retro.org/content/images/2023/07/karate-full-1.png 648w"></figure><!--kg-card-begin: markdown--><p>And there you have it &#x2013; the sprites are now converted. If you had a raster, you could also utilize hatch lines (patterns like 10101010) to create additional group colors. However, for the Iskra Delta Partner, we only used two groups because drawing other patterns would be too slow. Wishing you a happy converting experience!</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://www.oddbit-retro.org/content/images/2023/07/trees-single.png" class="kg-image" alt loading="lazy" width="313" height="295"></figure>]]></content:encoded></item><item><title><![CDATA[Retro Game Programming Tips, Part 1: The Game Clock]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Retro computer games are characterized by their vivid visuals, featuring flying spaceships, blinking rocket thrusts, falling meteors, bullets soaring through the air, and explosions happening all around. The elements in these games can move at various speeds, and while the effects are temporary, the gameplay itself remains constant.</p>
<p>In this</p>]]></description><link>https://www.oddbit-retro.org/retro-game-programming-tips-part-1-the-game-clock/</link><guid isPermaLink="false">64bd5c47c5ce840001925a69</guid><dc:creator><![CDATA[Tomaz Stih]]></dc:creator><pubDate>Sun, 23 Jul 2023 17:55:50 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Retro computer games are characterized by their vivid visuals, featuring flying spaceships, blinking rocket thrusts, falling meteors, bullets soaring through the air, and explosions happening all around. The elements in these games can move at various speeds, and while the effects are temporary, the gameplay itself remains constant.</p>
<p>In this article, we&apos;ll explore a simple trick to synchronize all these elements using a straightforward game clock. Let&apos;s begin by defining the clock.<br>
<br></p>
<pre lang="c">
#define MAX_CYCLES 10
int clock = MAX_CYCLES;
bool game_over = false;

void main()
{
    while (!game_over)
    {
        if (!--clock)
            clock = MAX_CYCLES;
    }
}
</pre><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>In the provided code, the clock serves as a straightforward counter that counts down to zero and resets once it reaches that point. If you have two sprites, A and B, and sprite A needs to move twice as fast as sprite B, the simplest solution is to move sprite A every clock cycle and sprite B every second clock cycle. Here&apos;s how it works:<br>
<br></p>
<pre lang="c">
#define MAX_CYCLES 10
int clock = MAX_CYCLES;
bool game_over = false;

void main()
{
    while (!game_over)
    {
        printf(&quot;Move A\n&quot;);
        if (clock &amp; 1) printf(&quot;Move B\n&quot;);
        if (!--clock)
            clock = MAX_CYCLES;
    }
}
</pre><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>Great job! You&apos;ve successfully created your first game synchronization mechanism. It&apos;s quite simple, but not very versatile. Now, let&apos;s enhance its flexibility by providing a separate clock counter for each sprite and/or event that requires synchronization.</p>
<p>To achieve this, we&apos;ll use a typedef structure called &quot;clk_t,&quot; which includes the following components:</p>
<ul>
<li><strong>ncycles</strong>: The total number of cycles for the clock.</li>
<li><strong>cycle</strong>: The current cycle count for the clock.</li>
<li><strong>action</strong>: A function pointer that specifies the action to be performed.</li>
</ul>
<p>Additionally, we&apos;ll create an array of pointers to these clocks, allowing us to manage multiple clocks simultaneously. We&apos;ll set a predefined value, <code>NUM_CLOCKS</code>, to determine the maximum number of clocks available in the system.</p>
<p>Here&apos;s the updated code:<br>
<br></p>
<pre lang="c">
typedef struct clk_s
{
    int ncycles;
    int cycle;
    void (*action)();
} clk_t;

/* create an array of pointers to clocks*/
#define NUM_CLOCKS 10
clk_t *clocks[NUM_CLOCKS];

/* initialize all clocks to NULL */
void clk_init()
{
    for (int i = 0; i &lt; NUM_CLOCKS; i++)
        clocks[i] = NULL;
}
</pre><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>Now that we have the initial data structures in place, let&apos;s proceed to create functions for managing the clocks. We need to implement functions to create a new clock and add it to the clocks array, destroy a clock and remove it from the array, and also update the clock ticks.</p>
<p>Here&apos;s the code:<br>
<br></p>
<pre lang="c">
static int _clk_find(clk_t *c)
{
    for (int i = 0; i &lt; NUM_CLOCKS; i++)
        if (clocks[i] == c)
            return i;
    return -1;
}

clk_t *clk_create(int ncycles, void (*action)())
{
    /* find empty slot */
    int pos = _clk_find(NULL);
    if (pos &gt;= 0)
    {
        /* allocate clock */
        clk_t *clk = malloc(sizeof(clk_t));
        clk-&gt;cycle = clk-&gt;ncycles = ncycles;
        clk-&gt;action = action;
        /* and add ptr to array */
        clocks[pos] = clk;
        /* return pointer */
        return clk;
    }
    /* not found! */
    return NULL;
}

void clk_destroy(clk_t *c)
{
    int pos=_clk_find(c);
    if (pos &gt;= 0)
        clocks[pos] = NULL;
    free(c);
}

void clk_tick() {
    for (int i = 0; i &lt; NUM_CLOCKS; i++) {
        if (clocks[i]) { /* not NULL */
            clocks[i]-&gt;cycle--; /* reduce cycle */
            if (!(clocks[i]-&gt;cycle)) {
                clocks[i]-&gt;action();
                clocks[i]-&gt;cycle=clocks[i]-&gt;ncycles;
            }
        }
    }
}
</pre><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>With these functions, you can effortlessly create and manage multiple clocks within your retro game. Each clock can be assigned its specific cycle count and associated action, enabling you to synchronize various game elements and events seamlessly. Let&apos;s see how the main program would utilize these functions:<br>
<br></p>
<pre lang="c">
void move_a() { printf (&quot;Move A\n&quot;); }
void move_b() { printf (&quot;Move B\n&quot;); }
int game_over=100;

void main() {
    clk_init();
    clk_t* a=clk_create(1,&amp;move_a);
    clk_t* b=clk_create(2,&amp;move_b);
    while(!game_over--)
        clk_tick();
    clk_destroy(a);
    clk_destroy(b);
}
</pre><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>Now, we have separate clocks for each event, and they are all synchronized with the same game clock. <strong>This forms the fundamental concept behind the game clock.</strong> Of course, on your platform, you have the option to write these functions in assembly instead of C, which would make them even faster. After all, this code will be responsible for controlling every particle of an exploding object on the screen.</p>
<p>Let&apos;s consider a scenario where we are developing the <em>Moon Lander</em> game, and we want to apply gravity to a sprite. Consequently, with each turn, the lander will fall faster and faster due to the applied gravity. As the lander&apos;s movement is governed by the clock, the clock must adapt accordingly. So, let&apos;s include a function that allows us to modify the clock.<br>
<br></p>
<pre lang="c">
void clk_change(clk_t *c, int ncycles2) {
    c-&gt;ncycles2=ncycles2;
}
</pre><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>But wait?! What&apos;s that ncycles2? This represents a pending change of the clock period. By adding this variable, we ensure that if the clock period changes in the middle of a cycle, the current cycle will complete before switching to the new clock period.</p>
<p>For this mechanism to function correctly, we need to modify the clk_t type and the functions responsible for creating clocks and handling clock ticks.</p>
<p>Here&apos;s the updated code:<br>
<br></p>
<pre lang="c">
typedef struct clk_s
{
    int ncycles;
    int ncycles2;
    int cycle;
    void (*action)(game_context_t *ctx);
} clk_t;

clk_t *clk_create(int ncycles, void (*action)(game_context_t *ctx))
{
    /* Find an empty slot in the array */
    int pos = _clk_find(NULL);
    if (pos &gt;= 0)
    {
        /* Allocate memory for the new clock */
        clk_t *clk = malloc(sizeof(clk_t));
        clk-&gt;cycle = clk-&gt;ncycles = clk-&gt;ncycles2 = ncycles;
        clk-&gt;action = action;

        /* Add the pointer to the clocks array */
        clocks[pos] = clk;

        /* Return the pointer to the new clock */
        return clk;
    }
    return NULL;
}

void clk_tick(game_context_t *ctx)
{
    for (int i = 0; i &lt; NUM_CLOCKS; i++)
    {
        if (clocks[i]) /* Check if the clock is not NULL */
        {
            clocks[i]-&gt;cycle--; /* Reduce the cycle count */

            if (!(clocks[i]-&gt;cycle)) /* Cycle reached 0? */
            {
                clocks[i]-&gt;action(ctx);
                clocks[i]-&gt;cycle = clocks[i]-&gt;ncycles = clocks[i]-&gt;ncycles2; 
            }
        }
    }
}
</pre><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>Finally, we&apos;ve added a pointer to the <strong>game context</strong> in all functions. This context represents the current state of the game, holding information about objects&apos; positions, draw status, game logic, and more. Each function called by the game clock receives this context and can modify it accordingly. Here&apos;s the complete code for our game clock:<br>
<br></p>
<pre lang="c">
typedef struct game_context_s {
    bool game_over;
} game_context_t;

typedef struct clk_s
{
    int ncycles;
    int ncycles2;
    int cycle;
    void (*action)(game_context_t *ctx);
} clk_t;

/* create an array of pointers to clocks*/
#define NUM_CLOCKS 10
clk_t *clocks[NUM_CLOCKS];

/* initialize all clocks to NULL */
void clk_init()
{
    for (int i = 0; i &lt; NUM_CLOCKS; i++)
        clocks[i] = NULL;
}

int _clk_find(clk_t *c)
{
    for (int i = 0; i &lt; NUM_CLOCKS; i++)
        if (clocks[i] == c)
            return i;
    return -1;
}

clk_t *clk_create(int ncycles, void (*action)())
{
    /* find empty slot */
    int pos = _clk_find(NULL);
    if (pos &gt;= 0)
    {
        /* allocate clock */
        clk_t *clk = malloc(sizeof(clk_t));
        clk-&gt;cycle = clk-&gt;ncycles = clk-&gt;ncycles2 = ncycles;
        clk-&gt;action = action;
        /* and add ptr to array */
        clocks[pos] = clk;
        /* return pointer */
        return clk;
    }
    /* not found! */
    return NULL;
}

void clk_destroy(clk_t *c)
{
    int pos=_clk_find(c);
    if (pos &gt;= 0)
        clocks[pos] = NULL;
    free(c);
}

void clk_tick(game_context_t *ctx) {
    for (int i = 0; i &lt; NUM_CLOCKS; i++) {
        if (clocks[i]) { /* not NULL */
            clocks[i]-&gt;cycle--; /* reduce cycle */
            if (!(clocks[i]-&gt;cycle)) { /* cycle reached 0? */
                clocks[i]-&gt;action(ctx);
                clocks[i]-&gt;cycle=clocks[i]-&gt;ncycles=clocks[i]-&gt;ncycles2;
            }
        }
    }
}

void clk_change(clk_t *c, int ncycles2) {
    c-&gt;ncycles2=ncycles2;
}

void move_a(game_context_t *ctx) { printf (&quot;Move A\n&quot;); }
void move_b(game_context_t *ctx) { printf (&quot;Move B\n&quot;); }

game_context_t gc={false};

int j=0;

void main() {
    clk_init();
    clk_t* a=clk_create(1,&amp;move_a);
    clk_t* b=clk_create(3,&amp;move_b);
    while(!gc.game_over) {
        clk_tick(&amp;gc);
        j++;
        if (j==10) clk_change(b,1);
        if (j==20) gc.game_over=true;
    }
    clk_destroy(a);
    clk_destroy(b);
}
</pre><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>We&apos;ve developed a simple yet powerful framework. However, up until now, we&apos;ve only used it for theoretical cases. To demonstrate its practical application, here&apos;s a code fragment that shows how a game of Froggy could look using our framework:<br>
<br></p>
<pre lang="c">
/* Frogger */

/* Create the game context */
game_context_t *gc = gc_create();

/* Clocks for various game events */
clk_create(1, kbd_handler);  /* Scan the keyboard every tick */
clk_create(1, dead_frog);    /* Check if the frog is dead every tick */
clk_create(3, move_row1);    /* Move the first row every 3 cycles */
clk_create(5, move_row2);    /* Move the second row every 5 cycles */
clk_create(3, move_row3);    /* Move the third row every 3 cycles */
clk_create(5, move_row4);    /* Move the fourth row every 5 cycles */

/* Main game loop */
while (!gc-&gt;game_over) {
    clk_tick(gc);
    update_screen(gc);
}
</pre><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>And that&apos;s all there is to it. Now, all you need to do is write event handlers for each specific event, and optimize the screen update function for smooth gameplay. With this straightforward code structure and our efficient framework, you&apos;re well-equipped to develop a captivating game like Froggy. Happy coding!</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p><a href="https://www.oddbit-retro.org/retro-game-programming-tips-part-2-page-switching/">Retro Game Programming Tips, Part 2: Double Buffering with Page Switching</a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Fixing an Iskra Delta Partner Computer From 1987]]></title><description><![CDATA[<p>In the realm of vintage computing, uncovering the secrets behind a faulty machine can be an exhilarating journey. Recently, I had the opportunity to delve into the inner workings of an Iskra Delta Partner (IDP) computer from 1987, which was plagued by random crashes and an endless boot-up loop. In</p>]]></description><link>https://www.oddbit-retro.org/fixing-an-iskra-delta-partner/</link><guid isPermaLink="false">6486e1a9c5ce840001925745</guid><dc:creator><![CDATA[Miha Grčar]]></dc:creator><pubDate>Mon, 12 Jun 2023 13:21:55 GMT</pubDate><content:encoded><![CDATA[<p>In the realm of vintage computing, uncovering the secrets behind a faulty machine can be an exhilarating journey. Recently, I had the opportunity to delve into the inner workings of an Iskra Delta Partner (IDP) computer from 1987, which was plagued by random crashes and an endless boot-up loop. In this post, I share my adventure of troubleshooting, measurements, and ultimately, the revival of this fairly unknown and rare computer produced in Slovenia.</p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="200" height="113" src="https://www.youtube.com/embed/5FvZQEKykdU?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="IDP - broken"></iframe><figcaption>IDP exhibiting erratic behavior on boot-up. During the memory-test routine, the computer &quot;jumped&quot; out of its regular code execution course and displayed the boot-up prompt [which failed to receive user input].</figcaption></figure><p>To unravel the root cause of the erratic behavior, I opted to examine the EPROM code. Since the normal execution of the code alone couldn&apos;t account for the observed output, I began suspecting that interrupts were being triggered, diverting the code from its regular sequence. As I delved deeper into the technicalities, I discovered that the Z80 architecture incorporates two pins, INT and NMI (which receive active-low signals), through which other devices or integrated circuits (ICs) can initiate interrupts.</p><p>To identify the source of the problem, I embarked on a meticulous comparison between a functioning machine and the faulty Iskra Delta Partner. Armed with a voltmeter, I measured the INT and NMI CPU inputs. Prior to these measurements, I removed the EPROM and the CTC (Counter/Timer Circuit) from their sockets to minimize the number of variables involved.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/06/ima_de52b33.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/06/ima_de52b33.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/06/ima_de52b33.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/06/ima_de52b33.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/06/ima_de52b33.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>A &quot;minimalistic&quot; environment for debugging and fixing an IDP. The primary objective was to get rid of the original monitor and PSU, which were quite weighty at approximately 100 kilograms (well, not exactly, but sure felt like it).</figcaption></figure><p>A critical discovery emerged as I observed the voltmeter readings. On the good machine, both NMI and INT exhibited high voltage levels, indicating normal operation. However, on the problematic computer, the INT pin registered consistently low voltage levels. This discrepancy shed light on the potential cause of the computer&apos;s malfunctions.</p><p>With the INT pin identified as the culprit, I proceeded to analyze the schematics of the Iskra Delta Partner. My objective was to locate any ICs or components that could potentially be responsible for pulling the INT signal down. The overall plan was to methodically remove these components from the board, one by one, in order to observe if the faulty state persisted. Fortunately, this step turned out to be unnecessary. </p><p>Upon conducting further measurements, I uncovered that one of the logical gates responsible for transmitting the DMA interrupt signal to the CPU was faulty. It was a logical inverter 74LS04. This IC was incorrectly converting a 4.5V input to a 2.2V output, which should have been much lower, ranging from 0 to 0.4V.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/06/image-6.png" class="kg-image" alt loading="lazy" width="2000" height="1125" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/06/image-6.png 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/06/image-6.png 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/06/image-6.png 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/06/image-6.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>The important part of the schematics. The grayed-out ICs were not present on the board during the readings (CTC was removed, the others were not installed at all). The green readings represent the expected values, while the red ones indicate incorrect values. The 2.2V reading reveals that the inverter (74LS04) is faulty.</figcaption></figure><p>With no better alternative in sight, I proceeded to replace the defective IC. Upon doing so, the INT pin on the CPU sprang back to high voltage levels. Filled with hope, I re-inserted the ICs (CTC and EPROM) and eagerly rebooted the board.</p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="200" height="113" src="https://www.youtube.com/embed/kZ_TPwvTcqk?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="IDP - fixed"></iframe><figcaption>IDP works again.</figcaption></figure><p>In a moment of sheer joy, the Iskra Delta Partner computer booted up flawlessly. The random crashes and boot loop were but distant memories, replaced by the smooth operation of a fully functional vintage machine. It was a testament to the power of investigation, measurement, and perseverance.</p><h2 id="acknowledgements">Acknowledgements</h2><p>This text is (mostly) written by ChatGPT. It kind of sucks, but I rather spend my time fixing/using retro computers than typing these texts. Life is relatively short, you know.</p>]]></content:encoded></item><item><title><![CDATA[Reviving an Iskra Delta Trident Computer (Triglav)]]></title><description><![CDATA[<p><br>A year or two ago, I came across a Trident computer being sold by a collector in Bosnia. The computer was in a less-than-ideal condition, missing crucial components like the PSU, hard disk, floppy disk, RAM board, and operating system (or any kind of software for that matter). Undeterred by</p>]]></description><link>https://www.oddbit-retro.org/booting-up-triglav-trident/</link><guid isPermaLink="false">6475deb7c5ce8400019254c8</guid><dc:creator><![CDATA[Miha Grčar]]></dc:creator><pubDate>Tue, 06 Jun 2023 12:45:29 GMT</pubDate><content:encoded><![CDATA[<p><br>A year or two ago, I came across a Trident computer being sold by a collector in Bosnia. The computer was in a less-than-ideal condition, missing crucial components like the PSU, hard disk, floppy disk, RAM board, and operating system (or any kind of software for that matter). Undeterred by the challenge it presented, I recently decided to embark on the seemingly impossible mission of bringing this Trident back to life. In the lot, there was also a Paka terminal that was used to operate this Trident back in the day. My plan was to tackle the terminal and computer separately, using a modern PC as a makeshift terminal during the restoration process.</p><h2 id="chapter-1-the-terminal">Chapter 1: The Terminal</h2><p>The Paka terminal, just as every computer terminal, serves as a communication bridge between the user and the computer. To my relief, the Paka appeared to be in decent shape. I powered it on, only to be greeted by the letter &quot;H&quot; on the screen, indicating a missing keyboard.</p><p>Fortunately, I managed to strike a deal with a fellow retro enthusiast, and obtained a really nice Paka keyboard. I connected the keyboard, hoping for a fully functional terminal. However, I encountered stability issues with the display, and eventually, the terminal froze. After some investigation, I discovered that the PSU was to blame.</p><p>To overcome this hurdle, I decided to connect the terminal to a modern ATX PSU, with an additional power supply for the 20V line. Miraculously, this resolved the display instability, and the terminal and keyboard appeared to be working well together. While I was eager to connect the terminal to the computer, I knew I needed a fully functional machine first. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/05/IMG_6860--1-.jpg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/05/IMG_6860--1-.jpg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/05/IMG_6860--1-.jpg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/05/IMG_6860--1-.jpg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/05/IMG_6860--1-.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>The terminal and the keyboard powered by a modern PSU.</figcaption></figure><h2 id="chapter-2-the-computer">Chapter 2: The Computer</h2><p>The next step in this restoration journey was to power up the Trident computer. With all cards removed, I carefully connected the empty backplane to a power supply. To facilitate this, I devised a cable that connected the Trident to an ATX PSU, albeit without certain signals provided by the original PSU.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/05/psu_connector-2.png" class="kg-image" alt loading="lazy" width="2000" height="928" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/05/psu_connector-2.png 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/05/psu_connector-2.png 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/05/psu_connector-2.png 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/05/psu_connector-2.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>The original PSU connector pinout, from two different sources. Apart from the main voltages (5V and 12V), the following lines are connected to the computer: ACFAIL, SYSRESET, and -12V (SENSE is connected directly to 5V and GND, KEYSW and -5V are connected only to the keylock). The only seemingly relevant signal that I was unable to get from a modern ATX PSU is SYSRESET. I decided to simply ignore it (shrug).</figcaption></figure><p>With the backplane connected and no immediate signs of catastrophe, I inserted the J11 board (CPU) and powered on the computer. Hoping for a glimmer of success, I observed the screen of my PC, which served as a makeshift terminal.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/06/ima_4db7e58.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/06/ima_4db7e58.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/06/ima_4db7e58.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/06/ima_4db7e58.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/06/ima_4db7e58.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>The cable that connects the Trident to an ATX PSU.</figcaption></figure><p>The terminal displayed a &quot;hello world&quot; message: &quot;IDC J11 boot [d: W0] (W0,W1,F0,F1,F2,F3) &gt;&quot;. It was a joyful moment, filled with excitement. Motivated by these promising signs of life, I proceeded to install the floppy/hard disk controller (&quot;FD/WD&quot;).</p><figure class="kg-card kg-embed-card"><iframe width="200" height="150" src="https://www.youtube.com/embed/J5K1gZEsesA?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="IDC Trident - first signs of life"></iframe></figure><p>(To be continued.)</p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="200" height="113" src="https://www.youtube.com/embed/SZELbcsHIIE?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="IDC Trident - boot"></iframe><figcaption>Trident boots up!</figcaption></figure><h2 id="resources">Resources</h2><h3 id="eprom-dumps">EPROM Dumps</h3><ul><li>Paka terminal board &quot;KLT-T&quot; ROMs: <a href="https://files.oddbit-retro.org/pub/Triglav/Dumps/Roms/Paka/">https://files.oddbit-retro.org/pub/Triglav/Dumps/Roms/Paka/</a></li><li>Paka keyboard ROM (&quot;0269-87&quot;): <a href="https://files.oddbit-retro.org/pub/Triglav/Dumps/Roms/Keyboard/">https://files.oddbit-retro.org/pub/Triglav/Dumps/Roms/Keyboard/</a></li><li>Triglav communication board (ICC) ROM: <a href="https://files.oddbit-retro.org/pub/Triglav/Dumps/Roms/ICC/">https://files.oddbit-retro.org/pub/Triglav/Dumps/Roms/ICC/</a></li><li>Triglav J11 CPU board: To do.</li></ul><h2 id="acknowledgements">Acknowledgements</h2><ul><li>To do.</li></ul>]]></content:encoded></item><item><title><![CDATA[Restoring Ines, a ZX Spectrum Keyboard]]></title><description><![CDATA[<p>I recently acquired two Ines keyboards from a collector in Zagreb. These keyboards were produced in the 80s by a Slovenian company called In&#x161;titut za elektroniko in vakuumsko tehniko (IEVT), which still exists today as <a href="https://www.tipro.si/">Tipro</a>. </p><p>I restored the first Ines keyboard and documented the process.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/04/ima_0cfe8be.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/04/ima_0cfe8be.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/04/ima_0cfe8be.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/04/ima_0cfe8be.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/04/ima_0cfe8be.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>The two</figcaption></figure>]]></description><link>https://www.oddbit-retro.org/restoring-ines-a-zx-spectrum-keyboard/</link><guid isPermaLink="false">64494abdc5ce840001925298</guid><dc:creator><![CDATA[Miha Grčar]]></dc:creator><pubDate>Wed, 26 Apr 2023 16:18:24 GMT</pubDate><content:encoded><![CDATA[<p>I recently acquired two Ines keyboards from a collector in Zagreb. These keyboards were produced in the 80s by a Slovenian company called In&#x161;titut za elektroniko in vakuumsko tehniko (IEVT), which still exists today as <a href="https://www.tipro.si/">Tipro</a>. </p><p>I restored the first Ines keyboard and documented the process.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/04/ima_0cfe8be.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/04/ima_0cfe8be.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/04/ima_0cfe8be.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/04/ima_0cfe8be.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/04/ima_0cfe8be.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>The two Ines keyboards in their previous home.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/04/ima_5699e6d.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/04/ima_5699e6d.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/04/ima_5699e6d.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/04/ima_5699e6d.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/04/ima_5699e6d.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>The work begins.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/04/ima_e923cc8.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/04/ima_e923cc8.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/04/ima_e923cc8.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/04/ima_e923cc8.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/04/ima_e923cc8.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Keycaps removed.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/04/ima_b2e46f4.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/04/ima_b2e46f4.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/04/ima_b2e46f4.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/04/ima_b2e46f4.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/04/ima_b2e46f4.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>One switch was broken. The spare switch was not fully compatible with this keyboard.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/04/ima_8c6b154.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/04/ima_8c6b154.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/04/ima_8c6b154.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/04/ima_8c6b154.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/04/ima_8c6b154.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>A compatible replacement switch was built from parts.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/04/ima_f946099.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/04/ima_f946099.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/04/ima_f946099.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/04/ima_f946099.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/04/ima_f946099.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>One faulty RAM chip was replaced. The Speccy was also fully recapped and the ULA heatsink was reattached.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/04/ima_69ac3fc--2-.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/04/ima_69ac3fc--2-.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/04/ima_69ac3fc--2-.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/04/ima_69ac3fc--2-.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/04/ima_69ac3fc--2-.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>A connector was added to supply power to the keyboard (originally, the keyboard power wires were soldered directly onto the voltage regulator).</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/04/ima_77139bc.jpeg" class="kg-image" alt loading="lazy" width="2000" height="2667" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/04/ima_77139bc.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/04/ima_77139bc.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/04/ima_77139bc.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/04/ima_77139bc.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Welcome back, Ines.</figcaption></figure>]]></content:encoded></item><item><title><![CDATA[Deciphering the RS-232 Port on Iskra Delta Partner]]></title><description><![CDATA[<p>Iskra Delta Partner is a series of personal computers that were manufactured in Yugoslavia in the 1980s. In this blog post, we will explore how a Partner (specifically, the &quot;GDP&quot; version) can be connected to a PC using a serial connection.</p><p>First, let&apos;s take a look</p>]]></description><link>https://www.oddbit-retro.org/exploring-the-rs-232-connection-on-the-iskra-delta-partner/</link><guid isPermaLink="false">64047204c5ce840001924e12</guid><dc:creator><![CDATA[Miha Grčar]]></dc:creator><pubDate>Wed, 22 Mar 2023 18:21:31 GMT</pubDate><content:encoded><![CDATA[<p>Iskra Delta Partner is a series of personal computers that were manufactured in Yugoslavia in the 1980s. In this blog post, we will explore how a Partner (specifically, the &quot;GDP&quot; version) can be connected to a PC using a serial connection.</p><p>First, let&apos;s take a look at how the I/O channels of Iskra Delta Partner are described in the <a href="https://files.oddbit-retro.org/pub/Partner/Docs/Manuals/PartnerGPrirocnikZaUporabnike.pdf">IDP User&apos;s Manual</a>. All versions of Partner come with at least one RS-232-C serial port called &quot;LPT,&quot; which is meant to connect a serial printer. This connector is supposed to be labeled &quot;J7&quot; on the back of the computer. Extension option 1 includes two more serial channels (which are the same as LPT): &quot;VAX,&quot; labeled as &quot;J8,&quot; and &quot;MOD,&quot; labeled as &quot;J9.&quot; Extension options 2 and 3 include a parallel port with binary input-outputs, called &quot;CEN,&quot; and labeled as &quot;J6&quot; on the back of the computer. Option &quot;Lsyn 0002&quot; extends Partner with a synchronous communication channel, and option &quot;LAN&quot; enables a local network between Partners. A special software package needs to be used for the last two options to work.</p><p>Our Partner has an I/O card which provides 3 I/O channels. The card itself is named &quot;LAN&quot; and &quot;comes with&quot; a manual called &quot;<a href="https://files.oddbit-retro.org/pub/IskraDeltaClubMirror/Partner/IDC%20Partner%20komunikacijski%20adapter%20LSYN-002.pdf">Communication Adapter LSYN-002</a>&quot;. It is not yet clear which of the above extensions are implemented with this card, but most likely it adds options &quot;Lsyn 0002&quot; and &quot;LAN&quot;. Looking at this Manual, it becomes apparent that the LPT RS-232 port is labeled as &quot;J8&quot; (and not as &quot;J7&quot;) on the back of the computer if this card is installed. So, our best bet is to connect to the &quot;J8&quot; port.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/03/image.png" class="kg-image" alt loading="lazy" width="2000" height="1365" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/03/image.png 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/03/image.png 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/03/image.png 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/03/image.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>The LSYN-002 communication adapter for Iskra Delta Partner. The LPT port is labeled as &quot;J8&quot; on the back of the computer.</figcaption></figure><p>The IDP Manual explains how the ports can be configured. Specifically, there are logical I/O channels (CON, AUX, LST) and physical I/O channels (GDP, CRT, LPT, VAX, MOD, CEN). The CP/M command &quot;DEVICE&quot; can be used to tie a logical channel to a physical channel and also set the communication parameters (such as flow control and baud rate). The command is used as follows: <code>DEVICE logical-channel:=physical-channel[parameters</code>. For example, <code>DEVICE AUX:=LPT[x,1200</code> ties the logical channel &quot;AUX&quot; to the physical channel &quot;LPT&quot; and sets the flow control to XON/XOFF and baud rate to 1200 bauds per second.</p><p>The User&apos;s Manual suggests using the &quot;RMT20&quot; program to utilize Partner as a terminal. The manual hints that RMT20 communicates with a remote computer through the AUX logical I/O channel. This led us to connect a [female] DB25 connector to the LPT port, configure the AUX channel, and test if RMT20 transmits something over the transmit line. </p><p>To set up the connection, we soldered two wires to the DB25 connector, one to pin 2 (TX, transmit) and the other to pin 7 (GND, ground). We then plugged this connector to &quot;J8&quot; on Partner. Next, we attached an oscilloscope to the two attached wires. We assigned the LPT physical channel to the AUX port and disabled hardware flow control by setting it to XON/XOFF using the CP/M command <code>DEVICE aux:=lpt[x,1200</code>. We found the RMT20 program on the hard disk provided with the emulator (GRMT20.COM). When pressing keys on Partner&apos;s keyboard, we were able to detect, with the oscilloscope, that characters were being transmitted over the TX line.</p><p>Since our PC did not have a serial port, we had to use a USB-to-Serial adapter. The adapter had a female DB9 connector on one end. We attached the two wires coming from the Partner to a male DB9 connector, with one wire connected to pin 5 (GND, ground) and the other to pin 2 (RX, receive). Next, we attached the USB adapter to the PC and configured a terminal program to match the AUX/LPT settings on the Partner. We discovered that we needed to set the baud rate to twice the setting on the Partner for it to transmit correctly. With this setup, we were able to receive data on the PC. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/03/null-modem-cable.png" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/03/null-modem-cable.png 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/03/null-modem-cable.png 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/03/null-modem-cable.png 1600w, https://www.oddbit-retro.org/content/images/2023/03/null-modem-cable.png 2000w" sizes="(min-width: 720px) 720px"><figcaption>DB25 to DB9 connector null-modem cable.</figcaption></figure><p>We ended up creating a full-blown null-modem cable. However, we discovered that the DTR and DSR signals are not supported by IDP, which means that we had to set the PC terminal to RTS/CTS hardware flow control when switching the Partner to &quot;hardware flow control&quot;.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/03/image-9.png" class="kg-image" alt loading="lazy" width="2000" height="1288" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/03/image-9.png 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/03/image-9.png 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/03/image-9.png 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/03/image-9.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>DTR?</figcaption></figure><p>After successfully establishing communication, our next step was to be able to do the same thing from within our own program running on the Partner. To get started, we decided to look at how GRMT20 sends a character to the PC. Using the emulator, we were able to trace the program and locate the following piece of code:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/03/image-7.png" class="kg-image" alt loading="lazy" width="2000" height="1314" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/03/image-7.png 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/03/image-7.png 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/03/image-7.png 1600w, https://www.oddbit-retro.org/content/images/2023/03/image-7.png 2289w" sizes="(min-width: 720px) 720px"><figcaption>Some &quot;archecode&quot;.</figcaption></figure><p>We were able to use this code to send a character to the PC, but only after running GRMT20 first and breaking out of it with Shift+Break. This suggested that GRMT20 initializes the Z80 SIO chips that are used by IDP for serial communication. Upon further investigation, we discovered that GRMT20 extends BDOS with SIO-related functions and installs its own interrupt handlers. While this may have been a viable solution, it seemed too messy for our liking, so we decided to refer to the Z80 SIO documentation instead (<a href="https://files.oddbit-retro.org/pub/Partner/Docs/HardwareSpecs/z80_sio_long.pdf">this</a> or <a href="https://files.oddbit-retro.org/pub/Partner/Docs/HardwareSpecs/z80_sio_short.pdf">this</a>).</p><p>IDP has two SIO chips, each supporting two channels corresponding to CRT, LPT, VAX, and MOD, respectively. We were using channel B on the first SIO chip (LPT), and we had to make sure not to break the keyboard that was using channel A on the same chip. Interestingly, disabling the &quot;Status Affects Vector&quot; option on channel B can actually break the keyboard, since this setting affects both channels. Similarly, setting the interrupt vector also affects both channels and can have the same effect.</p><p>By initializing SIO correctly, we were soon able to send a character to the PC. With this accomplished, we moved on to receiving characters and creating a full-blown polling loop. Since interrupts can bring in all sorts of trouble, we decided not to utilize them in the first step. Nonetheless, we explored interrupts to set the groundwork for using them later in the project.</p><p>The IDP interrupt vectors for the SIO channels are as follows:</p><ul><li>Chip 1, channel B (LPT): 90h</li><li>Chip 1, channel A (CRT; keyboard): 98h</li><li>Chip 2, channel B: A0h</li><li>Chip 2, channel A: A8h</li></ul><p>These addresses are offsets from the start of BIOS. To obtain the BIOS address, we need to read the memory at 0001h and subtract 3 from that. On our Partner, that&apos;s FA00h.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/03/image-3.png" class="kg-image" alt loading="lazy" width="2000" height="1409" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/03/image-3.png 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/03/image-3.png 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/03/image-3.png 1600w, https://www.oddbit-retro.org/content/images/2023/03/image-3.png 2236w" sizes="(min-width: 720px) 720px"><figcaption>Interrupt Vector Table (IVT) as described in the IDP Manual.</figcaption></figure><p>Interrupt handlers and the buffers used by the handlers must reside in the shared memory space because they can fire when either of the two memory banks is enabled. The shared memory space supposedly starts at C000h and goes to FFFFh, with BDOS starting at F400h. Further tests are needed to validate this. The space available for handlers is thus from C000h to F400h. However, other interrupt handlers and their data may be installed here, so we must be careful not to overwrite them. The keyboard handler is at FEA7h (inside BIOS, which is good as we will not interfere with it). GRMT installs its SIO handlers at EE9Ch and EE81h (and we most likely should do the same).</p><p>When utilizing interrupts, certain functions must be put into &quot;transactions&quot; and not allow the interrupt handlers to execute while they are executing, especially when addressing the SIO registers. We are not yet sure whether this can be achieved simply with DI-EI or whether something more elaborate is required.</p><p>As you can see, interrupts pose several challenges. We are not yet sure if these outweigh the benefits. However, we will explore this further as it represents a good challenge and will uncover a lot about how IDP interrupts work.</p><p>In conclusion, we were able to establish a successful serial connection between an Iskra Delta Partner and a PC. We were able to configure both the physical and logical I/O channels for initial testing. Despite encountering some hurdles, we managed to implement a serial communication library for IDP. In addition, we delved into the Z80 SIO documentation and gained a deeper understanding of the Partner&apos;s serial communication system. Last but not least, we laid the foundation for extending the library with interrupts in the future. </p>]]></content:encoded></item><item><title><![CDATA[From Obsolete to Accessible: Restoring Data from Old Gorenje Dialog Floppies]]></title><description><![CDATA[<p>Recently, our team was presented with a unique challenge - retrieve data from 30-40-year-old floppies that contained data and programs for Dialog, a relatively rare Slovene computer. Despite the odds, we were quite successful in the task.</p><p>To retrieve the data from the old floppies, we used <a href="https://www.kryoflux.com/">Kryoflux</a> - a</p>]]></description><link>https://www.oddbit-retro.org/from-obsolete-to-accessible-restoring-data-from-old-gorenje-dialog-floppies/</link><guid isPermaLink="false">63f1f194d8dcf100018f8707</guid><dc:creator><![CDATA[Miha Grčar]]></dc:creator><pubDate>Sun, 19 Feb 2023 13:56:55 GMT</pubDate><content:encoded><![CDATA[<p>Recently, our team was presented with a unique challenge - retrieve data from 30-40-year-old floppies that contained data and programs for Dialog, a relatively rare Slovene computer. Despite the odds, we were quite successful in the task.</p><p>To retrieve the data from the old floppies, we used <a href="https://www.kryoflux.com/">Kryoflux</a> - a USB-based device designed specifically for acquiring reliable low-level reads suitable for software preservation. With the help of Kryoflux and a standard 80-track floppy drive, specifically, the Newtronics Mitsumi D509V5, we were able to read the magnetic flux transitions on the disk and reconstruct the data in a way that was faithful to the original.</p><p>The floppies we worked with had two sides, with 80 tracks per side, 5 sectors per track, and 1024 bytes per sector. Such a configuration would normally denote high-density floppies, but in our case, they were double-density floppies.</p><p>Our process of reading the data in the end converged to the following steps:</p><p>1. Check the maker of the floppy. Different manufacturers used different magnetic materials and coatings, which could affect the readout quality. For example, if you learn that a certain manufacturer&apos;s floppies from the same batch tend to disintegrate in the drive, you might want to avoid reading them entirely. Similarly, if you notice that a read heavily changes the magnetic disk by leaving scratches or marks, you might want to leave those disks for the end of the process, because that will also smear the drive heads and you will need to clean them. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/02/ima_5ef65cf.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/02/ima_5ef65cf.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/02/ima_5ef65cf.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/02/ima_5ef65cf.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/02/ima_5ef65cf.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Several floppies in the batch were from this particular manufacturer. They tended to disintegrate during the read process. As a result, we decided to avoid reading them altogether.</figcaption></figure><p>2. Inspect the magnetic disk visually. Look for any signs of damage, such as scratches, mold, or dust. This is important because you want to remember the state the disk is in so you can see if the read visibly damages the disk. Furthermore, if you notice dust on the disk, gently blow it off before inserting the floppy into the drive. If you notice mold on the disk, clean it gently with a cotton swab and isopropyl alcohol, being careful not to damage the magnetic surface of the disk. If you notice heavy scratches, just be aware of them, as there isn&apos;t much you can do to repair the damage.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/02/linstar_wsrgb.JPG" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/02/linstar_wsrgb.JPG 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/02/linstar_wsrgb.JPG 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/02/linstar_wsrgb.JPG 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/02/linstar_wsrgb.JPG 2400w" sizes="(min-width: 720px) 720px"><figcaption>Some mold on the magnetic disk. It was successfully cleaned off by isopropyl.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/02/ima_7370db2.jpeg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/02/ima_7370db2.jpeg 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/02/ima_7370db2.jpeg 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/02/ima_7370db2.jpeg 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/02/ima_7370db2.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption>A scratch. This caused several bad sectors.</figcaption></figure><p>3. Insert the floppy and note the sounds coming out of the drive, such as clicking, grinding, or squealing. These sounds usually appear immediately after closing the drive door as the drive spins up. If you hear any worrying sounds, it&apos;s important to take caution. It may be best to do a partial read instead, then abort and check what happened to the disk before proceeding with a full read. You might want to read such floppies at the end, or if there is no visible damage, proceed with reading them anyway.</p><p>4. Read the floppy using Kryoflux. This involves connecting the floppy drive to the Kryoflux device and running the appropriate software to extract the data from the disk. You can choose to do either a full or partial read. A full read involves reading every track on the disk, while a partial read involves only reading specific tracks. We will talk more about this step later on.</p><p>5. Inspect the magnetic disk visually again. Look for any new signs of damage that may have occurred during the readout process. By checking for any new signs of damage, you can determine whether you can simply re-read the floppy to retrieve more good sectors. If the condition of the disk did not change, this is possible. However, if the disk deteriorated during the readout process, you should be careful with your next read and only attempt to read the missing tracks. If the disk disintegrated completely, there is nothing you can do, and subsequent reads will be in vain. In such cases, you need to clean the drive heads, as the material that fell off the disk is most likely on the drive heads.</p><p>6. Check the retrieved data. After the data has been retrieved from the floppy, it is important to check the integrity of the data. One way to do this is to use the <a href="https://hxc2001.com/download/floppy_drive_emulator/">HxC Floppy Emulator software</a> to decode the data and identify any bad sectors with incorrect CRC codes. This information can be used to determine which tracks need to be re-read or where on the magnetic disk an issue may be located. Another tool that can be used to check the state of the data is the <a href="https://www.kryoflux.com/?page=download">Kryoflux Disk Tool Console (DTC) software</a>. This software can identify which tracks contain bad sectors. This information is crucial for attempting to fix issues, re-read data, and/or reconstruct the data. We will talk more about this later on in this post.</p><p>7. Clean the drive heads if necessary. When reading deteriorated floppy disks, there is a chance that material from the magnetic disk has ended up on the drive heads, which can not only prevent the drive from reading subsequent floppies, but can also damage them. If you notice that the read has caused heavy deterioration of the disk, it&apos;s important to clean the drive heads carefully. However, cleaning the heads can be a delicate process, and if done incorrectly, it can render your drive useless. To clean the heads, you can start by gently blowing towards the drive heads. If you still see black specks on the heads, you can use a flattened cotton swab with isopropyl to gently clean the heads by barely touching them, being careful not to lift the upper head.</p><p>8. Repeat the read if necessary. After completing the read of the floppy and examining the retrieved data, you may find that there are still some bad sectors that were not successfully read. In this case, it may be necessary to repeat the read. However, it&apos;s important to note that repeated reads can further damage the magnetic disk, so you should only do so if you believe it&apos;s necessary and the disk is still in good enough condition. It&apos;s generally best to re-read only the tracks that were not successfully read, rather than re-reading the entire disk. Once you have successfully read all the necessary tracks, you can then combine the data from each read into a full image of the floppy. </p><p>In the following section, we provide more details on how we used the Kryoflux and HxC software for reading and decoding our floppies.</p><p><strong>Reading with Kryoflux DTC.</strong> Kryoflux&apos;s DTC (Disk Tool Console) is a tool for reading and writing floppy disks. To read a floppy disk, we used the following command in the Windows console:</p><p><code>dtc -d1 -ffloppy_name\floppy_name_ -i0</code></p><p>Here, <code>-d1</code> specifies the drive number, <code>floppy_name</code> is the name of the floppy disk we want to read, and <code>-i0</code> specifies that we want to perform a low-level read for archival purposes. Please refer to the <a href="https://www.kryoflux.com/download/kryoflux_manual.pdf">DTC Manual</a> for more information.</p><p><strong>Checking for Successful Read.</strong> After reading the floppy disk, we needed to check whether the read was successful. To do this, we issued the following command:</p><p><code>dtc -m1 -ffloppy_name\* -i0 -ftest.img -z3 -i4 2&gt;&amp;1 | findstr &quot;failed mismatch unformatted&quot;</code></p><p>The following explains the parameters that we used. Once again, please refer to the <a href="https://www.kryoflux.com/download/kryoflux_manual.pdf">DTC Manual</a> for more information.</p><ul><li><code>-m1</code>: Enables the image file input.</li><li><code>-ffloppy_name\*</code>: Specifies the image files to read.</li><li><code>-i0</code>: Specifies the format of the input image file.</li><li><code>-ftest.img</code>: Specifies the output file for the decoded image.</li><li><code>-z3</code>: Specifies the sector size (in this case, 1024 bytes).</li><li><code>-i4</code>: Specifies the format of the output image file (MFM, 80 tracks).</li><li><code>2&gt;&amp;1</code>: Redirects error messages to standard output so they can be piped to the next command.</li><li><code>|</code>: Pipes the output of the DTC command to the &quot;findstr&quot; command.</li><li><code>findstr &quot;failed mismatch unformatted&quot;</code>: Searches the output for the strings &quot;failed&quot;, &quot;mismatch&quot;, and &quot;unformatted&quot;, and displays any lines that contain them. This is used to determine which tracks, if any, had errors during the read process.</li></ul><p>If the read was not completely successful, the above command will expose any bad tracks that may have been encountered during the read process. For example: </p><pre><code class="language-plain-text">44.0    : Read operation failed
45.0    : Read operation failed
80.0    : MFM: &lt;unformatted&gt;
80.1    : MFM: &lt;unformatted&gt;
81.0    : MFM: &lt;unformatted&gt;
81.1    : MFM: &lt;unformatted&gt;
82.0    : MFM: &lt;mismatch&gt;, *NT
82.1    : MFM: &lt;unformatted&gt;
83.0    : MFM: &lt;unformatted&gt;
83.1    : MFM: &lt;unformatted&gt;</code></pre><p>In the above example, tracks 44 and 45 (on side 0) were not read successfully, and tracks 80 to 83 were either unformatted or had a mismatch. However, note that unformatted and mismatched tracks after track 79 (which is the 80th consecutive track) are usually not a cause for concern on an 80-track floppy disk.</p><p>Given the above, we should re-read tracks 44 and 45 to ensure that the data is fully recovered. When re-reading, it is important to use DTC&apos;s options <code>-g</code>, <code>-s</code>, and <code>-e</code> to properly target the bad tracks. The <code>-g</code> option allows DTC to read only one side of the disk. The <code>-s</code> option specifies the start track to read, and the <code>-e</code> option specifies the end track to read. By specifying the range of affected tracks, DTC can focus on recovering only the necessary data, reducing the risk of further damaging the disk.</p><p>If we have created multiple images of a floppy disk using the Kryoflux DTC software, the next step is to combine the resulting tracks. This can be done simply by copying the corresponding track files into the same folder.</p><p>Another software we used to check the success of our reads is the HxC Floppy Emulator software. This software allows us to visualize and analyze the data acquired with Kryoflux. The visualizations provided by the HxC software are especially helpful in showing the location of damaged areas on the disk. We can then visually inspect the damaged area to see if it can be cleaned or fixed in some other way.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/02/image-1.png" class="kg-image" alt loading="lazy" width="2000" height="1125" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/02/image-1.png 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/02/image-1.png 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/02/image-1.png 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/02/image-1.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>HxC view of a raw floppy disk image. We can see the location of a damaged area on the &quot;front&quot; side of the disk.</figcaption></figure><p>Moreover, the HxC software provides detailed information about the exact location of the damaged sectors. This information can be useful for reconstructing tracks from sectors, a process that can be challenging but is achievable with the help of specialized tools.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.oddbit-retro.org/content/images/2023/02/image-3.png" class="kg-image" alt loading="lazy" width="2000" height="1125" srcset="https://www.oddbit-retro.org/content/images/size/w600/2023/02/image-3.png 600w, https://www.oddbit-retro.org/content/images/size/w1000/2023/02/image-3.png 1000w, https://www.oddbit-retro.org/content/images/size/w1600/2023/02/image-3.png 1600w, https://www.oddbit-retro.org/content/images/size/w2400/2023/02/image-3.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Sector 2 (on track 43 on side 1) was not read correctly.</figcaption></figure><p><strong>In Conclusion...</strong> We have outlined the process we used to quite successfully read and decode 30-40 years old floppy disks. By using the Kryoflux DTC software, we were able to read and assess the quality of the data, and then by using the HxC software, we were able to analyze and visualize the data to determine its integrity.</p><p>We want to emphasize that the methodology we presented here is not intended as a recommendation, but rather as a record of what worked for us in our specific case. The process of reading and decoding old floppy disks can be complex and challenging, and the results will vary depending on the condition of the disks and other factors.</p><p>If you are attempting to recover data from old floppy disks, we recommend that you carefully research the available methods and tools, and consult with experts if necessary. With patience, persistence, and a bit of luck, it is often possible to recover valuable data from these seemingly unsalvageable storage media.</p><h2 id="resources">Resources</h2><p>These Kryoflux floppy images are available here: </p><ul><li><a href="https://files.oddbit-retro.org/pub/GorenjeDialog/FloppyImg/DialogDisketeBox1.zip">https://files.oddbit-retro.org/pub/GorenjeDialog/FloppyImg/DialogDisketeBox1.zip</a></li><li><a href="https://files.oddbit-retro.org/pub/GorenjeDialog/FloppyImg/DialogDisketeBox2.zip">https://files.oddbit-retro.org/pub/GorenjeDialog/FloppyImg/DialogDisketeBox2.zip</a> </li></ul>]]></content:encoded></item></channel></rss>