diff options
| author | Sadeep Madurange <sadeep@asciimx.com> | 2025-10-25 10:53:52 +0800 |
|---|---|---|
| committer | Sadeep Madurange <sadeep@asciimx.com> | 2025-10-25 10:53:52 +0800 |
| commit | 79e9eec0ac05634fe367220df701b20c91523291 (patch) | |
| tree | 620654a06938f4147b4a22cc332ce1e5f40cb247 | |
| parent | 8e52e6f8e11c78d1935ac4994db1d6537e948bb7 (diff) | |
| download | www-79e9eec0ac05634fe367220df701b20c91523291.tar.gz | |
Project folder.
76 files changed, 1356 insertions, 56 deletions
diff --git a/_config.yml b/_config.yml index e26b366..71ededd 100644 --- a/_config.yml +++ b/_config.yml @@ -1,6 +1,8 @@ title: 'ASCIIMX' tagline: "Wickramage Don Sadeep's Personal Website" +permalink: pretty + collections: blog: output: true diff --git a/_layouts/default.html b/_layouts/default.html index 77a3ca0..9adf234 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -14,8 +14,11 @@ <li {% if page.url == "/" %}class="active"{% endif %}> <a href="{{ site.baseurl }}/">hme</a> </li> - <li {% if page.url == "/about.html" %}class="active"{% endif %}> - <a href="{{ site.baseurl }}/about.html">abt</a> + <li {% if page.url == "/projects/" %}class="active"{% endif %}> + <a href="{{ site.baseurl }}/projects/">tnk</a> + </li> + <li {% if page.url == "/about/" %}class="active"{% endif %}> + <a href="{{ site.baseurl }}/about/">abt</a> </li> <li><a href="{{ site.baseurl }}/feed.xml">rss</a></li> </ul> diff --git a/_layouts/projects.html b/_layouts/projects.html new file mode 100644 index 0000000..d1d155b --- /dev/null +++ b/_layouts/projects.html @@ -0,0 +1,37 @@ +--- +layout: default +title: Projects +--- + +<table style="border: none; width: 100%"> + <tr style="border: none;"> + <td style="border: none; width: 50%; vertical-align: top; background-color: transparent;"> + <img src="e-reader/thumb.png" alt="E-reader" style="width: 100%"> + <h5><a href="e-reader">Prototype e-reader</a></h5> + </td> + <td style="border: none; width: 50%; vertical-align: top; background-color: transparent;"> + <img src="matrix-digital-rain/thumb.png" alt="The Matrix" style="width: 100%"> + <h5><a href="matrix-digital-rain">The Matrix digital rain</a></h5> + </td> + </tr> + <tr style="border: none;"> + <td style="border: none; width: 50%; vertical-align: top; background-color: transparent;"> + <img src="etlas/dash.jpg" alt="Etlas" style="width: 100%"> + <h5><a href="etlas">Etlas: e-paper display for news, stocks, and the weather</a></h5> + </td> + <td style="border: none; width: 50%; vertical-align: top; background-color: transparent;"> + <img src="bumblebee/thumb.png" alt="Bumblebee" style="width: 100%"> + <h5><a href="bumblebee">Bumblebee: turn browser sessions to code</a></h5> + </td> + </tr> + <tr style="border: none;"> + <td style="border: none; width: 50%; vertical-align: top; background-color: transparent;"> + <img src="fpm-door-lock/pcb.jpg" alt="FPM door lock" style="width: 100%"> + <h5><a href="fpm-door-lock">Prototype fingerprint door lock</a></h5> + </td> + <td style="border: none; width: 50%; vertical-align: top; background-color: transparent;"> + <img src="desktop-unix/linux.png" alt="Unix desktop" style="width: 100%"> + <h5><a href="desktop-unix">Unix for the desktop</a></h5> + </td> + </tr> +</table> diff --git a/_projects/bumblebee/bee.mp4 b/_projects/bumblebee/bee.mp4 Binary files differnew file mode 100644 index 0000000..835600d --- /dev/null +++ b/_projects/bumblebee/bee.mp4 diff --git a/_projects/bumblebee/index.md b/_projects/bumblebee/index.md new file mode 100644 index 0000000..467fa39 --- /dev/null +++ b/_projects/bumblebee/index.md @@ -0,0 +1,26 @@ +--- +title: "Bumblebee: turn browser sessions to code" +date: 2025-04-02T00:00:00+08:00 +author: W. D. Sadeep Madurange +--- + +Bumblebee is a web browser that turns browser sessions into C# scripts. Its +objective is to eliminate the need for authoring scripts for testing, web +scraping, and other browser automation tasks. + +<video style="max-width:100%; margin-bottom: 10px" controls="" poster="thumb.png"> + <source src="bee.mp4" type="video/mp4"> +</video> + +Bumblebee is a Windows Forms application written in C#. The rendering of web +content is handled by the embedded Microsoft Edge browser (via WebView). The +text editor on the right is {{< link +src="https://github.com/desjarlais/Scintilla.NET" class="external" +target="_blank" rel="noopener noreferrer">}}Scintilla.NET{{< /link >}}. It +enables users to override the script at any point during the session. There are +application settings that let users debounce events, ignore hidden elements +and scripts, and more. + +Unfortunately, I can't share the source code for Bumblebee. I developed it for +an employer. Hence, the software is proprietary. + diff --git a/_projects/bumblebee/thumb.png b/_projects/bumblebee/thumb.png Binary files differnew file mode 100644 index 0000000..1e0db2b --- /dev/null +++ b/_projects/bumblebee/thumb.png diff --git a/_projects/desktop-unix/dotfiles.tar.gz b/_projects/desktop-unix/dotfiles.tar.gz Binary files differnew file mode 100644 index 0000000..c6774c4 --- /dev/null +++ b/_projects/desktop-unix/dotfiles.tar.gz diff --git a/_projects/desktop-unix/index.md b/_projects/desktop-unix/index.md new file mode 100644 index 0000000..6e97964 --- /dev/null +++ b/_projects/desktop-unix/index.md @@ -0,0 +1,29 @@ +--- +title: Unix for the desktop +date: 2025-09-21T00:00:00+08:00 +author: Wickramage Don Sadeep Madurange +--- + +The year 2020 transformed my personal computing experience. In March 2020, I +stumbled upon Arch Linux and discovered that I could customize my desktop +environment to look and work any way I liked. I exercised that newfound freedom +to create a Matrix-themed setup: + + + +The system employs the X display server and the i3 window manager. The terminal +emulator used is Urxvt. The translucent effect is achieved with the help of the +Xcompmgr compositor. This sort of setup was popular among minimalist Linux +users. + +In February 2024, I switched to an OpenBSD system with Xenocara (the OpenBSD +build of X display server) as the display server and i3 as the window manager: + + + +Unlike Linux, OpenBSD includes a coherent desktop environment out of the box. +Except for the window manager, for which I prefer a tiling one, I'm now using +the default OpenBSD setup. For the window manager, I use dwm from the Suckless +team. + +Files: [dotfiles.tar.gz](dotfiles.tar.gz) diff --git a/_projects/desktop-unix/linux.png b/_projects/desktop-unix/linux.png Binary files differnew file mode 100644 index 0000000..40bc33b --- /dev/null +++ b/_projects/desktop-unix/linux.png diff --git a/_projects/desktop-unix/openbsd.png b/_projects/desktop-unix/openbsd.png Binary files differnew file mode 100644 index 0000000..d7489f7 --- /dev/null +++ b/_projects/desktop-unix/openbsd.png diff --git a/_projects/e-reader/circuit.svg b/_projects/e-reader/circuit.svg new file mode 100644 index 0000000..fd7508b --- /dev/null +++ b/_projects/e-reader/circuit.svg @@ -0,0 +1,145 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Circuit Diagram, cdlibrary.dll 4.0.0.0 --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg version="1.1" width="680" height="360" xmlns="http://www.w3.org/2000/svg"> + <line x1="360" y1="130" x2="360" y2="330" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="70" y1="330" x2="360" y2="330" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="70" y1="250" x2="70" y2="260" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="70" y1="260" x2="70" y2="275" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="70" y1="315" x2="70" y2="330" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 70,275 L 70,277 L 63,280 L 77,286 L 63,292 L 77,298 L 63,304 L 77,310 L 70,313 L 70,315" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="56" y="295" style="font-family:Arial;font-size:11px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 56, 295)">10 kΩ</text> + <line x1="70" y1="250" x2="100" y2="250" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="130" y1="250" x2="190" y2="250" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="100" y1="250" x2="101" y2="250" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="129" y1="250" x2="130" y2="250" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <ellipse cx="104" cy="250" rx="3" ry="3" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <ellipse cx="126" cy="250" rx="3" ry="3" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <line x1="103" y1="242" x2="127" y2="242" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="109" y1="236" x2="121" y2="236" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="115" y1="236" x2="115" y2="242" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="70" y1="140" x2="190" y2="140" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="70" y1="60" x2="70" y2="140" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="70" y1="100" x2="100" y2="100" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="130" y1="100" x2="190" y2="100" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="70" y1="60" x2="100" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="130" y1="60" x2="190" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="100" y1="100" x2="101" y2="100" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="129" y1="100" x2="130" y2="100" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <ellipse cx="104" cy="100" rx="3" ry="3" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <ellipse cx="126" cy="100" rx="3" ry="3" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <line x1="103" y1="92" x2="127" y2="92" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="109" y1="86" x2="121" y2="86" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="115" y1="86" x2="115" y2="92" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="100" y1="60" x2="101" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="129" y1="60" x2="130" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <ellipse cx="104" cy="60" rx="3" ry="3" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <ellipse cx="126" cy="60" rx="3" ry="3" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <line x1="103" y1="52" x2="127" y2="52" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="109" y1="46" x2="121" y2="46" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="115" y1="46" x2="115" y2="52" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="310" y1="130" x2="410" y2="130" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="310" y1="120" x2="410" y2="120" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="310" y1="110" x2="410" y2="110" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="310" y1="100" x2="410" y2="100" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="310" y1="90" x2="410" y2="90" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="310" y1="80" x2="410" y2="80" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="310" y1="70" x2="410" y2="70" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="310" y1="60" x2="420" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="450" y1="160" x2="500" y2="160" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:3" /> + <path d="M 430,120 L 430,110 L 420,100 L 410,100" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 430,120 L 420,110 L 410,110" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 430,130 L 420,120 L 410,120" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 430,140 L 420,130 L 410,130" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 510,180 L 520,190 L 530,190" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 510,190 L 520,200 L 530,200" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 510,200 L 520,210 L 530,210" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 510,200 L 510,210 L 520,220 L 530,220" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="524" y="185" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 524, 185)">1</text> + <text x="524" y="195" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 524, 195)">2</text> + <text x="524" y="205" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 524, 205)">3</text> + <text x="524" y="215" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 524, 215)">4</text> + <line x1="445" y1="155" x2="450" y2="165" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:1" /> + <line x1="495" y1="155" x2="490" y2="165" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:1" /> + <path d="M 455,160 L 440,160 L 430,150 L 430,80" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:3" /> + <path d="M 485,160 L 500,160 L 510,170 L 510,240" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:3" /> + <path d="M 430,80 L 430,70 L 420,60 L 410,60" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 430,80 L 420,70 L 410,70" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 430,90 L 420,80 L 410,80" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 430,100 L 420,90 L 410,90" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="413" y="55" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 413, 55)">1</text> + <text x="413" y="65" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 413, 65)">2</text> + <text x="413" y="75" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 413, 75)">3</text> + <text x="413" y="85" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 413, 85)">4</text> + <text x="413" y="95" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 413, 95)">5</text> + <text x="413" y="105" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 413, 105)">6</text> + <text x="413" y="115" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 413, 115)">7</text> + <text x="413" y="125" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 413, 125)">8</text> + <path d="M 510,220 L 520,230 L 530,230" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 510,230 L 520,240 L 530,240" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 510,240 L 520,250 L 530,250" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 510,240 L 510,250 L 520,260 L 530,260" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="524" y="225" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 524, 225)">5</text> + <text x="524" y="235" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 524, 235)">6</text> + <text x="524" y="245" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 524, 245)">7</text> + <text x="524" y="255" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 524, 255)">8</text> + <text x="470" y="155" style="font-family:Arial;font-size:11px;text-anchor:middle" dominant-baseline="baseline" transform="rotate(0, 470, 155)">BUS</text> + <text x="590" y="170" style="font-family:Arial;font-size:11px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 590, 170)">E-Paper Display HAT</text> + <rect x="540" y="180" width="100" height="90" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <line x1="530" y1="190" x2="540" y2="190" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="190" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 544, 190)">CS</text> + <line x1="530" y1="200" x2="540" y2="200" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="200" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 544, 200)">DC</text> + <line x1="530" y1="210" x2="540" y2="210" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="210" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 544, 210)">DIN</text> + <line x1="530" y1="220" x2="540" y2="220" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="220" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 544, 220)">CLK</text> + <line x1="530" y1="230" x2="540" y2="230" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="230" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 544, 230)">BUSY</text> + <line x1="530" y1="240" x2="540" y2="240" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="240" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 544, 240)">RST</text> + <line x1="530" y1="250" x2="540" y2="250" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="250" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 544, 250)">GND</text> + <line x1="530" y1="260" x2="540" y2="260" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="260" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 544, 260)">VCC</text> + <rect x="200" y="50" width="100" height="210" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <text x="250" y="40" style="font-family:Arial;font-size:11px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 250, 40)">ESP-WROOM-32</text> + <line x1="190" y1="60" x2="200" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="204" y="60" style="font-family:Arial;font-size:10px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 204, 60)">IO21</text> + <text x="196" y="58" style="font-family:Arial;font-size:8px;text-anchor:end" dominant-baseline="baseline" transform="rotate(0, 196, 58)"></text> + <line x1="300" y1="60" x2="310" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="296" y="60" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 296, 60)">IO5</text> + <text x="304" y="58" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 304, 58)"></text> + <line x1="300" y1="70" x2="310" y2="70" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="296" y="70" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 296, 70)">IO16</text> + <text x="304" y="68" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 304, 68)"></text> + <line x1="300" y1="80" x2="310" y2="80" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="296" y="80" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 296, 80)">IO23</text> + <text x="304" y="78" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 304, 78)"></text> + <line x1="300" y1="90" x2="310" y2="90" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="296" y="90" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 296, 90)">IO18</text> + <text x="304" y="88" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 304, 88)"></text> + <line x1="190" y1="100" x2="200" y2="100" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="204" y="100" style="font-family:Arial;font-size:10px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 204, 100)">IO22</text> + <text x="196" y="98" style="font-family:Arial;font-size:8px;text-anchor:end" dominant-baseline="baseline" transform="rotate(0, 196, 98)"></text> + <line x1="300" y1="100" x2="310" y2="100" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="296" y="100" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 296, 100)">IO4</text> + <text x="304" y="98" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 304, 98)"></text> + <line x1="300" y1="110" x2="310" y2="110" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="296" y="110" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 296, 110)">IO2</text> + <text x="304" y="108" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 304, 108)"></text> + <line x1="300" y1="120" x2="310" y2="120" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="296" y="120" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 296, 120)">GND</text> + <text x="304" y="118" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 304, 118)"></text> + <line x1="300" y1="130" x2="310" y2="130" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="296" y="130" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 296, 130)">3V3</text> + <text x="304" y="128" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 304, 128)"></text> + <line x1="190" y1="140" x2="200" y2="140" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="204" y="140" style="font-family:Arial;font-size:10px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 204, 140)">GND</text> + <text x="196" y="138" style="font-family:Arial;font-size:8px;text-anchor:end" dominant-baseline="baseline" transform="rotate(0, 196, 138)"></text> + <line x1="190" y1="250" x2="200" y2="250" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="204" y="250" style="font-family:Arial;font-size:10px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 204, 250)">IO15</text> + <text x="196" y="248" style="font-family:Arial;font-size:8px;text-anchor:end" dominant-baseline="baseline" transform="rotate(0, 196, 248)"></text> + <ellipse cx="360" cy="130" rx="2" ry="2" style="fill-opacity:1;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <ellipse cx="70" cy="100" rx="2" ry="2" style="fill-opacity:1;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> +</svg>
\ No newline at end of file diff --git a/_projects/e-reader/ereader.mp4 b/_projects/e-reader/ereader.mp4 Binary files differnew file mode 100644 index 0000000..a57a5b1 --- /dev/null +++ b/_projects/e-reader/ereader.mp4 diff --git a/_projects/e-reader/index.md b/_projects/e-reader/index.md new file mode 100644 index 0000000..a19603a --- /dev/null +++ b/_projects/e-reader/index.md @@ -0,0 +1,90 @@ +--- +title: Prototype e-reader +date: 2023-10-24T00:00:00+08:00 +author: W. D. Sadeep Madurange +--- + +This project features a prototype e-reader powered by a 7.5-inch {{< link +src="https://www.waveshare.com/" class="external" target="_blank" +rel="noopener noreferrer">}}Waveshare{{< /link >}} e-paper display and an +ESP-WROOM-32 development board. + +<video style="max-width:100%" controls="" poster=thumb.png> + <source src="ereader.mp4" type="video/mp4"> +</video> + +{{< toc >}} + +## Overview + +In 2017, during a short stint as a project manager, I was tasked with +installing some e-paper displays in a car park. Not knowing how they worked, I +remember marveling at their sight like a muggle witnessing magic. As someone +who enjoys reading, I found e-paper to be a true innovation. This project was +born out of that enduring curiosity and love of e-paper technology. + +The prototype, while far from ready for daily use, has some nifty features that +fellow hobbyists and tinkerers may find interesting. The reader can display +books of arbitrary sizes by streaming them over HTTP. It employs sleep modes to +minimize power consumption when not in use and records the reading progress in +the chip's RTC memory. + +The following schematic outlines the electrical connections of the e-reader. + + + +The biggest challenge when building an e-reader using an ESP32 board is its low +memory and lack of storage. My ESP-WROOM-32 board has 512 KB of SRAM and 4 MB +of flash memory, which the freeRTOS, ESP-IDF, and my own program have to share. +To put things into perspective, compare that to a Kindle Paperwhite, which has +at least 256 MB of memory and 8 GB of storage. + +Despite its constraints, as microcontrollers go, ESP32 is a powerful +system-on-a-chip with a 160 MHz dual-core processor and integrated WiFi. So, I +thought it’d be amusing to embrace the constraints and build my e-reader using +just a $5 MCU and the power of C programming. + +## The file format + +The file format dictates the complexity of the embedded software. So, I’ll +begin there. The e-reader works by downloading and rendering a rasterized +monochrome image of a page (a .ebm file). + +The EBM file contains a series of bitmaps, where each bitmap corresponds to a +page of a book sized to fit the e-paper display. Each byte contains information +for rendering eight pixels. For my display, which has a resolution of 480x800, +the bitmaps are laid out along 48 KB boundaries. This simple file format lends +well to HTTP streaming, which is its main advantage, as we will soon see. + +The enclosed pdftoebm.py script in the tarball at the end of the page converts +PDF documents to an EBM file. I use it to make EBM files before uploading them +to a web server. + +## How does it work? + +As the e-reader has no storage, it can't store books locally. Instead, I first +have to upload the EBM file I want to read to a web server. The location of the +file is configured via the `EBM_ARCH_URL` setting in the Kconfig.projbuild +file. To read a different book, I create an EBM file with the same name and +upload it to the original location. That way, I don't have to recompile the +embedded software. + +Upon powering up, the e-reader checks the reading progress stored in the RTC +memory. It then downloads three pages (current, previous, and next) to a +circular buffer in DMA-capable memory. When the user turns a page, one of the +two cores of the MCU transfers it from the buffer to the display over a Serial +Peripheral Interface (SPI). The other downloads a new page in the background. I +use the ESP-IDF task API to pin the two routines to each core. + +I designed the EBM format with HTTP streaming in mind. To download a page based +on the current reading progress, the e-reader specifies the page offset and the +chunk size using the HTTP Range header. + +## Afterword + +It's been six years since the car park and the displays. At the time, I knew +nothing about embedded systems or display drivers. It took a long time to +develop the skill set, but now, at last, I know how those displays worked and +how to build my own. + +Files: [source.tar.gz](source.tar.gz) diff --git a/_projects/e-reader/source.tar.gz b/_projects/e-reader/source.tar.gz Binary files differnew file mode 100644 index 0000000..3e343a7 --- /dev/null +++ b/_projects/e-reader/source.tar.gz diff --git a/_projects/e-reader/thumb.png b/_projects/e-reader/thumb.png Binary files differnew file mode 100644 index 0000000..1e222d2 --- /dev/null +++ b/_projects/e-reader/thumb.png diff --git a/_projects/etlas/circuit.svg b/_projects/etlas/circuit.svg new file mode 100644 index 0000000..6255045 --- /dev/null +++ b/_projects/etlas/circuit.svg @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Circuit Diagram, cdlibrary.dll 4.0.0.0 --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg version="1.1" width="700" height="300" xmlns="http://www.w3.org/2000/svg"> + <line x1="380" y1="220" x2="380" y2="280" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="340" y1="220" x2="550" y2="220" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="340" y1="210" x2="550" y2="210" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="340" y1="200" x2="550" y2="200" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="340" y1="190" x2="550" y2="190" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="340" y1="180" x2="550" y2="180" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="130" y1="50" x2="210" y2="50" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="130" y1="70" x2="160" y2="70" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="160" y1="280" x2="380" y2="280" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="160" y1="70" x2="160" y2="280" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="130" y1="60" x2="210" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="70" y="30" style="font-family:Arial;font-size:11px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 70, 30)">AM2302</text> + <rect x="20" y="40" width="100" height="40" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <line x1="120" y1="50" x2="130" y2="50" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="116" y="50" style="font-family:Arial;font-size:11px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 116, 50)">DATA</text> + <line x1="120" y1="60" x2="130" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="116" y="60" style="font-family:Arial;font-size:11px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 116, 60)">GND</text> + <line x1="120" y1="70" x2="130" y2="70" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="116" y="70" style="font-family:Arial;font-size:11px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 116, 70)">VCC</text> + <text x="610" y="120" style="font-family:Arial;font-size:11px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 610, 120)">E-Paper display HAT</text> + <rect x="560" y="130" width="100" height="100" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <line x1="550" y1="140" x2="560" y2="140" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="140" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 140)">CS</text> + <line x1="550" y1="150" x2="560" y2="150" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="150" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 150)">DC</text> + <line x1="550" y1="160" x2="560" y2="160" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="160" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 160)">DIN</text> + <line x1="550" y1="170" x2="560" y2="170" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="170" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 170)">CLK</text> + <line x1="550" y1="180" x2="560" y2="180" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="180" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 180)">BUSY</text> + <line x1="550" y1="190" x2="560" y2="190" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="190" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 190)">RST</text> + <line x1="550" y1="200" x2="560" y2="200" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="200" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 200)">PWR</text> + <line x1="550" y1="210" x2="560" y2="210" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="210" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 210)">GND</text> + <line x1="550" y1="220" x2="560" y2="220" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="220" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 220)">VCC</text> + <line x1="340" y1="80" x2="430" y2="80" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="340" y1="70" x2="430" y2="70" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="340" y1="60" x2="430" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="340" y1="50" x2="430" y2="50" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="470" y1="110" x2="520" y2="110" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:3" /> + <path d="M 450,70 L 450,60 L 440,50 L 430,50" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 450,70 L 440,60 L 430,60" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 450,80 L 440,70 L 430,70" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 450,90 L 440,80 L 430,80" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 530,130 L 540,140 L 550,140" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 530,140 L 540,150 L 550,150" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 530,150 L 540,160 L 550,160" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 530,150 L 530,160 L 540,170 L 550,170" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="135" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 544, 135)">1</text> + <text x="544" y="145" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 544, 145)">2</text> + <text x="544" y="155" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 544, 155)">3</text> + <text x="544" y="165" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 544, 165)">4</text> + <line x1="465" y1="105" x2="470" y2="115" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:1" /> + <line x1="515" y1="105" x2="510" y2="115" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:1" /> + <path d="M 475,110 L 460,110 L 450,100 L 450,70" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:3" /> + <path d="M 505,110 L 520,110 L 530,120 L 530,150" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:3" /> + <text x="433" y="45" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 433, 45)">1</text> + <text x="433" y="55" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 433, 55)">2</text> + <text x="433" y="65" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 433, 65)">3</text> + <text x="433" y="75" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 433, 75)">4</text> + <rect x="220" y="40" width="100" height="210" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <text x="270" y="30" style="font-family:Arial;font-size:11px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 270, 30)">ESP32 Mini NodeMCU D1</text> + <line x1="200" y1="50" x2="220" y2="50" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="224" y="50" style="font-family:Arial;font-size:10px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 224, 50)">IO19</text> + <text x="216" y="48" style="font-family:Arial;font-size:8px;text-anchor:end" dominant-baseline="baseline" transform="rotate(0, 216, 48)"></text> + <line x1="320" y1="50" x2="340" y2="50" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="50" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 50)">IO15</text> + <text x="324" y="48" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 48)"></text> + <line x1="200" y1="60" x2="220" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="224" y="60" style="font-family:Arial;font-size:10px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 224, 60)">GND</text> + <text x="216" y="58" style="font-family:Arial;font-size:8px;text-anchor:end" dominant-baseline="baseline" transform="rotate(0, 216, 58)"></text> + <line x1="320" y1="60" x2="340" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="60" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 60)">IO27</text> + <text x="324" y="58" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 58)"></text> + <line x1="320" y1="70" x2="340" y2="70" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="70" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 70)">IO14</text> + <text x="324" y="68" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 68)"></text> + <line x1="320" y1="80" x2="340" y2="80" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="80" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 80)">IO13</text> + <text x="324" y="78" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 78)"></text> + <line x1="320" y1="180" x2="340" y2="180" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="180" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 180)">IO25</text> + <text x="324" y="178" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 178)"></text> + <line x1="320" y1="190" x2="340" y2="190" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="190" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 190)">IO26</text> + <text x="324" y="188" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 188)"></text> + <line x1="320" y1="200" x2="340" y2="200" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="200" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 200)">IO16</text> + <text x="324" y="198" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 198)"></text> + <line x1="320" y1="210" x2="340" y2="210" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="210" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 210)">GND</text> + <text x="324" y="208" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 208)"></text> + <line x1="320" y1="220" x2="340" y2="220" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="220" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 220)">3V3</text> + <text x="324" y="218" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 218)"></text> + <ellipse cx="380" cy="220" rx="2" ry="2" style="fill-opacity:1;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> +</svg>
\ No newline at end of file diff --git a/_projects/etlas/dash.jpg b/_projects/etlas/dash.jpg Binary files differnew file mode 100644 index 0000000..cf4efc6 --- /dev/null +++ b/_projects/etlas/dash.jpg diff --git a/_projects/etlas/etlas_arch.png b/_projects/etlas/etlas_arch.png Binary files differnew file mode 100644 index 0000000..241e9f1 --- /dev/null +++ b/_projects/etlas/etlas_arch.png diff --git a/_projects/etlas/index.md b/_projects/etlas/index.md new file mode 100644 index 0000000..97d92a9 --- /dev/null +++ b/_projects/etlas/index.md @@ -0,0 +1,65 @@ +--- +title: "Etlas: e-paper display for news, stocks, and the weather" +date: 2024-09-05T00:00:00+08:00 +author: W. D. Sadeep Madurange +--- + +Etlas is a news, stock market, and weather tracker powered by an ESP32 NodeMCU +D1, featuring a 7.5-inch {{< link src="https://www.waveshare.com/" +class="external" target="_blank" rel="noopener noreferrer">}}Waveshare{{< +/link>}} e-paper display and a DHT22 sensor module. + +<table style="border: none;"> + <tr style="border: none;"> + <td style="border: none;"><img src="dash.jpg" alt="front" style="width: 100%"></td> + <td style="border: none;"><img src="pcb.jpg" alt="back" style="width: 100%"></td> + </tr> +</table> + +The top left panel displays the end-of-day stock prices from the {{< link +src="https://polygon.io/" class="external" target="_blank" +rel="noopener noreferrer" >}}Polygon.io{{</link >}} API, relayed through my own +FastCGI-wrapped Flask app hosted on a VPS. The stock symbols can be configured +through the Flask app's application settings. The server.fcgi script enclosed +in the tarball at the end of the page contains the Flask app. + +The following diagram outlines this system architecture. + + + +Unlike my [e-reader](/projects/e-reader), which worked with raster images, +Etlas downloads time series data as CSV and computes the price curves on the +ESP32. + +The more prominent panel on the right of the e-paper display shows local +(Singapore) and world news from the {{< link +src="https://www.channelnewsasia.com/" class="external" target="_blank" +rel="noopener noreferrer" >}}Channel News Asia{{< /link>}} RSS feed. The MCU +downloads and parses XML data from the RSS feed directly before rendering it to +the display. Although I did it this way to avoid writing server code, it limits +the feeds from which Etlas can receive data. In a future version, I will relay +the RSS feed through a server (like the stock prices) to make it more flexible. + +The bottom panels (middle and right) display the temperature and relative +humidity from a DHT22 sensor. The DHT22 driver, arguably the most interesting +part of the software, reads real-time sensor data by comparing relative pulse +widths. The pulses themselves are too quick for the ESP32 to reliably measure +directly. I ported {{< link src="https://github.com/Fonger/ESP8266-RTOS-DHT" +class="external" target="_blank" rel="noopener noreferrer" >}}this{{< /link>}} +implementation for ESP8266 modules to my ESP32. All credit for the algorithm +belongs to them. + +Much of the heavy lifting of acquiring, interpreting, and rendering data from +different data sources is performed on the microcontroller using less than 512 +KB of memory. The embedded software that makes that possible is written in C +using the ESP-IDF v5.2.1. My e-paper display driver is a port of Waveshare {{< +link src="https://github.com/waveshareteam/e-Paper" class="external" +target="_blank" rel="noopener noreferrer" >}}examples{{< /link>}} for Arduino +and STM32 platforms. + +I've been using Etlas daily (for a couple of hours on weekdays and all day on +weekends) since August 2024. As of October 2025, it's been running reliably for +over a year. If you are interested in an e-paper display like this, drop me an +email at the address on my home page. + +Files: [source.tar.gz](source.tar.gz) diff --git a/_projects/etlas/pcb.jpg b/_projects/etlas/pcb.jpg Binary files differnew file mode 100644 index 0000000..fcb40fa --- /dev/null +++ b/_projects/etlas/pcb.jpg diff --git a/_projects/etlas/schematic.svg b/_projects/etlas/schematic.svg new file mode 100644 index 0000000..3070dd1 --- /dev/null +++ b/_projects/etlas/schematic.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Do not edit this file with editors other than diagrams.net --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg" style="background-color: rgb(255, 255, 255);" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="741px" height="371px" viewBox="-0.5 -0.5 741 371" content="<mxfile host="app.diagrams.net" modified="2024-01-04T10:11:48.655Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0" etag="QhztmAZcJcNMJFkbNl2E" version="21.2.9" type="device"><diagram name="Page-1" id="Hvsc3-8LFRnBvTan2Q2q">7VzbcuI4EP0aHkPZEr49gpNJqjbJpoYku5mXlMdWwLsGMbZIYL9+JVvyVYAJBpOBqtQgt2RJ7nPU6m7Z04H2ZHEdOrPxHfZQ0AGKt+jAyw4AqmIZ9IdJlolEs5REMAp9jzfKBEP/PyTu5NK576Go0JBgHBB/VhS6eDpFLinInDDEH8VmbzgojjpzRqgiGLpOUJX+5XtknEhNTcnkN8gfjcXIqsJrJo5ozAXR2PHwR04ErzrQDjEmSWmysFHAlCf0ktz3bUVtOrEQTUmdG66NwIeLl/HLD+sFfn8e2PbTjwutl3Tz7gRz/sR8tmQpVBDi+dRDrBelAwcfY5+g4cxxWe0HBZ3KxmQS0CuVFiMS4n+RjQMcUskUT2mzAR8DhQQtVs5eTXVCyYTwBJFwSZvwG3qgqyX3cCLpPSH4yHAxelzZ4xwm0OBCh3NhlPaeqYsWuMa20J4gaU5ZyKP04Zc4JGM8wlMnuMqkg6I6sza3GM+4Ev9BhCz5WnDmBBdVjBY++ZvfzsovufLlIn/BVKWJqwcU+vSxUShaTL0+WyAZTFTyzWca4PVURfE4XcvqCUE8WFftpYJsxPhqmb8qD5loi6loPQuoRvE8dNE61XNb4IQjRNa0s+SsClHgEP+9OA8ZP/itD9inM0zZCATPOBnVMsWSefG7MpZRhTvLXLMZaxCtGQdY0nEy0iY9ZhROn3EHVldNwNGx2ljNas5atchYoG0k7Prl8AW5u5ttg8fPAmtbFkBjMw0kduvMjdIke8fPDeqJbUmOs4XYkgXaF2AB3MyCrqLoRSboNXaLs5nYTBD9CxBkjX8sNxM1tpAzDfKD98w2aaDWo8EaV+Lo0DT2EtNoZjGmAfp+YhrNVAvjQPUQMU2nnOa4Gj5AJrrzpz79ucceurOfaIkyBlTj+jR7wzD3nGicctMJ/NGUll2KN2NNNRsiEkexZOzMWJeTxYglzLooQC4JfdcJugEe+e7ryCEo6v56m7367op0S+D8RMG9M+F5MRs+U1/drv5d318mBdWS1m/8Yzu8DQz2j84u2TDM97OBJmkPBzN/esvm9riMn9GdR6SZ7A9USvG2QCiX+wGA+yP53E+afWvcqmntxEdZUkYrbEsb9qQGrZRR00oBs81Nx6gs+Mubx12Tmw0wWdP1IpPNKpNVkZXNM9kw96Qpq2oaL2bUSIVUeNN/bF1jqlLcL1RNojFdkvdVyxtYYyoD1e2kagwyv8ENnCiKrXlOM0U11l2fVS3ltKBJlCBkOzoHKpBv2mkXidmoOAebO6qZOW3KEQBtm21TLQSb6gbD3XagKZh93CGGmOW6BRn7pSi8ekfMPU00zwwT871ir+UBRz7xMfPlfmJC8CRx1z7h9olu+7wNYTRZ5/pFtJ0TvCZoRN1ZiAmto2p7RU7IlncThhSoxcMEKDGkUh9qb2a0evgIu/C5ghx9ZCLzq0unjG90AZREFZDKyEx8z4uXuGwfk5nonRCwVti+HACyjQzuzYdVK7o+iDE8nPXSd7RK8k1Mh0Unjsa0tTaxbUNloxT2iHH2GioD/WxLN65k3TSLSxm2bUvNE7OlujigXIPAQY2p6Ph0goLUgShDsG1QUOnIPGxQAKvx3O+9eCAoKdywWl487Zwop2HZ6pCMnwwcVVAG6wZlK1hwmKBMzPLsSKx7r9EoORJG9a3GgzoSUJOgpgfM7r3h2Ghn8Om/5lhUXETxKuvTBtZsEStHVNPSiP3aQ9ETnVnSWVLx+xpaIF6VSZOX9QytSFM1D2/VT2wK3kv7DG/NfXR/8Faz+U3B+334eIL4giK+Vsv49mRBRkPW+faPk8eXAiz5quCwCMvOlppB+O7P4cvpQawUHSwgdsDWAJa5xc0APHg6SYCLxxqgbRerB/cG8LN9ej5W6dAEmPUM9N5SFT3ZJ3PNwBu/sHVi8AJFLX3Z1z7C+wuBT3EBlzP1QCT7WsNXdsJ1XsGfPwvTy/hKnGjxJeNhEK6+/tdYlqP/2D85iDVdK0NcARhKspSfAJheZt/EJwc82f8sAK/+Bw==</diagram></mxfile>"><defs/><g><rect x="0.5" y="0.5" width="740" height="370" fill="rgb(255, 255, 255)" stroke="none" pointer-events="all"/><path d="M 258 106 L 187 106 L 187 109 L 218.04 109.04" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 258 126 L 219 126" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 258 146 L 219 146" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 258 166 L 219 166" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 258 186 L 219.96 186" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 258 206 L 219 206" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 483 246 L 539 246 L 539 194" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 273 66 L 468 66 L 473 71 L 473 261 L 468 266 L 273 266 L 268 261 L 268 71 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="277.5" y="109.3">15</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="277.5" y="129.3">27</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="277.5" y="149.3">26</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="277.5" y="169.3">13</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="277.5" y="189.3">14</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="277.5" y="209.3">25</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="462.5" y="229.3">19</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="307.5" y="259.3">3V3</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="447.5" y="259.3">GND</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><path d="M 258 86 L 268 86 M 473 86 L 483 86 M 258 106 L 268 106 M 473 106 L 483 106 M 258 126 L 268 126 M 473 126 L 483 126 M 258 146 L 268 146 M 473 146 L 483 146 M 258 166 L 268 166 M 473 166 L 483 166 M 258 186 L 268 186 M 473 186 L 483 186 M 258 206 L 268 206 M 473 206 L 483 206 M 258 226 L 268 226 M 473 226 L 483 226 M 258 246 L 268 246 M 473 246 L 483 246 M 288 56 L 288 66 M 288 266 L 288 276 M 308 56 L 308 66 M 308 266 L 308 276 M 328 56 L 328 66 M 328 266 L 328 276 M 348 56 L 348 66 M 348 266 L 348 276 M 368 56 L 368 66 M 368 266 L 368 276 M 388 56 L 388 66 M 388 266 L 388 276 M 408 56 L 408 66 M 408 266 L 408 276 M 428 56 L 428 66 M 428 266 L 428 276 M 448 56 L 448 66 M 448 266 L 448 276" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><ellipse cx="278" cy="256" rx="5" ry="5" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 223px; height: 1px; padding-top: 166px; margin-left: 259px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">ESP32 Mini NodeMCU D1 </div></div></div></foreignObject><text x="371" y="170" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">ESP32 Mini NodeMCU D1 </text></switch></g><path d="M 601 194 L 601 259.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 601 264.88 L 597.5 257.88 L 601 259.63 L 604.5 257.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="524" y="116" width="154" height="78" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 152px; height: 1px; padding-top: 155px; margin-left: 525px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">DHT22</div></div></div></foreignObject><text x="601" y="159" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">DHT22</text></switch></g><rect x="59" y="86" width="160" height="160" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 166px; margin-left: 60px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">E-paper HAT</div></div></div></foreignObject><text x="139" y="170" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">E-paper HAT</text></switch></g><path d="M 79 246 L 79 299.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 79 304.88 L 75.5 297.88 L 79 299.63 L 82.5 297.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 189.5 286 L 189.5 266 L 189.56 246" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><rect x="177" y="286" width="25" height="20" fill="none" stroke="none" pointer-events="all"/><path d="M 177 291 L 202 291 M 179 293.5 L 200 293.5 M 181 296 L 198 296 M 185.25 301 L 193.75 301 M 187.25 303.5 L 191.75 303.5 M 189.5 286 L 189.5 291 M 183.25 298.5 L 195.75 298.5 M 189.25 306 L 189.75 306" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="49" y="306" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 321px; margin-left: 50px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">3.3V</div></div></div></foreignObject><text x="79" y="325" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">3.3V</text></switch></g><path d="M 658.5 266 L 658.5 194 L 594 194" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><rect x="646" y="266" width="25" height="20" fill="none" stroke="none" pointer-events="all"/><path d="M 646 271 L 671 271 M 648 273.5 L 669 273.5 M 650 276 L 667 276 M 654.25 281 L 662.75 281 M 656.25 283.5 L 660.75 283.5 M 658.5 266 L 658.5 271 M 652.25 278.5 L 664.75 278.5 M 658.25 286 L 658.75 286" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="571" y="266" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 281px; margin-left: 572px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">3.3V</div></div></div></foreignObject><text x="601" y="285" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">3.3V</text></switch></g><path d="M 308 266 L 308 309.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 308 314.88 L 304.5 307.88 L 308 309.63 L 311.5 307.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="278" y="315" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 330px; margin-left: 279px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">3.3V</div></div></div></foreignObject><text x="308" y="334" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">3.3V</text></switch></g><path d="M 448.5 311 L 448.5 291 L 448 276" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><rect x="436" y="311" width="25" height="20" fill="none" stroke="none" pointer-events="all"/><path d="M 436 316 L 461 316 M 438 318.5 L 459 318.5 M 440 321 L 457 321 M 444.25 326 L 452.75 326 M 446.25 328.5 L 450.75 328.5 M 448.5 311 L 448.5 316 M 442.25 323.5 L 454.75 323.5 M 448.25 331 L 448.75 331" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="172" y="95" width="60" height="23" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 107px; margin-left: 173px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">CS</font></div></div></div></foreignObject><text x="202" y="110" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">CS</text></switch></g><rect x="172" y="115" width="60" height="23" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 127px; margin-left: 173px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">DC</font></div></div></div></foreignObject><text x="202" y="130" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">DC</text></switch></g><rect x="170" y="135" width="60" height="23" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 147px; margin-left: 171px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">RST</font></div></div></div></foreignObject><text x="200" y="150" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">RST</text></switch></g><rect x="170" y="155.5" width="60" height="23" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 167px; margin-left: 171px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">CLK</font></div></div></div></foreignObject><text x="200" y="171" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">CLK</text></switch></g><rect x="166" y="174" width="60" height="23" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 186px; margin-left: 167px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">MOSY</font></div></div></div></foreignObject><text x="196" y="189" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">MOSY</text></switch></g><rect x="167" y="195" width="60" height="23" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 207px; margin-left: 168px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">BUSY</font></div></div></div></foreignObject><text x="197" y="210" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">BUSY</text></switch></g><rect x="49" y="221" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 236px; margin-left: 50px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">VCC</font></div></div></div></foreignObject><text x="79" y="240" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">VCC</text></switch></g><rect x="159.5" y="221" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 236px; margin-left: 161px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">GND</font></div></div></div></foreignObject><text x="190" y="240" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">GND</text></switch></g><rect x="571" y="170" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 185px; margin-left: 572px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">VCC</font></div></div></div></foreignObject><text x="601" y="189" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">VCC</text></switch></g><rect x="644" y="170.5" width="29" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 27px; height: 1px; padding-top: 186px; margin-left: 645px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">GND</font></div></div></div></foreignObject><text x="659" y="189" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">GND</text></switch></g><rect x="523" y="170" width="35" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 33px; height: 1px; padding-top: 185px; margin-left: 524px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">DATA</font></div></div></div></foreignObject><text x="541" y="189" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">DATA</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.drawio.com/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Text is not SVG - cannot display</text></a></switch></svg>
\ No newline at end of file diff --git a/_projects/etlas/source.tar.gz b/_projects/etlas/source.tar.gz Binary files differnew file mode 100644 index 0000000..8b12cf6 --- /dev/null +++ b/_projects/etlas/source.tar.gz diff --git a/_projects/fpm-door-lock/breadboard.jpg b/_projects/fpm-door-lock/breadboard.jpg Binary files differnew file mode 100644 index 0000000..2bf47a9 --- /dev/null +++ b/_projects/fpm-door-lock/breadboard.jpg diff --git a/_projects/fpm-door-lock/footprint.png b/_projects/fpm-door-lock/footprint.png Binary files differnew file mode 100644 index 0000000..5511bf1 --- /dev/null +++ b/_projects/fpm-door-lock/footprint.png diff --git a/_projects/fpm-door-lock/gerber.zip b/_projects/fpm-door-lock/gerber.zip Binary files differnew file mode 100644 index 0000000..19a9d19 --- /dev/null +++ b/_projects/fpm-door-lock/gerber.zip diff --git a/_projects/fpm-door-lock/index.md b/_projects/fpm-door-lock/index.md new file mode 100644 index 0000000..72fd171 --- /dev/null +++ b/_projects/fpm-door-lock/index.md @@ -0,0 +1,112 @@ +--- +title: Prototype fingerprint door lock +date: 2025-10-03T00:00:00+08:00 +author: W. D. Sadeep Madurange +--- + +This project features a fingerprint door lock powered by an ATmega328P +microcontroller. + +<video style="max-width:100%;" controls="" poster="pcb.jpg"> + <source src="video.mp4" type="video/mp4"> +</video> + +## Overview + +The lock comprises three subsystems: the ATmega328P, an R503 fingerprint +sensor, and an FS5106B high-torque servo. The sensor mounted on the front +surface of the door enables users to unlock it from the outside. The servo is +attached to the interior door knob. The MCU must be installed at the back of +the door to prevent unauthorized users from tampering with it. + +When no one is interacting with the lock, the MCU is in deep sleep. The sensor +and the servo each draw 13.8 mA and 4.6 mA of quiescent currents. To prevent +this idle current draw, the MCU employs MOSFETs to cut off power to them before +entering deep sleep. Doing so is crucial for conserving the battery. + +Without power, the sensor remains in a low-power state, drawing approximately +2.9 μA through a separate power rail. When a finger comes into contact with the +sensor, the sensor triggers a pin change interrupt, waking up the MCU. The MCU +activates a MOSFET, which in turn activates the sensor. Over UART, the MCU +unlocks the sensor and issues commands to scan and match the fingerprint. + +If the fingerprint matches an enrolled fingerprint, the MCU activates the blue +LED on the sensor, turns on the MOSFET connected to the servo, and sends a PWM +signal to the servo to unlock the door. Otherwise, the MCU activates the red +LED on the sensor. Finally, the MCU deactivates the MOSFETS and goes back to +sleep. + +## Embedded software + +The embedded software, written in C with the help of the AVR toolchain, +includes a driver for the sensor, servo control routines, and a battery +monitoring system. + +In addition to controlling the sensor and the servo, the program strives to +maintain precise control over the sleep mode, as well as when the peripherals +are activated and for how long they remain active. I thoroughly enjoyed writing +the embedded software. There's something magical about being able to alter the +physical world around you by uttering a few lines of C code. + +The source code of the project, which includes a driver for the R503 +fingerprint sensor module, is enclosed in the tarball linked at the end of the +page. + +## The circuit board + +For this project, I designed a custom PCB and had it fabricated by JLCPCB. Like +the software, the circuit is chiefly concerned with optimizing power +consumption and extending battery life. + +<table style="border: none; width: 100%"> + <tr style="border: none;"> + <td style="border: none; width: 49.9%; background-color: transparent; text-align: center;"> + <img src="breadboard.jpg" alt="PCB" style="width: 100%"> + </td> + <td style="border: none; background-color: transparent; text-align: center;"> + <img src="pcb1.jpg" alt="Design" style="width: 100%"> + </td> + </tr> + <tr style="border: none;"> + <td colspan="2" style="border: none; background-color: transparent; text-align: center;"> + <img src="footprint.png" alt="PCB footprint" style="width: 100%"> + </td> + </tr> +</table> + +To that end, the principal components of the circuit are the 2N7000 and +NDP6020P field-effect transistors. They switch power electronically to the +servo and the fingerprint sensor, the two most power-hungry parts of the +circuit. The two MP1584EN buck converters play an axial role in efficiently +regulating power to the MCU and the sensor. + +The ATmega328P typically operates at 5 V with a 16 MHz crystal oscillator. To +further reduce power consumption, I modified the ATmega328P's fuses to run at +3.3 V with an 8 MHz crystal oscillator. + +The bottom right area of the PCB isolates the power supply of the servo from +the rest of the circuit. This shields components such as the MCU from the +servo's high current draw, which can exceed 1 A. The IN4007 diode in slot U2 +serves as a flyback diode, protecting the MOSFET from reverse currents +generated by the servo. + +Lastly, the 56 kΩ and 10 kΩ resistors in slots R10 and R11 form a voltage +divider circuit. Its output is fed to the ADC of the MCU, which measures the +supply voltage by comparing it to the internal bandgap reference voltage. + +## Epilogue + +This project began nearly a year ago when I attempted to unlock our door +wirelessly by writing to the UART ports of two MCUs connected to inexpensive +433 MHz RF transceivers, as if there were an invisible wire between them. +Although I failed, it led me down a rabbit hole of RF communications, MOSFETs, +PCB design, and low-power circuits. + +During the project, I reinvented the wheel many times. I implemented a +low-level network stack using only RF modules and an 8-bit microcontroller, +designed my first PCB, and developed drivers from scratch. The project was far +from a smooth sail. Bad electrical connections, soldering, desoldering, and the +heartache of purchasing the wrong parts were routine. It was a long but +rewarding journey from the messy breadboard to the shiny PCB. + +Files: [source.tar.gz](source.tar.gz), [gerber.zip](gerber.zip) diff --git a/_projects/fpm-door-lock/pcb.jpg b/_projects/fpm-door-lock/pcb.jpg Binary files differnew file mode 100644 index 0000000..fbd800b --- /dev/null +++ b/_projects/fpm-door-lock/pcb.jpg diff --git a/_projects/fpm-door-lock/pcb1.jpg b/_projects/fpm-door-lock/pcb1.jpg Binary files differnew file mode 100644 index 0000000..367187d --- /dev/null +++ b/_projects/fpm-door-lock/pcb1.jpg diff --git a/_projects/fpm-door-lock/source.tar.gz b/_projects/fpm-door-lock/source.tar.gz Binary files differnew file mode 100644 index 0000000..ef23422 --- /dev/null +++ b/_projects/fpm-door-lock/source.tar.gz diff --git a/_projects/fpm-door-lock/video.mp4 b/_projects/fpm-door-lock/video.mp4 Binary files differnew file mode 100644 index 0000000..a907a9b --- /dev/null +++ b/_projects/fpm-door-lock/video.mp4 diff --git a/_projects/matrix-digital-rain/index.md b/_projects/matrix-digital-rain/index.md new file mode 100644 index 0000000..c469762 --- /dev/null +++ b/_projects/matrix-digital-rain/index.md @@ -0,0 +1,36 @@ +--- +title: The Matrix digital rain for Unix terminals +date: 2024-01-12T00:00:00+08:00 +author: W. D. Sadeep Madurange +--- + +The famous digital rain from the movie The Matrix implemented in C for +the Unix terminal without using any GUI/TUI kits: + +<video style="max-width:100%;" controls="" poster="thumb.png"> + <source src="matrix.mp4" type="video/mp4"> +</video> + +Domsson's {{< link src="https://github.com/domsson/fakesteak" class="external" +target="_blank" rel="noopener noreferrer" >}}Fakesteak{{< /link >}} inspired +this project. I added the following features while trying to keep the original +project's simplicity intact as much as possible: + +- Customize the rain color to match the theme of the setup. +- Support for UTF-32 characters. +- The ghosting effect of old monochrome displays. +- The rain more closely resembles the original from the first movie. + +To use them, you need a terminal emulator that supports 24-bit RGB colors and +Unicode characters. + +The background, head, and tail colors of the rain can be configured via +`COLOR_BG_*`, `COLOR_HD_*`, and `COLOR_TL_*` settings. The `UNICODE_MIN` and +`UNICODE_MAX` values control the character set used for the rain. For instance, +use `0x30A1` and `0x30F6` for Katakana: + +<img style="width: 100%;" src="katakana.png" /> + +Happy ricing! + +Files: [source.tar.gz](source.tar.gz) diff --git a/_projects/matrix-digital-rain/katakana.png b/_projects/matrix-digital-rain/katakana.png Binary files differnew file mode 100644 index 0000000..b9df873 --- /dev/null +++ b/_projects/matrix-digital-rain/katakana.png diff --git a/_projects/matrix-digital-rain/matrix.mp4 b/_projects/matrix-digital-rain/matrix.mp4 Binary files differnew file mode 100644 index 0000000..84a9839 --- /dev/null +++ b/_projects/matrix-digital-rain/matrix.mp4 diff --git a/_projects/matrix-digital-rain/source.tar.gz b/_projects/matrix-digital-rain/source.tar.gz Binary files differnew file mode 100644 index 0000000..fead280 --- /dev/null +++ b/_projects/matrix-digital-rain/source.tar.gz diff --git a/_projects/matrix-digital-rain/thumb.png b/_projects/matrix-digital-rain/thumb.png Binary files differnew file mode 100644 index 0000000..7e008c2 --- /dev/null +++ b/_projects/matrix-digital-rain/thumb.png diff --git a/_projects/post-1/index.md b/_projects/post-1/index.md deleted file mode 100644 index e96cfb0..0000000 --- a/_projects/post-1/index.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: default -author: jill -type: project -date: 2025-10-24 ---- - -An apple is a sweet, edible fruit produced by an apple tree. - -Apple trees are cultivated worldwide, and are the most widely grown -species in the genus Malus. The tree originated in Central Asia, where -its wild ancestor, Malus sieversii, is still found today. Apples have -been grown for thousands of years in Asia and Europe, and were brought -to North America by European colonists. diff --git a/_site/about.html b/_site/about/index.html index 1274d2a..6546e0b 100644 --- a/_site/about.html +++ b/_site/about/index.html @@ -14,8 +14,11 @@ <li > <a href="/">hme</a> </li> + <li > + <a href="/projects/">tnk</a> + </li> <li class="active"> - <a href="/about.html">abt</a> + <a href="/about/">abt</a> </li> <li><a href="/feed.xml">rss</a></li> </ul> diff --git a/_site/assets/css/main.css b/_site/assets/css/main.css index e58a791..0a787f2 100644 --- a/_site/assets/css/main.css +++ b/_site/assets/css/main.css @@ -32,6 +32,11 @@ img { mix-blend-mode: multiply; } +table { + border: none; + width: 100%; +} + #navlist > .active > a { color: #000; font-weight: 600; @@ -157,3 +162,4 @@ and (min-device-width : 768px) { margin-right:0; } } + diff --git a/_site/blog/post-1/index.html b/_site/blog/post-1/index/index.html index db0b37e..de0e7ef 100644 --- a/_site/blog/post-1/index.html +++ b/_site/blog/post-1/index/index.html @@ -15,7 +15,10 @@ <a href="/">hme</a> </li> <li > - <a href="/about.html">abt</a> + <a href="/projects/">tnk</a> + </li> + <li > + <a href="/about/">abt</a> </li> <li><a href="/feed.xml">rss</a></li> </ul> diff --git a/_site/index.html b/_site/index.html index c60b34a..22feae3 100644 --- a/_site/index.html +++ b/_site/index.html @@ -15,7 +15,10 @@ <a href="/">hme</a> </li> <li > - <a href="/about.html">abt</a> + <a href="/projects/">tnk</a> + </li> + <li > + <a href="/about/">abt</a> </li> <li><a href="/feed.xml">rss</a></li> </ul> @@ -26,14 +29,53 @@ <li> - <h2><a href="/blog/post-1/index.html">Index</a></h2> + <h2><a href="/blog/post-1/index/">Index</a></h2> <p>An apple is a sweet, edible fruit produced by an apple tree.</p> </li> <li> - <h2><a href="/projects/post-1/index.html">Index</a></h2> - <p>An apple is a sweet, edible fruit produced by an apple tree.</p> + <h2><a href="/projects/e-reader/index/">Prototype e-reader</a></h2> + <p>This project features a prototype e-reader powered by a 7.5-inch Waveshare e-paper display and an +ESP-WROOM-32 development board.</p> + + </li> + + <li> + <h2><a href="/projects/matrix-digital-rain/index/">The Matrix digital rain for Unix terminals</a></h2> + <p>The famous digital rain from the movie The Matrix implemented in C for +the Unix terminal without using any GUI/TUI kits:</p> + + </li> + + <li> + <h2><a href="/projects/etlas/index/">Etlas: e-paper display for news, stocks, and the weather</a></h2> + <p>Etlas is a news, stock market, and weather tracker powered by an ESP32 NodeMCU +D1, featuring a 7.5-inch Waveshare e-paper display and a DHT22 sensor module.</p> + + </li> + + <li> + <h2><a href="/projects/bumblebee/index/">Bumblebee: turn browser sessions to code</a></h2> + <p>Bumblebee is a web browser that turns browser sessions into C# scripts. Its +objective is to eliminate the need for authoring scripts for testing, web +scraping, and other browser automation tasks.</p> + + </li> + + <li> + <h2><a href="/projects/desktop-unix/index/">Unix for the desktop</a></h2> + <p>The year 2020 transformed my personal computing experience. In March 2020, I +stumbled upon Arch Linux and discovered that I could customize my desktop +environment to look and work any way I liked. I exercised that newfound freedom +to create a Matrix-themed setup:</p> + + </li> + + <li> + <h2><a href="/projects/fpm-door-lock/index/">Prototype fingerprint door lock</a></h2> + <p>This project features a fingerprint door lock powered by an ATmega328P +microcontroller.</p> </li> diff --git a/_site/projects/bumblebee/bee.mp4 b/_site/projects/bumblebee/bee.mp4 Binary files differnew file mode 100644 index 0000000..835600d --- /dev/null +++ b/_site/projects/bumblebee/bee.mp4 diff --git a/_site/projects/bumblebee/index/index.html b/_site/projects/bumblebee/index/index.html new file mode 100644 index 0000000..162a9c0 --- /dev/null +++ b/_site/projects/bumblebee/index/index.html @@ -0,0 +1,18 @@ +<p>Bumblebee is a web browser that turns browser sessions into C# scripts. Its +objective is to eliminate the need for authoring scripts for testing, web +scraping, and other browser automation tasks.</p> + +<video style="max-width:100%; margin-bottom: 10px" controls="" poster="thumb.png"> + <source src="bee.mp4" type="video/mp4" /> +</video> + +<p>Bumblebee is a Windows Forms application written in C#. The rendering of web +content is handled by the embedded Microsoft Edge browser (via WebView). The +text editor on the right is Scintilla.NET. It +enables users to override the script at any point during the session. There are +application settings that let users debounce events, ignore hidden elements +and scripts, and more.</p> + +<p>Unfortunately, I can’t share the source code for Bumblebee. I developed it for +an employer. Hence, the software is proprietary.</p> + diff --git a/_site/projects/bumblebee/thumb.png b/_site/projects/bumblebee/thumb.png Binary files differnew file mode 100644 index 0000000..1e0db2b --- /dev/null +++ b/_site/projects/bumblebee/thumb.png diff --git a/_site/projects/desktop-unix/dotfiles.tar.gz b/_site/projects/desktop-unix/dotfiles.tar.gz Binary files differnew file mode 100644 index 0000000..c6774c4 --- /dev/null +++ b/_site/projects/desktop-unix/dotfiles.tar.gz diff --git a/_site/projects/desktop-unix/index/index.html b/_site/projects/desktop-unix/index/index.html new file mode 100644 index 0000000..8566dea --- /dev/null +++ b/_site/projects/desktop-unix/index/index.html @@ -0,0 +1,23 @@ +<p>The year 2020 transformed my personal computing experience. In March 2020, I +stumbled upon Arch Linux and discovered that I could customize my desktop +environment to look and work any way I liked. I exercised that newfound freedom +to create a Matrix-themed setup:</p> + +<p><img src="linux.png" alt="Arch Linux" /></p> + +<p>The system employs the X display server and the i3 window manager. The terminal +emulator used is Urxvt. The translucent effect is achieved with the help of the +Xcompmgr compositor. This sort of setup was popular among minimalist Linux +users.</p> + +<p>In February 2024, I switched to an OpenBSD system with Xenocara (the OpenBSD +build of X display server) as the display server and i3 as the window manager:</p> + +<p><img src="openbsd.png" alt="OpenBSD i3" /></p> + +<p>Unlike Linux, OpenBSD includes a coherent desktop environment out of the box. +Except for the window manager, for which I prefer a tiling one, I’m now using +the default OpenBSD setup. For the window manager, I use dwm from the Suckless +team.</p> + +<p>Files: <a href="dotfiles.tar.gz">dotfiles.tar.gz</a></p> diff --git a/_site/projects/desktop-unix/linux.png b/_site/projects/desktop-unix/linux.png Binary files differnew file mode 100644 index 0000000..40bc33b --- /dev/null +++ b/_site/projects/desktop-unix/linux.png diff --git a/_site/projects/desktop-unix/openbsd.png b/_site/projects/desktop-unix/openbsd.png Binary files differnew file mode 100644 index 0000000..d7489f7 --- /dev/null +++ b/_site/projects/desktop-unix/openbsd.png diff --git a/_site/projects/e-reader/circuit.svg b/_site/projects/e-reader/circuit.svg new file mode 100644 index 0000000..fd7508b --- /dev/null +++ b/_site/projects/e-reader/circuit.svg @@ -0,0 +1,145 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Circuit Diagram, cdlibrary.dll 4.0.0.0 --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg version="1.1" width="680" height="360" xmlns="http://www.w3.org/2000/svg"> + <line x1="360" y1="130" x2="360" y2="330" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="70" y1="330" x2="360" y2="330" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="70" y1="250" x2="70" y2="260" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="70" y1="260" x2="70" y2="275" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="70" y1="315" x2="70" y2="330" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 70,275 L 70,277 L 63,280 L 77,286 L 63,292 L 77,298 L 63,304 L 77,310 L 70,313 L 70,315" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="56" y="295" style="font-family:Arial;font-size:11px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 56, 295)">10 kΩ</text> + <line x1="70" y1="250" x2="100" y2="250" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="130" y1="250" x2="190" y2="250" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="100" y1="250" x2="101" y2="250" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="129" y1="250" x2="130" y2="250" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <ellipse cx="104" cy="250" rx="3" ry="3" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <ellipse cx="126" cy="250" rx="3" ry="3" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <line x1="103" y1="242" x2="127" y2="242" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="109" y1="236" x2="121" y2="236" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="115" y1="236" x2="115" y2="242" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="70" y1="140" x2="190" y2="140" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="70" y1="60" x2="70" y2="140" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="70" y1="100" x2="100" y2="100" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="130" y1="100" x2="190" y2="100" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="70" y1="60" x2="100" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="130" y1="60" x2="190" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="100" y1="100" x2="101" y2="100" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="129" y1="100" x2="130" y2="100" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <ellipse cx="104" cy="100" rx="3" ry="3" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <ellipse cx="126" cy="100" rx="3" ry="3" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <line x1="103" y1="92" x2="127" y2="92" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="109" y1="86" x2="121" y2="86" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="115" y1="86" x2="115" y2="92" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="100" y1="60" x2="101" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="129" y1="60" x2="130" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <ellipse cx="104" cy="60" rx="3" ry="3" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <ellipse cx="126" cy="60" rx="3" ry="3" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <line x1="103" y1="52" x2="127" y2="52" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="109" y1="46" x2="121" y2="46" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="115" y1="46" x2="115" y2="52" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="310" y1="130" x2="410" y2="130" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="310" y1="120" x2="410" y2="120" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="310" y1="110" x2="410" y2="110" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="310" y1="100" x2="410" y2="100" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="310" y1="90" x2="410" y2="90" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="310" y1="80" x2="410" y2="80" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="310" y1="70" x2="410" y2="70" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="310" y1="60" x2="420" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="450" y1="160" x2="500" y2="160" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:3" /> + <path d="M 430,120 L 430,110 L 420,100 L 410,100" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 430,120 L 420,110 L 410,110" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 430,130 L 420,120 L 410,120" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 430,140 L 420,130 L 410,130" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 510,180 L 520,190 L 530,190" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 510,190 L 520,200 L 530,200" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 510,200 L 520,210 L 530,210" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 510,200 L 510,210 L 520,220 L 530,220" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="524" y="185" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 524, 185)">1</text> + <text x="524" y="195" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 524, 195)">2</text> + <text x="524" y="205" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 524, 205)">3</text> + <text x="524" y="215" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 524, 215)">4</text> + <line x1="445" y1="155" x2="450" y2="165" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:1" /> + <line x1="495" y1="155" x2="490" y2="165" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:1" /> + <path d="M 455,160 L 440,160 L 430,150 L 430,80" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:3" /> + <path d="M 485,160 L 500,160 L 510,170 L 510,240" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:3" /> + <path d="M 430,80 L 430,70 L 420,60 L 410,60" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 430,80 L 420,70 L 410,70" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 430,90 L 420,80 L 410,80" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 430,100 L 420,90 L 410,90" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="413" y="55" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 413, 55)">1</text> + <text x="413" y="65" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 413, 65)">2</text> + <text x="413" y="75" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 413, 75)">3</text> + <text x="413" y="85" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 413, 85)">4</text> + <text x="413" y="95" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 413, 95)">5</text> + <text x="413" y="105" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 413, 105)">6</text> + <text x="413" y="115" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 413, 115)">7</text> + <text x="413" y="125" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 413, 125)">8</text> + <path d="M 510,220 L 520,230 L 530,230" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 510,230 L 520,240 L 530,240" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 510,240 L 520,250 L 530,250" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 510,240 L 510,250 L 520,260 L 530,260" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="524" y="225" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 524, 225)">5</text> + <text x="524" y="235" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 524, 235)">6</text> + <text x="524" y="245" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 524, 245)">7</text> + <text x="524" y="255" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 524, 255)">8</text> + <text x="470" y="155" style="font-family:Arial;font-size:11px;text-anchor:middle" dominant-baseline="baseline" transform="rotate(0, 470, 155)">BUS</text> + <text x="590" y="170" style="font-family:Arial;font-size:11px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 590, 170)">E-Paper Display HAT</text> + <rect x="540" y="180" width="100" height="90" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <line x1="530" y1="190" x2="540" y2="190" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="190" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 544, 190)">CS</text> + <line x1="530" y1="200" x2="540" y2="200" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="200" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 544, 200)">DC</text> + <line x1="530" y1="210" x2="540" y2="210" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="210" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 544, 210)">DIN</text> + <line x1="530" y1="220" x2="540" y2="220" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="220" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 544, 220)">CLK</text> + <line x1="530" y1="230" x2="540" y2="230" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="230" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 544, 230)">BUSY</text> + <line x1="530" y1="240" x2="540" y2="240" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="240" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 544, 240)">RST</text> + <line x1="530" y1="250" x2="540" y2="250" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="250" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 544, 250)">GND</text> + <line x1="530" y1="260" x2="540" y2="260" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="260" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 544, 260)">VCC</text> + <rect x="200" y="50" width="100" height="210" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <text x="250" y="40" style="font-family:Arial;font-size:11px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 250, 40)">ESP-WROOM-32</text> + <line x1="190" y1="60" x2="200" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="204" y="60" style="font-family:Arial;font-size:10px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 204, 60)">IO21</text> + <text x="196" y="58" style="font-family:Arial;font-size:8px;text-anchor:end" dominant-baseline="baseline" transform="rotate(0, 196, 58)"></text> + <line x1="300" y1="60" x2="310" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="296" y="60" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 296, 60)">IO5</text> + <text x="304" y="58" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 304, 58)"></text> + <line x1="300" y1="70" x2="310" y2="70" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="296" y="70" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 296, 70)">IO16</text> + <text x="304" y="68" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 304, 68)"></text> + <line x1="300" y1="80" x2="310" y2="80" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="296" y="80" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 296, 80)">IO23</text> + <text x="304" y="78" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 304, 78)"></text> + <line x1="300" y1="90" x2="310" y2="90" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="296" y="90" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 296, 90)">IO18</text> + <text x="304" y="88" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 304, 88)"></text> + <line x1="190" y1="100" x2="200" y2="100" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="204" y="100" style="font-family:Arial;font-size:10px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 204, 100)">IO22</text> + <text x="196" y="98" style="font-family:Arial;font-size:8px;text-anchor:end" dominant-baseline="baseline" transform="rotate(0, 196, 98)"></text> + <line x1="300" y1="100" x2="310" y2="100" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="296" y="100" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 296, 100)">IO4</text> + <text x="304" y="98" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 304, 98)"></text> + <line x1="300" y1="110" x2="310" y2="110" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="296" y="110" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 296, 110)">IO2</text> + <text x="304" y="108" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 304, 108)"></text> + <line x1="300" y1="120" x2="310" y2="120" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="296" y="120" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 296, 120)">GND</text> + <text x="304" y="118" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 304, 118)"></text> + <line x1="300" y1="130" x2="310" y2="130" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="296" y="130" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 296, 130)">3V3</text> + <text x="304" y="128" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 304, 128)"></text> + <line x1="190" y1="140" x2="200" y2="140" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="204" y="140" style="font-family:Arial;font-size:10px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 204, 140)">GND</text> + <text x="196" y="138" style="font-family:Arial;font-size:8px;text-anchor:end" dominant-baseline="baseline" transform="rotate(0, 196, 138)"></text> + <line x1="190" y1="250" x2="200" y2="250" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="204" y="250" style="font-family:Arial;font-size:10px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 204, 250)">IO15</text> + <text x="196" y="248" style="font-family:Arial;font-size:8px;text-anchor:end" dominant-baseline="baseline" transform="rotate(0, 196, 248)"></text> + <ellipse cx="360" cy="130" rx="2" ry="2" style="fill-opacity:1;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <ellipse cx="70" cy="100" rx="2" ry="2" style="fill-opacity:1;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> +</svg>
\ No newline at end of file diff --git a/_site/projects/e-reader/ereader.mp4 b/_site/projects/e-reader/ereader.mp4 Binary files differnew file mode 100644 index 0000000..a57a5b1 --- /dev/null +++ b/_site/projects/e-reader/ereader.mp4 diff --git a/_site/projects/e-reader/index/index.html b/_site/projects/e-reader/index/index.html new file mode 100644 index 0000000..dd58a14 --- /dev/null +++ b/_site/projects/e-reader/index/index.html @@ -0,0 +1,81 @@ +<p>This project features a prototype e-reader powered by a 7.5-inch Waveshare e-paper display and an +ESP-WROOM-32 development board.</p> + +<p><video style=”max-width:100%” controls=”” poster=thumb.png></p> +<source src="ereader.mp4" type="video/mp4" /> + +<p></video></p> + +<h2 id="overview">Overview</h2> + +<p>In 2017, during a short stint as a project manager, I was tasked with +installing some e-paper displays in a car park. Not knowing how they worked, I +remember marveling at their sight like a muggle witnessing magic. As someone +who enjoys reading, I found e-paper to be a true innovation. This project was +born out of that enduring curiosity and love of e-paper technology.</p> + +<p>The prototype, while far from ready for daily use, has some nifty features that +fellow hobbyists and tinkerers may find interesting. The reader can display +books of arbitrary sizes by streaming them over HTTP. It employs sleep modes to +minimize power consumption when not in use and records the reading progress in +the chip’s RTC memory.</p> + +<p>The following schematic outlines the electrical connections of the e-reader.</p> + +<p><img src="circuit.svg" alt="circuit" /></p> + +<p>The biggest challenge when building an e-reader using an ESP32 board is its low +memory and lack of storage. My ESP-WROOM-32 board has 512 KB of SRAM and 4 MB +of flash memory, which the freeRTOS, ESP-IDF, and my own program have to share. +To put things into perspective, compare that to a Kindle Paperwhite, which has +at least 256 MB of memory and 8 GB of storage.</p> + +<p>Despite its constraints, as microcontrollers go, ESP32 is a powerful +system-on-a-chip with a 160 MHz dual-core processor and integrated WiFi. So, I +thought it’d be amusing to embrace the constraints and build my e-reader using +just a $5 MCU and the power of C programming.</p> + +<h2 id="the-file-format">The file format</h2> + +<p>The file format dictates the complexity of the embedded software. So, I’ll +begin there. The e-reader works by downloading and rendering a rasterized +monochrome image of a page (a .ebm file).</p> + +<p>The EBM file contains a series of bitmaps, where each bitmap corresponds to a +page of a book sized to fit the e-paper display. Each byte contains information +for rendering eight pixels. For my display, which has a resolution of 480x800, +the bitmaps are laid out along 48 KB boundaries. This simple file format lends +well to HTTP streaming, which is its main advantage, as we will soon see.</p> + +<p>The enclosed pdftoebm.py script in the tarball at the end of the page converts +PDF documents to an EBM file. I use it to make EBM files before uploading them +to a web server.</p> + +<h2 id="how-does-it-work">How does it work?</h2> + +<p>As the e-reader has no storage, it can’t store books locally. Instead, I first +have to upload the EBM file I want to read to a web server. The location of the +file is configured via the <code class="language-plaintext highlighter-rouge">EBM_ARCH_URL</code> setting in the Kconfig.projbuild +file. To read a different book, I create an EBM file with the same name and +upload it to the original location. That way, I don’t have to recompile the +embedded software.</p> + +<p>Upon powering up, the e-reader checks the reading progress stored in the RTC +memory. It then downloads three pages (current, previous, and next) to a +circular buffer in DMA-capable memory. When the user turns a page, one of the +two cores of the MCU transfers it from the buffer to the display over a Serial +Peripheral Interface (SPI). The other downloads a new page in the background. I +use the ESP-IDF task API to pin the two routines to each core.</p> + +<p>I designed the EBM format with HTTP streaming in mind. To download a page based +on the current reading progress, the e-reader specifies the page offset and the +chunk size using the HTTP Range header.</p> + +<h2 id="afterword">Afterword</h2> + +<p>It’s been six years since the car park and the displays. At the time, I knew +nothing about embedded systems or display drivers. It took a long time to +develop the skill set, but now, at last, I know how those displays worked and +how to build my own.</p> + +<p>Files: <a href="source.tar.gz">source.tar.gz</a></p> diff --git a/_site/projects/e-reader/source.tar.gz b/_site/projects/e-reader/source.tar.gz Binary files differnew file mode 100644 index 0000000..3e343a7 --- /dev/null +++ b/_site/projects/e-reader/source.tar.gz diff --git a/_site/projects/e-reader/thumb.png b/_site/projects/e-reader/thumb.png Binary files differnew file mode 100644 index 0000000..1e222d2 --- /dev/null +++ b/_site/projects/e-reader/thumb.png diff --git a/_site/projects/etlas/circuit.svg b/_site/projects/etlas/circuit.svg new file mode 100644 index 0000000..6255045 --- /dev/null +++ b/_site/projects/etlas/circuit.svg @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Circuit Diagram, cdlibrary.dll 4.0.0.0 --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg version="1.1" width="700" height="300" xmlns="http://www.w3.org/2000/svg"> + <line x1="380" y1="220" x2="380" y2="280" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="340" y1="220" x2="550" y2="220" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="340" y1="210" x2="550" y2="210" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="340" y1="200" x2="550" y2="200" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="340" y1="190" x2="550" y2="190" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="340" y1="180" x2="550" y2="180" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="130" y1="50" x2="210" y2="50" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="130" y1="70" x2="160" y2="70" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="160" y1="280" x2="380" y2="280" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="160" y1="70" x2="160" y2="280" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="130" y1="60" x2="210" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="70" y="30" style="font-family:Arial;font-size:11px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 70, 30)">AM2302</text> + <rect x="20" y="40" width="100" height="40" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <line x1="120" y1="50" x2="130" y2="50" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="116" y="50" style="font-family:Arial;font-size:11px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 116, 50)">DATA</text> + <line x1="120" y1="60" x2="130" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="116" y="60" style="font-family:Arial;font-size:11px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 116, 60)">GND</text> + <line x1="120" y1="70" x2="130" y2="70" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="116" y="70" style="font-family:Arial;font-size:11px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 116, 70)">VCC</text> + <text x="610" y="120" style="font-family:Arial;font-size:11px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 610, 120)">E-Paper display HAT</text> + <rect x="560" y="130" width="100" height="100" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <line x1="550" y1="140" x2="560" y2="140" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="140" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 140)">CS</text> + <line x1="550" y1="150" x2="560" y2="150" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="150" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 150)">DC</text> + <line x1="550" y1="160" x2="560" y2="160" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="160" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 160)">DIN</text> + <line x1="550" y1="170" x2="560" y2="170" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="170" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 170)">CLK</text> + <line x1="550" y1="180" x2="560" y2="180" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="180" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 180)">BUSY</text> + <line x1="550" y1="190" x2="560" y2="190" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="190" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 190)">RST</text> + <line x1="550" y1="200" x2="560" y2="200" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="200" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 200)">PWR</text> + <line x1="550" y1="210" x2="560" y2="210" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="210" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 210)">GND</text> + <line x1="550" y1="220" x2="560" y2="220" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="564" y="220" style="font-family:Arial;font-size:11px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 564, 220)">VCC</text> + <line x1="340" y1="80" x2="430" y2="80" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="340" y1="70" x2="430" y2="70" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="340" y1="60" x2="430" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="340" y1="50" x2="430" y2="50" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <line x1="470" y1="110" x2="520" y2="110" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:3" /> + <path d="M 450,70 L 450,60 L 440,50 L 430,50" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 450,70 L 440,60 L 430,60" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 450,80 L 440,70 L 430,70" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 450,90 L 440,80 L 430,80" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 530,130 L 540,140 L 550,140" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 530,140 L 540,150 L 550,150" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 530,150 L 540,160 L 550,160" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <path d="M 530,150 L 530,160 L 540,170 L 550,170" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="544" y="135" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 544, 135)">1</text> + <text x="544" y="145" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 544, 145)">2</text> + <text x="544" y="155" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 544, 155)">3</text> + <text x="544" y="165" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 544, 165)">4</text> + <line x1="465" y1="105" x2="470" y2="115" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:1" /> + <line x1="515" y1="105" x2="510" y2="115" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:1" /> + <path d="M 475,110 L 460,110 L 450,100 L 450,70" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:3" /> + <path d="M 505,110 L 520,110 L 530,120 L 530,150" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:3" /> + <text x="433" y="45" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 433, 45)">1</text> + <text x="433" y="55" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 433, 55)">2</text> + <text x="433" y="65" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 433, 65)">3</text> + <text x="433" y="75" style="font-family:Arial;font-size:8px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 433, 75)">4</text> + <rect x="220" y="40" width="100" height="210" style="fill-opacity:0;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> + <text x="270" y="30" style="font-family:Arial;font-size:11px;text-anchor:middle" dominant-baseline="middle" transform="rotate(0, 270, 30)">ESP32 Mini NodeMCU D1</text> + <line x1="200" y1="50" x2="220" y2="50" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="224" y="50" style="font-family:Arial;font-size:10px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 224, 50)">IO19</text> + <text x="216" y="48" style="font-family:Arial;font-size:8px;text-anchor:end" dominant-baseline="baseline" transform="rotate(0, 216, 48)"></text> + <line x1="320" y1="50" x2="340" y2="50" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="50" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 50)">IO15</text> + <text x="324" y="48" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 48)"></text> + <line x1="200" y1="60" x2="220" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="224" y="60" style="font-family:Arial;font-size:10px;text-anchor:start" dominant-baseline="middle" transform="rotate(0, 224, 60)">GND</text> + <text x="216" y="58" style="font-family:Arial;font-size:8px;text-anchor:end" dominant-baseline="baseline" transform="rotate(0, 216, 58)"></text> + <line x1="320" y1="60" x2="340" y2="60" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="60" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 60)">IO27</text> + <text x="324" y="58" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 58)"></text> + <line x1="320" y1="70" x2="340" y2="70" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="70" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 70)">IO14</text> + <text x="324" y="68" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 68)"></text> + <line x1="320" y1="80" x2="340" y2="80" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="80" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 80)">IO13</text> + <text x="324" y="78" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 78)"></text> + <line x1="320" y1="180" x2="340" y2="180" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="180" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 180)">IO25</text> + <text x="324" y="178" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 178)"></text> + <line x1="320" y1="190" x2="340" y2="190" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="190" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 190)">IO26</text> + <text x="324" y="188" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 188)"></text> + <line x1="320" y1="200" x2="340" y2="200" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="200" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 200)">IO16</text> + <text x="324" y="198" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 198)"></text> + <line x1="320" y1="210" x2="340" y2="210" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="210" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 210)">GND</text> + <text x="324" y="208" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 208)"></text> + <line x1="320" y1="220" x2="340" y2="220" style="stroke:rgb(0, 0, 0);stroke-linecap:square;stroke-width:2" /> + <text x="316" y="220" style="font-family:Arial;font-size:10px;text-anchor:end" dominant-baseline="middle" transform="rotate(0, 316, 220)">3V3</text> + <text x="324" y="218" style="font-family:Arial;font-size:8px;text-anchor:start" dominant-baseline="baseline" transform="rotate(0, 324, 218)"></text> + <ellipse cx="380" cy="220" rx="2" ry="2" style="fill-opacity:1;fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);stroke-width:2" /> +</svg>
\ No newline at end of file diff --git a/_site/projects/etlas/dash.jpg b/_site/projects/etlas/dash.jpg Binary files differnew file mode 100644 index 0000000..cf4efc6 --- /dev/null +++ b/_site/projects/etlas/dash.jpg diff --git a/_site/projects/etlas/etlas_arch.png b/_site/projects/etlas/etlas_arch.png Binary files differnew file mode 100644 index 0000000..241e9f1 --- /dev/null +++ b/_site/projects/etlas/etlas_arch.png diff --git a/_site/projects/etlas/index/index.html b/_site/projects/etlas/index/index.html new file mode 100644 index 0000000..667c42e --- /dev/null +++ b/_site/projects/etlas/index/index.html @@ -0,0 +1,50 @@ +<p>Etlas is a news, stock market, and weather tracker powered by an ESP32 NodeMCU +D1, featuring a 7.5-inch Waveshare e-paper display and a DHT22 sensor module.</p> + +<table style="border: none;"> + <tr style="border: none;"> + <td style="border: none;"><img src="dash.jpg" alt="front" style="width: 100%" /></td> + <td style="border: none;"><img src="pcb.jpg" alt="back" style="width: 100%" /></td> + </tr> +</table> + +<p>The top left panel displays the end-of-day stock prices from the Polygon.io API, relayed through my own +FastCGI-wrapped Flask app hosted on a VPS. The stock symbols can be configured +through the Flask app’s application settings. The server.fcgi script enclosed +in the tarball at the end of the page contains the Flask app.</p> + +<p>The following diagram outlines this system architecture.</p> + +<p><img src="etlas_arch.png" alt="architecture" /></p> + +<p>Unlike my <a href="/projects/e-reader">e-reader</a>, which worked with raster images, +Etlas downloads time series data as CSV and computes the price curves on the +ESP32.</p> + +<p>The more prominent panel on the right of the e-paper display shows local +(Singapore) and world news from the Channel News Asia RSS feed. The MCU +downloads and parses XML data from the RSS feed directly before rendering it to +the display. Although I did it this way to avoid writing server code, it limits +the feeds from which Etlas can receive data. In a future version, I will relay +the RSS feed through a server (like the stock prices) to make it more flexible.</p> + +<p>The bottom panels (middle and right) display the temperature and relative +humidity from a DHT22 sensor. The DHT22 driver, arguably the most interesting +part of the software, reads real-time sensor data by comparing relative pulse +widths. The pulses themselves are too quick for the ESP32 to reliably measure +directly. I ported this +implementation for ESP8266 modules to my ESP32. All credit for the algorithm +belongs to them.</p> + +<p>Much of the heavy lifting of acquiring, interpreting, and rendering data from +different data sources is performed on the microcontroller using less than 512 +KB of memory. The embedded software that makes that possible is written in C +using the ESP-IDF v5.2.1. My e-paper display driver is a port of Waveshare examples for Arduino +and STM32 platforms.</p> + +<p>I’ve been using Etlas daily (for a couple of hours on weekdays and all day on +weekends) since August 2024. As of October 2025, it’s been running reliably for +over a year. If you are interested in an e-paper display like this, drop me an +email at the address on my home page.</p> + +<p>Files: <a href="source.tar.gz">source.tar.gz</a></p> diff --git a/_site/projects/etlas/pcb.jpg b/_site/projects/etlas/pcb.jpg Binary files differnew file mode 100644 index 0000000..fcb40fa --- /dev/null +++ b/_site/projects/etlas/pcb.jpg diff --git a/_site/projects/etlas/schematic.svg b/_site/projects/etlas/schematic.svg new file mode 100644 index 0000000..3070dd1 --- /dev/null +++ b/_site/projects/etlas/schematic.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Do not edit this file with editors other than diagrams.net --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg" style="background-color: rgb(255, 255, 255);" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="741px" height="371px" viewBox="-0.5 -0.5 741 371" content="<mxfile host="app.diagrams.net" modified="2024-01-04T10:11:48.655Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0" etag="QhztmAZcJcNMJFkbNl2E" version="21.2.9" type="device"><diagram name="Page-1" id="Hvsc3-8LFRnBvTan2Q2q">7VzbcuI4EP0aHkPZEr49gpNJqjbJpoYku5mXlMdWwLsGMbZIYL9+JVvyVYAJBpOBqtQgt2RJ7nPU6m7Z04H2ZHEdOrPxHfZQ0AGKt+jAyw4AqmIZ9IdJlolEs5REMAp9jzfKBEP/PyTu5NK576Go0JBgHBB/VhS6eDpFLinInDDEH8VmbzgojjpzRqgiGLpOUJX+5XtknEhNTcnkN8gfjcXIqsJrJo5ozAXR2PHwR04ErzrQDjEmSWmysFHAlCf0ktz3bUVtOrEQTUmdG66NwIeLl/HLD+sFfn8e2PbTjwutl3Tz7gRz/sR8tmQpVBDi+dRDrBelAwcfY5+g4cxxWe0HBZ3KxmQS0CuVFiMS4n+RjQMcUskUT2mzAR8DhQQtVs5eTXVCyYTwBJFwSZvwG3qgqyX3cCLpPSH4yHAxelzZ4xwm0OBCh3NhlPaeqYsWuMa20J4gaU5ZyKP04Zc4JGM8wlMnuMqkg6I6sza3GM+4Ev9BhCz5WnDmBBdVjBY++ZvfzsovufLlIn/BVKWJqwcU+vSxUShaTL0+WyAZTFTyzWca4PVURfE4XcvqCUE8WFftpYJsxPhqmb8qD5loi6loPQuoRvE8dNE61XNb4IQjRNa0s+SsClHgEP+9OA8ZP/itD9inM0zZCATPOBnVMsWSefG7MpZRhTvLXLMZaxCtGQdY0nEy0iY9ZhROn3EHVldNwNGx2ljNas5atchYoG0k7Prl8AW5u5ttg8fPAmtbFkBjMw0kduvMjdIke8fPDeqJbUmOs4XYkgXaF2AB3MyCrqLoRSboNXaLs5nYTBD9CxBkjX8sNxM1tpAzDfKD98w2aaDWo8EaV+Lo0DT2EtNoZjGmAfp+YhrNVAvjQPUQMU2nnOa4Gj5AJrrzpz79ucceurOfaIkyBlTj+jR7wzD3nGicctMJ/NGUll2KN2NNNRsiEkexZOzMWJeTxYglzLooQC4JfdcJugEe+e7ryCEo6v56m7367op0S+D8RMG9M+F5MRs+U1/drv5d318mBdWS1m/8Yzu8DQz2j84u2TDM97OBJmkPBzN/esvm9riMn9GdR6SZ7A9USvG2QCiX+wGA+yP53E+afWvcqmntxEdZUkYrbEsb9qQGrZRR00oBs81Nx6gs+Mubx12Tmw0wWdP1IpPNKpNVkZXNM9kw96Qpq2oaL2bUSIVUeNN/bF1jqlLcL1RNojFdkvdVyxtYYyoD1e2kagwyv8ENnCiKrXlOM0U11l2fVS3ltKBJlCBkOzoHKpBv2mkXidmoOAebO6qZOW3KEQBtm21TLQSb6gbD3XagKZh93CGGmOW6BRn7pSi8ekfMPU00zwwT871ir+UBRz7xMfPlfmJC8CRx1z7h9olu+7wNYTRZ5/pFtJ0TvCZoRN1ZiAmto2p7RU7IlncThhSoxcMEKDGkUh9qb2a0evgIu/C5ghx9ZCLzq0unjG90AZREFZDKyEx8z4uXuGwfk5nonRCwVti+HACyjQzuzYdVK7o+iDE8nPXSd7RK8k1Mh0Unjsa0tTaxbUNloxT2iHH2GioD/WxLN65k3TSLSxm2bUvNE7OlujigXIPAQY2p6Ph0goLUgShDsG1QUOnIPGxQAKvx3O+9eCAoKdywWl487Zwop2HZ6pCMnwwcVVAG6wZlK1hwmKBMzPLsSKx7r9EoORJG9a3GgzoSUJOgpgfM7r3h2Ghn8Om/5lhUXETxKuvTBtZsEStHVNPSiP3aQ9ETnVnSWVLx+xpaIF6VSZOX9QytSFM1D2/VT2wK3kv7DG/NfXR/8Faz+U3B+334eIL4giK+Vsv49mRBRkPW+faPk8eXAiz5quCwCMvOlppB+O7P4cvpQawUHSwgdsDWAJa5xc0APHg6SYCLxxqgbRerB/cG8LN9ej5W6dAEmPUM9N5SFT3ZJ3PNwBu/sHVi8AJFLX3Z1z7C+wuBT3EBlzP1QCT7WsNXdsJ1XsGfPwvTy/hKnGjxJeNhEK6+/tdYlqP/2D85iDVdK0NcARhKspSfAJheZt/EJwc82f8sAK/+Bw==</diagram></mxfile>"><defs/><g><rect x="0.5" y="0.5" width="740" height="370" fill="rgb(255, 255, 255)" stroke="none" pointer-events="all"/><path d="M 258 106 L 187 106 L 187 109 L 218.04 109.04" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 258 126 L 219 126" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 258 146 L 219 146" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 258 166 L 219 166" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 258 186 L 219.96 186" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 258 206 L 219 206" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 483 246 L 539 246 L 539 194" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 273 66 L 468 66 L 473 71 L 473 261 L 468 266 L 273 266 L 268 261 L 268 71 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="277.5" y="109.3">15</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="277.5" y="129.3">27</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="277.5" y="149.3">26</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="277.5" y="169.3">13</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="277.5" y="189.3">14</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="277.5" y="209.3">25</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="462.5" y="229.3">19</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="307.5" y="259.3">3V3</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"><text x="447.5" y="259.3">GND</text></g><g fill="rgb(0, 0, 0)" font-family="Arial,Helvetica" text-anchor="middle" font-size="9.600000000000001px"/><path d="M 258 86 L 268 86 M 473 86 L 483 86 M 258 106 L 268 106 M 473 106 L 483 106 M 258 126 L 268 126 M 473 126 L 483 126 M 258 146 L 268 146 M 473 146 L 483 146 M 258 166 L 268 166 M 473 166 L 483 166 M 258 186 L 268 186 M 473 186 L 483 186 M 258 206 L 268 206 M 473 206 L 483 206 M 258 226 L 268 226 M 473 226 L 483 226 M 258 246 L 268 246 M 473 246 L 483 246 M 288 56 L 288 66 M 288 266 L 288 276 M 308 56 L 308 66 M 308 266 L 308 276 M 328 56 L 328 66 M 328 266 L 328 276 M 348 56 L 348 66 M 348 266 L 348 276 M 368 56 L 368 66 M 368 266 L 368 276 M 388 56 L 388 66 M 388 266 L 388 276 M 408 56 L 408 66 M 408 266 L 408 276 M 428 56 L 428 66 M 428 266 L 428 276 M 448 56 L 448 66 M 448 266 L 448 276" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><ellipse cx="278" cy="256" rx="5" ry="5" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 223px; height: 1px; padding-top: 166px; margin-left: 259px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">ESP32 Mini NodeMCU D1 </div></div></div></foreignObject><text x="371" y="170" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">ESP32 Mini NodeMCU D1 </text></switch></g><path d="M 601 194 L 601 259.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 601 264.88 L 597.5 257.88 L 601 259.63 L 604.5 257.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="524" y="116" width="154" height="78" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 152px; height: 1px; padding-top: 155px; margin-left: 525px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">DHT22</div></div></div></foreignObject><text x="601" y="159" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">DHT22</text></switch></g><rect x="59" y="86" width="160" height="160" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 166px; margin-left: 60px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">E-paper HAT</div></div></div></foreignObject><text x="139" y="170" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">E-paper HAT</text></switch></g><path d="M 79 246 L 79 299.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 79 304.88 L 75.5 297.88 L 79 299.63 L 82.5 297.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 189.5 286 L 189.5 266 L 189.56 246" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><rect x="177" y="286" width="25" height="20" fill="none" stroke="none" pointer-events="all"/><path d="M 177 291 L 202 291 M 179 293.5 L 200 293.5 M 181 296 L 198 296 M 185.25 301 L 193.75 301 M 187.25 303.5 L 191.75 303.5 M 189.5 286 L 189.5 291 M 183.25 298.5 L 195.75 298.5 M 189.25 306 L 189.75 306" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="49" y="306" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 321px; margin-left: 50px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">3.3V</div></div></div></foreignObject><text x="79" y="325" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">3.3V</text></switch></g><path d="M 658.5 266 L 658.5 194 L 594 194" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><rect x="646" y="266" width="25" height="20" fill="none" stroke="none" pointer-events="all"/><path d="M 646 271 L 671 271 M 648 273.5 L 669 273.5 M 650 276 L 667 276 M 654.25 281 L 662.75 281 M 656.25 283.5 L 660.75 283.5 M 658.5 266 L 658.5 271 M 652.25 278.5 L 664.75 278.5 M 658.25 286 L 658.75 286" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="571" y="266" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 281px; margin-left: 572px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">3.3V</div></div></div></foreignObject><text x="601" y="285" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">3.3V</text></switch></g><path d="M 308 266 L 308 309.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 308 314.88 L 304.5 307.88 L 308 309.63 L 311.5 307.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="278" y="315" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 330px; margin-left: 279px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">3.3V</div></div></div></foreignObject><text x="308" y="334" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">3.3V</text></switch></g><path d="M 448.5 311 L 448.5 291 L 448 276" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><rect x="436" y="311" width="25" height="20" fill="none" stroke="none" pointer-events="all"/><path d="M 436 316 L 461 316 M 438 318.5 L 459 318.5 M 440 321 L 457 321 M 444.25 326 L 452.75 326 M 446.25 328.5 L 450.75 328.5 M 448.5 311 L 448.5 316 M 442.25 323.5 L 454.75 323.5 M 448.25 331 L 448.75 331" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="172" y="95" width="60" height="23" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 107px; margin-left: 173px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">CS</font></div></div></div></foreignObject><text x="202" y="110" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">CS</text></switch></g><rect x="172" y="115" width="60" height="23" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 127px; margin-left: 173px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">DC</font></div></div></div></foreignObject><text x="202" y="130" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">DC</text></switch></g><rect x="170" y="135" width="60" height="23" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 147px; margin-left: 171px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">RST</font></div></div></div></foreignObject><text x="200" y="150" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">RST</text></switch></g><rect x="170" y="155.5" width="60" height="23" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 167px; margin-left: 171px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">CLK</font></div></div></div></foreignObject><text x="200" y="171" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">CLK</text></switch></g><rect x="166" y="174" width="60" height="23" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 186px; margin-left: 167px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">MOSY</font></div></div></div></foreignObject><text x="196" y="189" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">MOSY</text></switch></g><rect x="167" y="195" width="60" height="23" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 207px; margin-left: 168px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">BUSY</font></div></div></div></foreignObject><text x="197" y="210" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">BUSY</text></switch></g><rect x="49" y="221" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 236px; margin-left: 50px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">VCC</font></div></div></div></foreignObject><text x="79" y="240" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">VCC</text></switch></g><rect x="159.5" y="221" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 236px; margin-left: 161px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">GND</font></div></div></div></foreignObject><text x="190" y="240" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">GND</text></switch></g><rect x="571" y="170" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 185px; margin-left: 572px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">VCC</font></div></div></div></foreignObject><text x="601" y="189" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">VCC</text></switch></g><rect x="644" y="170.5" width="29" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 27px; height: 1px; padding-top: 186px; margin-left: 645px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">GND</font></div></div></div></foreignObject><text x="659" y="189" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">GND</text></switch></g><rect x="523" y="170" width="35" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 33px; height: 1px; padding-top: 185px; margin-left: 524px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(0, 0, 0); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 9px;">DATA</font></div></div></div></foreignObject><text x="541" y="189" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">DATA</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.drawio.com/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Text is not SVG - cannot display</text></a></switch></svg>
\ No newline at end of file diff --git a/_site/projects/etlas/source.tar.gz b/_site/projects/etlas/source.tar.gz Binary files differnew file mode 100644 index 0000000..8b12cf6 --- /dev/null +++ b/_site/projects/etlas/source.tar.gz diff --git a/_site/projects/fpm-door-lock/breadboard.jpg b/_site/projects/fpm-door-lock/breadboard.jpg Binary files differnew file mode 100644 index 0000000..2bf47a9 --- /dev/null +++ b/_site/projects/fpm-door-lock/breadboard.jpg diff --git a/_site/projects/fpm-door-lock/footprint.png b/_site/projects/fpm-door-lock/footprint.png Binary files differnew file mode 100644 index 0000000..5511bf1 --- /dev/null +++ b/_site/projects/fpm-door-lock/footprint.png diff --git a/_site/projects/fpm-door-lock/gerber.zip b/_site/projects/fpm-door-lock/gerber.zip Binary files differnew file mode 100644 index 0000000..19a9d19 --- /dev/null +++ b/_site/projects/fpm-door-lock/gerber.zip diff --git a/_site/projects/fpm-door-lock/index/index.html b/_site/projects/fpm-door-lock/index/index.html new file mode 100644 index 0000000..4bcc9ed --- /dev/null +++ b/_site/projects/fpm-door-lock/index/index.html @@ -0,0 +1,106 @@ +<p>This project features a fingerprint door lock powered by an ATmega328P +microcontroller.</p> + +<video style="max-width:100%;" controls="" poster="pcb.jpg"> + <source src="video.mp4" type="video/mp4" /> +</video> + +<h2 id="overview">Overview</h2> + +<p>The lock comprises three subsystems: the ATmega328P, an R503 fingerprint +sensor, and an FS5106B high-torque servo. The sensor mounted on the front +surface of the door enables users to unlock it from the outside. The servo is +attached to the interior door knob. The MCU must be installed at the back of +the door to prevent unauthorized users from tampering with it.</p> + +<p>When no one is interacting with the lock, the MCU is in deep sleep. The sensor +and the servo each draw 13.8 mA and 4.6 mA of quiescent currents. To prevent +this idle current draw, the MCU employs MOSFETs to cut off power to them before +entering deep sleep. Doing so is crucial for conserving the battery.</p> + +<p>Without power, the sensor remains in a low-power state, drawing approximately +2.9 μA through a separate power rail. When a finger comes into contact with the +sensor, the sensor triggers a pin change interrupt, waking up the MCU. The MCU +activates a MOSFET, which in turn activates the sensor. Over UART, the MCU +unlocks the sensor and issues commands to scan and match the fingerprint.</p> + +<p>If the fingerprint matches an enrolled fingerprint, the MCU activates the blue +LED on the sensor, turns on the MOSFET connected to the servo, and sends a PWM +signal to the servo to unlock the door. Otherwise, the MCU activates the red +LED on the sensor. Finally, the MCU deactivates the MOSFETS and goes back to +sleep.</p> + +<h2 id="embedded-software">Embedded software</h2> + +<p>The embedded software, written in C with the help of the AVR toolchain, +includes a driver for the sensor, servo control routines, and a battery +monitoring system.</p> + +<p>In addition to controlling the sensor and the servo, the program strives to +maintain precise control over the sleep mode, as well as when the peripherals +are activated and for how long they remain active. I thoroughly enjoyed writing +the embedded software. There’s something magical about being able to alter the +physical world around you by uttering a few lines of C code.</p> + +<p>The source code of the project, which includes a driver for the R503 +fingerprint sensor module, is enclosed in the tarball linked at the end of the +page.</p> + +<h2 id="the-circuit-board">The circuit board</h2> + +<p>For this project, I designed a custom PCB and had it fabricated by JLCPCB. Like +the software, the circuit is chiefly concerned with optimizing power +consumption and extending battery life.</p> + +<table style="border: none; width: 100%"> + <tr style="border: none;"> + <td style="border: none; width: 49.9%; background-color: transparent; text-align: center;"> + <img src="breadboard.jpg" alt="PCB" style="width: 100%" /> + </td> + <td style="border: none; background-color: transparent; text-align: center;"> + <img src="pcb1.jpg" alt="Design" style="width: 100%" /> + </td> + </tr> + <tr style="border: none;"> + <td colspan="2" style="border: none; background-color: transparent; text-align: center;"> + <img src="footprint.png" alt="PCB footprint" style="width: 100%" /> + </td> + </tr> +</table> + +<p>To that end, the principal components of the circuit are the 2N7000 and +NDP6020P field-effect transistors. They switch power electronically to the +servo and the fingerprint sensor, the two most power-hungry parts of the +circuit. The two MP1584EN buck converters play an axial role in efficiently +regulating power to the MCU and the sensor.</p> + +<p>The ATmega328P typically operates at 5 V with a 16 MHz crystal oscillator. To +further reduce power consumption, I modified the ATmega328P’s fuses to run at +3.3 V with an 8 MHz crystal oscillator.</p> + +<p>The bottom right area of the PCB isolates the power supply of the servo from +the rest of the circuit. This shields components such as the MCU from the +servo’s high current draw, which can exceed 1 A. The IN4007 diode in slot U2 +serves as a flyback diode, protecting the MOSFET from reverse currents +generated by the servo.</p> + +<p>Lastly, the 56 kΩ and 10 kΩ resistors in slots R10 and R11 form a voltage +divider circuit. Its output is fed to the ADC of the MCU, which measures the +supply voltage by comparing it to the internal bandgap reference voltage.</p> + +<h2 id="epilogue">Epilogue</h2> + +<p>This project began nearly a year ago when I attempted to unlock our door +wirelessly by writing to the UART ports of two MCUs connected to inexpensive +433 MHz RF transceivers, as if there were an invisible wire between them. +Although I failed, it led me down a rabbit hole of RF communications, MOSFETs, +PCB design, and low-power circuits.</p> + +<p>During the project, I reinvented the wheel many times. I implemented a +low-level network stack using only RF modules and an 8-bit microcontroller, +designed my first PCB, and developed drivers from scratch. The project was far +from a smooth sail. Bad electrical connections, soldering, desoldering, and the +heartache of purchasing the wrong parts were routine. It was a long but +rewarding journey from the messy breadboard to the shiny PCB.</p> + +<p>Files: <a href="source.tar.gz">source.tar.gz</a>, <a href="gerber.zip">gerber.zip</a></p> diff --git a/_site/projects/fpm-door-lock/pcb.jpg b/_site/projects/fpm-door-lock/pcb.jpg Binary files differnew file mode 100644 index 0000000..fbd800b --- /dev/null +++ b/_site/projects/fpm-door-lock/pcb.jpg diff --git a/_site/projects/fpm-door-lock/pcb1.jpg b/_site/projects/fpm-door-lock/pcb1.jpg Binary files differnew file mode 100644 index 0000000..367187d --- /dev/null +++ b/_site/projects/fpm-door-lock/pcb1.jpg diff --git a/_site/projects/fpm-door-lock/source.tar.gz b/_site/projects/fpm-door-lock/source.tar.gz Binary files differnew file mode 100644 index 0000000..ef23422 --- /dev/null +++ b/_site/projects/fpm-door-lock/source.tar.gz diff --git a/_site/projects/fpm-door-lock/video.mp4 b/_site/projects/fpm-door-lock/video.mp4 Binary files differnew file mode 100644 index 0000000..a907a9b --- /dev/null +++ b/_site/projects/fpm-door-lock/video.mp4 diff --git a/_site/projects/index.html b/_site/projects/index.html new file mode 100644 index 0000000..a1ebd40 --- /dev/null +++ b/_site/projects/index.html @@ -0,0 +1,62 @@ +<!doctype html> +<html lang="en-us"> + <head> + <meta charset="utf-8"> + <title>Projects</title> + <link rel="stylesheet" href="/assets/css/main.css"> + <link rel="stylesheet" href="/assets/css/normalize.css"> + <link rel="stylesheet" href="/assets/css/skeleton.css"> + </head> + <body> + + <div class="container"> + <ul id="navlist" class="left"> + <li > + <a href="/">hme</a> + </li> + <li class="active"> + <a href="/projects/">tnk</a> + </li> + <li > + <a href="/about/">abt</a> + </li> + <li><a href="/feed.xml">rss</a></li> + </ul> + </div> + + <main class="container" id="main"><table style="border: none; width: 100%"> + <tr style="border: none;"> + <td style="border: none; width: 50%; vertical-align: top; background-color: transparent;"> + <img src="e-reader/thumb.png" alt="E-reader" style="width: 100%"> + <h5><a href="e-reader">Prototype e-reader</a></h5> + </td> + <td style="border: none; width: 50%; vertical-align: top; background-color: transparent;"> + <img src="matrix-digital-rain/thumb.png" alt="The Matrix" style="width: 100%"> + <h5><a href="matrix-digital-rain">The Matrix digital rain</a></h5> + </td> + </tr> + <tr style="border: none;"> + <td style="border: none; width: 50%; vertical-align: top; background-color: transparent;"> + <img src="etlas/dash.jpg" alt="Etlas" style="width: 100%"> + <h5><a href="etlas">Etlas: e-paper display for news, stocks, and the weather</a></h5> + </td> + <td style="border: none; width: 50%; vertical-align: top; background-color: transparent;"> + <img src="bumblebee/thumb.png" alt="Bumblebee" style="width: 100%"> + <h5><a href="bumblebee">Bumblebee: turn browser sessions to code</a></h5> + </td> + </tr> + <tr style="border: none;"> + <td style="border: none; width: 50%; vertical-align: top; background-color: transparent;"> + <img src="fpm-door-lock/pcb.jpg" alt="FPM door lock" style="width: 100%"> + <h5><a href="fpm-door-lock">Prototype fingerprint door lock</a></h5> + </td> + <td style="border: none; width: 50%; vertical-align: top; background-color: transparent;"> + <img src="desktop-unix/linux.png" alt="Unix desktop" style="width: 100%"> + <h5><a href="desktop-unix">Unix for the desktop</a></h5> + </td> + </tr> +</table> +</main> + + </body> +</html> diff --git a/_site/projects/matrix-digital-rain/index/index.html b/_site/projects/matrix-digital-rain/index/index.html new file mode 100644 index 0000000..e240834 --- /dev/null +++ b/_site/projects/matrix-digital-rain/index/index.html @@ -0,0 +1,31 @@ +<p>The famous digital rain from the movie The Matrix implemented in C for +the Unix terminal without using any GUI/TUI kits:</p> + +<video style="max-width:100%;" controls="" poster="thumb.png"> + <source src="matrix.mp4" type="video/mp4" /> +</video> + +<p>Domsson’s Fakesteak inspired +this project. I added the following features while trying to keep the original +project’s simplicity intact as much as possible:</p> + +<ul> + <li>Customize the rain color to match the theme of the setup.</li> + <li>Support for UTF-32 characters.</li> + <li>The ghosting effect of old monochrome displays.</li> + <li>The rain more closely resembles the original from the first movie.</li> +</ul> + +<p>To use them, you need a terminal emulator that supports 24-bit RGB colors and +Unicode characters.</p> + +<p>The background, head, and tail colors of the rain can be configured via +<code class="language-plaintext highlighter-rouge">COLOR_BG_*</code>, <code class="language-plaintext highlighter-rouge">COLOR_HD_*</code>, and <code class="language-plaintext highlighter-rouge">COLOR_TL_*</code> settings. The <code class="language-plaintext highlighter-rouge">UNICODE_MIN</code> and +<code class="language-plaintext highlighter-rouge">UNICODE_MAX</code> values control the character set used for the rain. For instance, +use <code class="language-plaintext highlighter-rouge">0x30A1</code> and <code class="language-plaintext highlighter-rouge">0x30F6</code> for Katakana:</p> + +<p><img style="width: 100%;" src="katakana.png" /></p> + +<p>Happy ricing!</p> + +<p>Files: <a href="source.tar.gz">source.tar.gz</a></p> diff --git a/_site/projects/matrix-digital-rain/katakana.png b/_site/projects/matrix-digital-rain/katakana.png Binary files differnew file mode 100644 index 0000000..b9df873 --- /dev/null +++ b/_site/projects/matrix-digital-rain/katakana.png diff --git a/_site/projects/matrix-digital-rain/matrix.mp4 b/_site/projects/matrix-digital-rain/matrix.mp4 Binary files differnew file mode 100644 index 0000000..84a9839 --- /dev/null +++ b/_site/projects/matrix-digital-rain/matrix.mp4 diff --git a/_site/projects/matrix-digital-rain/source.tar.gz b/_site/projects/matrix-digital-rain/source.tar.gz Binary files differnew file mode 100644 index 0000000..fead280 --- /dev/null +++ b/_site/projects/matrix-digital-rain/source.tar.gz diff --git a/_site/projects/matrix-digital-rain/thumb.png b/_site/projects/matrix-digital-rain/thumb.png Binary files differnew file mode 100644 index 0000000..7e008c2 --- /dev/null +++ b/_site/projects/matrix-digital-rain/thumb.png diff --git a/_site/projects/post-1/index.html b/_site/projects/post-1/index.html deleted file mode 100644 index db0b37e..0000000 --- a/_site/projects/post-1/index.html +++ /dev/null @@ -1,34 +0,0 @@ -<!doctype html> -<html lang="en-us"> - <head> - <meta charset="utf-8"> - <title>Index</title> - <link rel="stylesheet" href="/assets/css/main.css"> - <link rel="stylesheet" href="/assets/css/normalize.css"> - <link rel="stylesheet" href="/assets/css/skeleton.css"> - </head> - <body> - - <div class="container"> - <ul id="navlist" class="left"> - <li > - <a href="/">hme</a> - </li> - <li > - <a href="/about.html">abt</a> - </li> - <li><a href="/feed.xml">rss</a></li> - </ul> - </div> - - <main class="container" id="main"><p>An apple is a sweet, edible fruit produced by an apple tree.</p> - -<p>Apple trees are cultivated worldwide, and are the most widely grown -species in the genus Malus. The tree originated in Central Asia, where -its wild ancestor, Malus sieversii, is still found today. Apples have -been grown for thousands of years in Asia and Europe, and were brought -to North America by European colonists.</p> -</main> - - </body> -</html> diff --git a/assets/css/main.css b/assets/css/main.css index e58a791..0a787f2 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -32,6 +32,11 @@ img { mix-blend-mode: multiply; } +table { + border: none; + width: 100%; +} + #navlist > .active > a { color: #000; font-weight: 600; @@ -157,3 +162,4 @@ and (min-device-width : 768px) { margin-right:0; } } + diff --git a/projects.md b/projects.md new file mode 100644 index 0000000..15f630b --- /dev/null +++ b/projects.md @@ -0,0 +1,9 @@ +--- +layout: projects +title: Projects +--- + +# About page + +This page tells you a little bit about me. + |
