From e15f1076b59e997108914f6a5b9b28652d323268 Mon Sep 17 00:00:00 2001 From: Sadeep Madurange Date: Sat, 20 Dec 2025 21:36:45 +0800 Subject: Change website structure to a log. --- _config.yml | 4 +- _includes/nav.html | 4 +- _log/arduino-due.md | 2 +- _log/arduino-uno.md | 2 +- _log/bumblebee.md | 51 ++++ _log/bumblebee/bee.mp4 | Bin 0 -> 2352029 bytes _log/bumblebee/poster.png | Bin 0 -> 18024 bytes _log/bumblebee/thumb_sm.png | Bin 0 -> 6189 bytes _log/e-reader.md | 94 +++++++ _log/e-reader/circuit.svg | 145 +++++++++++ _log/e-reader/ereader.mp4 | Bin 0 -> 3101166 bytes _log/e-reader/poster.png | Bin 0 -> 674187 bytes _log/e-reader/source.tar.gz | Bin 0 -> 14304 bytes _log/e-reader/thumb_sm.png | Bin 0 -> 240117 bytes _log/etlas.md | 115 +++++++++ _log/etlas/circuit.svg | 105 ++++++++ _log/etlas/dash.jpg | Bin 0 -> 85874 bytes _log/etlas/etlas_arch.png | Bin 0 -> 47732 bytes _log/etlas/pcb.jpg | Bin 0 -> 75769 bytes _log/etlas/schematic.svg | 4 + _log/etlas/source.tar.gz | Bin 0 -> 46871 bytes _log/etlas/thumb_sm.jpg | Bin 0 -> 55678 bytes _log/fpm-door-lock.md | 113 +++++++++ _log/fpm-door-lock/breadboard.jpg | Bin 0 -> 46771 bytes _log/fpm-door-lock/footprint.png | Bin 0 -> 198127 bytes _log/fpm-door-lock/gerber.zip | Bin 0 -> 89431 bytes _log/fpm-door-lock/pcb.jpg | Bin 0 -> 68237 bytes _log/fpm-door-lock/pcb1.jpg | Bin 0 -> 37068 bytes _log/fpm-door-lock/source.tar.gz | Bin 0 -> 29473 bytes _log/fpm-door-lock/thumb_sm.jpg | Bin 0 -> 18380 bytes _log/fpm-door-lock/video.mp4 | Bin 0 -> 13264594 bytes _log/matrix-digital-rain.md | 96 ++++++++ _log/matrix-digital-rain/katakana.png | Bin 0 -> 133709 bytes _log/matrix-digital-rain/matrix.mp4 | Bin 0 -> 930430 bytes _log/matrix-digital-rain/poster.png | Bin 0 -> 70901 bytes _log/matrix-digital-rain/source.tar.gz | Bin 0 -> 2075 bytes _log/matrix-digital-rain/thumb_sm.png | Bin 0 -> 22764 bytes _log/my-first-pcb.md | 64 +++++ _log/my-first-pcb/back.jpeg | Bin 0 -> 34023 bytes _log/my-first-pcb/back_design.jpeg | Bin 0 -> 31946 bytes _log/my-first-pcb/front.jpeg | Bin 0 -> 28997 bytes _log/my-first-pcb/front_design.jpeg | Bin 0 -> 32174 bytes _log/my-first-pcb/gerber_back.zip | Bin 0 -> 48217 bytes _log/my-first-pcb/gerber_front.zip | Bin 0 -> 49605 bytes _log/my-first-pcb/source.tar.gz | Bin 0 -> 6660 bytes _log/my-first-pcb/thumb_sm.jpeg | Bin 0 -> 6181 bytes _log/neo4j-a-star-search.md | 317 ------------------------ _poc/bumblebee.md | 50 ---- _poc/bumblebee/bee.mp4 | Bin 2352029 -> 0 bytes _poc/bumblebee/poster.png | Bin 18024 -> 0 bytes _poc/bumblebee/thumb_sm.png | Bin 6189 -> 0 bytes _poc/e-reader.md | 93 ------- _poc/e-reader/circuit.svg | 145 ----------- _poc/e-reader/ereader.mp4 | Bin 3101166 -> 0 bytes _poc/e-reader/poster.png | Bin 674187 -> 0 bytes _poc/e-reader/source.tar.gz | Bin 14304 -> 0 bytes _poc/e-reader/thumb_sm.png | Bin 240117 -> 0 bytes _poc/etlas.md | 114 --------- _poc/etlas/circuit.svg | 105 -------- _poc/etlas/dash.jpg | Bin 85874 -> 0 bytes _poc/etlas/etlas_arch.png | Bin 47732 -> 0 bytes _poc/etlas/pcb.jpg | Bin 75769 -> 0 bytes _poc/etlas/schematic.svg | 4 - _poc/etlas/source.tar.gz | Bin 46871 -> 0 bytes _poc/etlas/thumb_sm.jpg | Bin 55678 -> 0 bytes _poc/fpm-door-lock.md | 112 --------- _poc/fpm-door-lock/breadboard.jpg | Bin 46771 -> 0 bytes _poc/fpm-door-lock/footprint.png | Bin 198127 -> 0 bytes _poc/fpm-door-lock/gerber.zip | Bin 89431 -> 0 bytes _poc/fpm-door-lock/pcb.jpg | Bin 68237 -> 0 bytes _poc/fpm-door-lock/pcb1.jpg | Bin 37068 -> 0 bytes _poc/fpm-door-lock/source.tar.gz | Bin 29473 -> 0 bytes _poc/fpm-door-lock/thumb_sm.jpg | Bin 18380 -> 0 bytes _poc/fpm-door-lock/video.mp4 | Bin 13264594 -> 0 bytes _poc/matrix-digital-rain.md | 95 ------- _poc/matrix-digital-rain/katakana.png | Bin 133709 -> 0 bytes _poc/matrix-digital-rain/matrix.mp4 | Bin 930430 -> 0 bytes _poc/matrix-digital-rain/poster.png | Bin 70901 -> 0 bytes _poc/matrix-digital-rain/source.tar.gz | Bin 2075 -> 0 bytes _poc/matrix-digital-rain/thumb_sm.png | Bin 22764 -> 0 bytes _poc/my-first-pcb.md | 64 ----- _poc/my-first-pcb/back.jpeg | Bin 34023 -> 0 bytes _poc/my-first-pcb/back_design.jpeg | Bin 31946 -> 0 bytes _poc/my-first-pcb/front.jpeg | Bin 28997 -> 0 bytes _poc/my-first-pcb/front_design.jpeg | Bin 32174 -> 0 bytes _poc/my-first-pcb/gerber_back.zip | Bin 48217 -> 0 bytes _poc/my-first-pcb/gerber_front.zip | Bin 49605 -> 0 bytes _poc/my-first-pcb/source.tar.gz | Bin 6660 -> 0 bytes _poc/my-first-pcb/thumb_sm.jpeg | Bin 6181 -> 0 bytes _site/404.html | 2 +- _site/about/index.html | 2 +- _site/feed.xml | 2 +- _site/index.html | 75 +++++- _site/log/arduino-due/index.html | 4 +- _site/log/arduino-uno/index.html | 4 +- _site/log/bumblebee/bee.mp4 | Bin 0 -> 2352029 bytes _site/log/bumblebee/index.html | 103 ++++++++ _site/log/bumblebee/poster.png | Bin 0 -> 18024 bytes _site/log/bumblebee/thumb_sm.png | Bin 0 -> 6189 bytes _site/log/e-reader/circuit.svg | 145 +++++++++++ _site/log/e-reader/ereader.mp4 | Bin 0 -> 3101166 bytes _site/log/e-reader/index.html | 148 +++++++++++ _site/log/e-reader/poster.png | Bin 0 -> 674187 bytes _site/log/e-reader/source.tar.gz | Bin 0 -> 14304 bytes _site/log/e-reader/thumb_sm.png | Bin 0 -> 240117 bytes _site/log/etlas/circuit.svg | 105 ++++++++ _site/log/etlas/dash.jpg | Bin 0 -> 85874 bytes _site/log/etlas/etlas_arch.png | Bin 0 -> 47732 bytes _site/log/etlas/index.html | 166 +++++++++++++ _site/log/etlas/pcb.jpg | Bin 0 -> 75769 bytes _site/log/etlas/schematic.svg | 4 + _site/log/etlas/source.tar.gz | Bin 0 -> 46871 bytes _site/log/etlas/thumb_sm.jpg | Bin 0 -> 55678 bytes _site/log/fpm-door-lock/breadboard.jpg | Bin 0 -> 46771 bytes _site/log/fpm-door-lock/footprint.png | Bin 0 -> 198127 bytes _site/log/fpm-door-lock/gerber.zip | Bin 0 -> 89431 bytes _site/log/fpm-door-lock/index.html | 168 +++++++++++++ _site/log/fpm-door-lock/pcb.jpg | Bin 0 -> 68237 bytes _site/log/fpm-door-lock/pcb1.jpg | Bin 0 -> 37068 bytes _site/log/fpm-door-lock/source.tar.gz | Bin 0 -> 29473 bytes _site/log/fpm-door-lock/thumb_sm.jpg | Bin 0 -> 18380 bytes _site/log/fpm-door-lock/video.mp4 | Bin 0 -> 13264594 bytes _site/log/index.html | 75 +++++- _site/log/matrix-digital-rain/index.html | 148 +++++++++++ _site/log/matrix-digital-rain/katakana.png | Bin 0 -> 133709 bytes _site/log/matrix-digital-rain/matrix.mp4 | Bin 0 -> 930430 bytes _site/log/matrix-digital-rain/poster.png | Bin 0 -> 70901 bytes _site/log/matrix-digital-rain/source.tar.gz | Bin 0 -> 2075 bytes _site/log/matrix-digital-rain/thumb_sm.png | Bin 0 -> 22764 bytes _site/log/mosfet-switches/index.html | 2 +- _site/log/my-first-pcb/back.jpeg | Bin 0 -> 34023 bytes _site/log/my-first-pcb/back_design.jpeg | Bin 0 -> 31946 bytes _site/log/my-first-pcb/front.jpeg | Bin 0 -> 28997 bytes _site/log/my-first-pcb/front_design.jpeg | Bin 0 -> 32174 bytes _site/log/my-first-pcb/gerber_back.zip | Bin 0 -> 48217 bytes _site/log/my-first-pcb/gerber_front.zip | Bin 0 -> 49605 bytes _site/log/my-first-pcb/index.html | 120 +++++++++ _site/log/my-first-pcb/source.tar.gz | Bin 0 -> 6660 bytes _site/log/my-first-pcb/thumb_sm.jpeg | Bin 0 -> 6181 bytes _site/log/neo4j-a-star-search/index.html | 370 ---------------------------- _site/log/suckless-software/index.html | 2 +- _site/poc/bumblebee/bee.mp4 | Bin 2352029 -> 0 bytes _site/poc/bumblebee/index.html | 103 -------- _site/poc/bumblebee/poster.png | Bin 18024 -> 0 bytes _site/poc/bumblebee/thumb_sm.png | Bin 6189 -> 0 bytes _site/poc/e-reader/circuit.svg | 145 ----------- _site/poc/e-reader/ereader.mp4 | Bin 3101166 -> 0 bytes _site/poc/e-reader/index.html | 148 ----------- _site/poc/e-reader/poster.png | Bin 674187 -> 0 bytes _site/poc/e-reader/source.tar.gz | Bin 14304 -> 0 bytes _site/poc/e-reader/thumb_sm.png | Bin 240117 -> 0 bytes _site/poc/etlas/circuit.svg | 105 -------- _site/poc/etlas/dash.jpg | Bin 85874 -> 0 bytes _site/poc/etlas/etlas_arch.png | Bin 47732 -> 0 bytes _site/poc/etlas/index.html | 166 ------------- _site/poc/etlas/pcb.jpg | Bin 75769 -> 0 bytes _site/poc/etlas/schematic.svg | 4 - _site/poc/etlas/source.tar.gz | Bin 46871 -> 0 bytes _site/poc/etlas/thumb_sm.jpg | Bin 55678 -> 0 bytes _site/poc/fpm-door-lock/breadboard.jpg | Bin 46771 -> 0 bytes _site/poc/fpm-door-lock/footprint.png | Bin 198127 -> 0 bytes _site/poc/fpm-door-lock/gerber.zip | Bin 89431 -> 0 bytes _site/poc/fpm-door-lock/index.html | 168 ------------- _site/poc/fpm-door-lock/pcb.jpg | Bin 68237 -> 0 bytes _site/poc/fpm-door-lock/pcb1.jpg | Bin 37068 -> 0 bytes _site/poc/fpm-door-lock/source.tar.gz | Bin 29473 -> 0 bytes _site/poc/fpm-door-lock/thumb_sm.jpg | Bin 18380 -> 0 bytes _site/poc/fpm-door-lock/video.mp4 | Bin 13264594 -> 0 bytes _site/poc/index.html | 120 --------- _site/poc/matrix-digital-rain/index.html | 148 ----------- _site/poc/matrix-digital-rain/katakana.png | Bin 133709 -> 0 bytes _site/poc/matrix-digital-rain/matrix.mp4 | Bin 930430 -> 0 bytes _site/poc/matrix-digital-rain/poster.png | Bin 70901 -> 0 bytes _site/poc/matrix-digital-rain/source.tar.gz | Bin 2075 -> 0 bytes _site/poc/matrix-digital-rain/thumb_sm.png | Bin 22764 -> 0 bytes _site/poc/my-first-pcb/back.jpeg | Bin 34023 -> 0 bytes _site/poc/my-first-pcb/back_design.jpeg | Bin 31946 -> 0 bytes _site/poc/my-first-pcb/front.jpeg | Bin 28997 -> 0 bytes _site/poc/my-first-pcb/front_design.jpeg | Bin 32174 -> 0 bytes _site/poc/my-first-pcb/gerber_back.zip | Bin 48217 -> 0 bytes _site/poc/my-first-pcb/gerber_front.zip | Bin 49605 -> 0 bytes _site/poc/my-first-pcb/index.html | 120 --------- _site/poc/my-first-pcb/source.tar.gz | Bin 6660 -> 0 bytes _site/poc/my-first-pcb/thumb_sm.jpeg | Bin 6181 -> 0 bytes _site/posts.xml | 2 +- _site/projects/index.html | 123 +++++++++ _site/robots.txt | 2 +- _site/sitemap.xml | 52 ++-- poc.html | 26 -- projects.html | 42 ++++ 190 files changed, 2239 insertions(+), 2778 deletions(-) create mode 100644 _log/bumblebee.md create mode 100644 _log/bumblebee/bee.mp4 create mode 100644 _log/bumblebee/poster.png create mode 100644 _log/bumblebee/thumb_sm.png create mode 100644 _log/e-reader.md create mode 100644 _log/e-reader/circuit.svg create mode 100644 _log/e-reader/ereader.mp4 create mode 100644 _log/e-reader/poster.png create mode 100644 _log/e-reader/source.tar.gz create mode 100644 _log/e-reader/thumb_sm.png create mode 100644 _log/etlas.md create mode 100644 _log/etlas/circuit.svg create mode 100644 _log/etlas/dash.jpg create mode 100644 _log/etlas/etlas_arch.png create mode 100644 _log/etlas/pcb.jpg create mode 100644 _log/etlas/schematic.svg create mode 100644 _log/etlas/source.tar.gz create mode 100644 _log/etlas/thumb_sm.jpg create mode 100644 _log/fpm-door-lock.md create mode 100644 _log/fpm-door-lock/breadboard.jpg create mode 100644 _log/fpm-door-lock/footprint.png create mode 100644 _log/fpm-door-lock/gerber.zip create mode 100644 _log/fpm-door-lock/pcb.jpg create mode 100644 _log/fpm-door-lock/pcb1.jpg create mode 100644 _log/fpm-door-lock/source.tar.gz create mode 100644 _log/fpm-door-lock/thumb_sm.jpg create mode 100644 _log/fpm-door-lock/video.mp4 create mode 100644 _log/matrix-digital-rain.md create mode 100644 _log/matrix-digital-rain/katakana.png create mode 100644 _log/matrix-digital-rain/matrix.mp4 create mode 100644 _log/matrix-digital-rain/poster.png create mode 100644 _log/matrix-digital-rain/source.tar.gz create mode 100644 _log/matrix-digital-rain/thumb_sm.png create mode 100644 _log/my-first-pcb.md create mode 100644 _log/my-first-pcb/back.jpeg create mode 100644 _log/my-first-pcb/back_design.jpeg create mode 100644 _log/my-first-pcb/front.jpeg create mode 100644 _log/my-first-pcb/front_design.jpeg create mode 100644 _log/my-first-pcb/gerber_back.zip create mode 100644 _log/my-first-pcb/gerber_front.zip create mode 100644 _log/my-first-pcb/source.tar.gz create mode 100644 _log/my-first-pcb/thumb_sm.jpeg delete mode 100644 _log/neo4j-a-star-search.md delete mode 100644 _poc/bumblebee.md delete mode 100644 _poc/bumblebee/bee.mp4 delete mode 100644 _poc/bumblebee/poster.png delete mode 100644 _poc/bumblebee/thumb_sm.png delete mode 100644 _poc/e-reader.md delete mode 100644 _poc/e-reader/circuit.svg delete mode 100644 _poc/e-reader/ereader.mp4 delete mode 100644 _poc/e-reader/poster.png delete mode 100644 _poc/e-reader/source.tar.gz delete mode 100644 _poc/e-reader/thumb_sm.png delete mode 100644 _poc/etlas.md delete mode 100644 _poc/etlas/circuit.svg delete mode 100644 _poc/etlas/dash.jpg delete mode 100644 _poc/etlas/etlas_arch.png delete mode 100644 _poc/etlas/pcb.jpg delete mode 100644 _poc/etlas/schematic.svg delete mode 100644 _poc/etlas/source.tar.gz delete mode 100644 _poc/etlas/thumb_sm.jpg delete mode 100644 _poc/fpm-door-lock.md delete mode 100644 _poc/fpm-door-lock/breadboard.jpg delete mode 100644 _poc/fpm-door-lock/footprint.png delete mode 100644 _poc/fpm-door-lock/gerber.zip delete mode 100644 _poc/fpm-door-lock/pcb.jpg delete mode 100644 _poc/fpm-door-lock/pcb1.jpg delete mode 100644 _poc/fpm-door-lock/source.tar.gz delete mode 100644 _poc/fpm-door-lock/thumb_sm.jpg delete mode 100644 _poc/fpm-door-lock/video.mp4 delete mode 100644 _poc/matrix-digital-rain.md delete mode 100644 _poc/matrix-digital-rain/katakana.png delete mode 100644 _poc/matrix-digital-rain/matrix.mp4 delete mode 100644 _poc/matrix-digital-rain/poster.png delete mode 100644 _poc/matrix-digital-rain/source.tar.gz delete mode 100644 _poc/matrix-digital-rain/thumb_sm.png delete mode 100644 _poc/my-first-pcb.md delete mode 100644 _poc/my-first-pcb/back.jpeg delete mode 100644 _poc/my-first-pcb/back_design.jpeg delete mode 100644 _poc/my-first-pcb/front.jpeg delete mode 100644 _poc/my-first-pcb/front_design.jpeg delete mode 100644 _poc/my-first-pcb/gerber_back.zip delete mode 100644 _poc/my-first-pcb/gerber_front.zip delete mode 100644 _poc/my-first-pcb/source.tar.gz delete mode 100644 _poc/my-first-pcb/thumb_sm.jpeg create mode 100644 _site/log/bumblebee/bee.mp4 create mode 100644 _site/log/bumblebee/index.html create mode 100644 _site/log/bumblebee/poster.png create mode 100644 _site/log/bumblebee/thumb_sm.png create mode 100644 _site/log/e-reader/circuit.svg create mode 100644 _site/log/e-reader/ereader.mp4 create mode 100644 _site/log/e-reader/index.html create mode 100644 _site/log/e-reader/poster.png create mode 100644 _site/log/e-reader/source.tar.gz create mode 100644 _site/log/e-reader/thumb_sm.png create mode 100644 _site/log/etlas/circuit.svg create mode 100644 _site/log/etlas/dash.jpg create mode 100644 _site/log/etlas/etlas_arch.png create mode 100644 _site/log/etlas/index.html create mode 100644 _site/log/etlas/pcb.jpg create mode 100644 _site/log/etlas/schematic.svg create mode 100644 _site/log/etlas/source.tar.gz create mode 100644 _site/log/etlas/thumb_sm.jpg create mode 100644 _site/log/fpm-door-lock/breadboard.jpg create mode 100644 _site/log/fpm-door-lock/footprint.png create mode 100644 _site/log/fpm-door-lock/gerber.zip create mode 100644 _site/log/fpm-door-lock/index.html create mode 100644 _site/log/fpm-door-lock/pcb.jpg create mode 100644 _site/log/fpm-door-lock/pcb1.jpg create mode 100644 _site/log/fpm-door-lock/source.tar.gz create mode 100644 _site/log/fpm-door-lock/thumb_sm.jpg create mode 100644 _site/log/fpm-door-lock/video.mp4 create mode 100644 _site/log/matrix-digital-rain/index.html create mode 100644 _site/log/matrix-digital-rain/katakana.png create mode 100644 _site/log/matrix-digital-rain/matrix.mp4 create mode 100644 _site/log/matrix-digital-rain/poster.png create mode 100644 _site/log/matrix-digital-rain/source.tar.gz create mode 100644 _site/log/matrix-digital-rain/thumb_sm.png create mode 100644 _site/log/my-first-pcb/back.jpeg create mode 100644 _site/log/my-first-pcb/back_design.jpeg create mode 100644 _site/log/my-first-pcb/front.jpeg create mode 100644 _site/log/my-first-pcb/front_design.jpeg create mode 100644 _site/log/my-first-pcb/gerber_back.zip create mode 100644 _site/log/my-first-pcb/gerber_front.zip create mode 100644 _site/log/my-first-pcb/index.html create mode 100644 _site/log/my-first-pcb/source.tar.gz create mode 100644 _site/log/my-first-pcb/thumb_sm.jpeg delete mode 100644 _site/log/neo4j-a-star-search/index.html delete mode 100644 _site/poc/bumblebee/bee.mp4 delete mode 100644 _site/poc/bumblebee/index.html delete mode 100644 _site/poc/bumblebee/poster.png delete mode 100644 _site/poc/bumblebee/thumb_sm.png delete mode 100644 _site/poc/e-reader/circuit.svg delete mode 100644 _site/poc/e-reader/ereader.mp4 delete mode 100644 _site/poc/e-reader/index.html delete mode 100644 _site/poc/e-reader/poster.png delete mode 100644 _site/poc/e-reader/source.tar.gz delete mode 100644 _site/poc/e-reader/thumb_sm.png delete mode 100644 _site/poc/etlas/circuit.svg delete mode 100644 _site/poc/etlas/dash.jpg delete mode 100644 _site/poc/etlas/etlas_arch.png delete mode 100644 _site/poc/etlas/index.html delete mode 100644 _site/poc/etlas/pcb.jpg delete mode 100644 _site/poc/etlas/schematic.svg delete mode 100644 _site/poc/etlas/source.tar.gz delete mode 100644 _site/poc/etlas/thumb_sm.jpg delete mode 100644 _site/poc/fpm-door-lock/breadboard.jpg delete mode 100644 _site/poc/fpm-door-lock/footprint.png delete mode 100644 _site/poc/fpm-door-lock/gerber.zip delete mode 100644 _site/poc/fpm-door-lock/index.html delete mode 100644 _site/poc/fpm-door-lock/pcb.jpg delete mode 100644 _site/poc/fpm-door-lock/pcb1.jpg delete mode 100644 _site/poc/fpm-door-lock/source.tar.gz delete mode 100644 _site/poc/fpm-door-lock/thumb_sm.jpg delete mode 100644 _site/poc/fpm-door-lock/video.mp4 delete mode 100644 _site/poc/index.html delete mode 100644 _site/poc/matrix-digital-rain/index.html delete mode 100644 _site/poc/matrix-digital-rain/katakana.png delete mode 100644 _site/poc/matrix-digital-rain/matrix.mp4 delete mode 100644 _site/poc/matrix-digital-rain/poster.png delete mode 100644 _site/poc/matrix-digital-rain/source.tar.gz delete mode 100644 _site/poc/matrix-digital-rain/thumb_sm.png delete mode 100644 _site/poc/my-first-pcb/back.jpeg delete mode 100644 _site/poc/my-first-pcb/back_design.jpeg delete mode 100644 _site/poc/my-first-pcb/front.jpeg delete mode 100644 _site/poc/my-first-pcb/front_design.jpeg delete mode 100644 _site/poc/my-first-pcb/gerber_back.zip delete mode 100644 _site/poc/my-first-pcb/gerber_front.zip delete mode 100644 _site/poc/my-first-pcb/index.html delete mode 100644 _site/poc/my-first-pcb/source.tar.gz delete mode 100644 _site/poc/my-first-pcb/thumb_sm.jpeg create mode 100644 _site/projects/index.html delete mode 100644 poc.html create mode 100644 projects.html diff --git a/_config.yml b/_config.yml index 77bc650..efe7905 100644 --- a/_config.yml +++ b/_config.yml @@ -6,12 +6,10 @@ permalink: pretty collections: log: output: true - poc: - output: true feed: collections: - poc: + log: path: "/feed.xml" posts: path: "/posts.xml" diff --git a/_includes/nav.html b/_includes/nav.html index c8afc3f..dc2cc5d 100644 --- a/_includes/nav.html +++ b/_includes/nav.html @@ -7,8 +7,8 @@
  • log
  • -
  • - poc +
  • + poc
  • abt diff --git a/_log/arduino-due.md b/_log/arduino-due.md index 57fa381..2cbd2f4 100644 --- a/_log/arduino-due.md +++ b/_log/arduino-due.md @@ -1,6 +1,6 @@ --- title: How to set up ATSAM3X8E microcontrollers for bare-metal programming in C -date: 2024-10-05 +date: 2024-09-16 layout: post --- diff --git a/_log/arduino-uno.md b/_log/arduino-uno.md index 6534c3c..dfdbdce 100644 --- a/_log/arduino-uno.md +++ b/_log/arduino-uno.md @@ -1,6 +1,6 @@ --- title: How to configure ATmega328P microcontrollers to run at 3.3V and 5V -date: 2025-04-10 +date: 2025-06-10 layout: post --- diff --git a/_log/bumblebee.md b/_log/bumblebee.md new file mode 100644 index 0000000..c26fa0d --- /dev/null +++ b/_log/bumblebee.md @@ -0,0 +1,51 @@ +--- +title: "Bumblebee: browser automation" +date: 2025-04-02 +layout: post +project: true +thumbnail: thumb_sm.png +--- + +Bumblebee is a tool I built for one of my employers to automate the generation +of web scraping scripts. + + + +In 2024, we were tasked with collecting market data using various methods, +including scraping data from authorized websites for traders' use. + +Manual authoring of such scripts took time. The scripts were often brittle due +to the complexity of the modern web, and they lacked optimizations such as +bypassing the UI and retrieving the data files directly when possible, which +would have significantly reduced our compute costs. + +To alleviate these challenges, I, with the help of a colleague, Andy Zhang, +built Bumblebee: a web browser powered by C# Windows Forms, Microsoft Edge WebView2, and +the Scintilla.NET text editor. + +Bumblebee works by injecting a custom JavaScript program that intercepts +client-side events and sends them to Bumblebee for analysis. In addition to +front-end events, Bumblebee also captures internal browser events, which it +then interprets to generate code in real time. Note that we developed Bumblebee +before the advent of now-popular LLMs. Bumblebee supports dynamic websites, +pop-ups, developer tools, live manual override, event debouncing, and filtering +hidden elements and scripts. + +Before settling on a desktop application, we contemplated designing Bumblebee +as a browser extension. We chose the desktop app because extensions don't offer +the deep, event-based control we needed. Besides, the company's security +policy, which prohibited browser extensions, would have complicated the +deployment of an extension-based solution. My first prototype used a C# binding +of the Chromium project. WebView's more intuitive API and its seamless +integration with Windows Forms led us to choose it over the Chromium wrapper. + +What began as a personal side project to improve my own workflow enabled us to +collectively improve the quality of our web scripts at a much larger scale. +Bumblebee predictably reduced the time we spent on authoring scripts from hours +to a few minutes. + diff --git a/_log/bumblebee/bee.mp4 b/_log/bumblebee/bee.mp4 new file mode 100644 index 0000000..835600d Binary files /dev/null and b/_log/bumblebee/bee.mp4 differ diff --git a/_log/bumblebee/poster.png b/_log/bumblebee/poster.png new file mode 100644 index 0000000..6dc955e Binary files /dev/null and b/_log/bumblebee/poster.png differ diff --git a/_log/bumblebee/thumb_sm.png b/_log/bumblebee/thumb_sm.png new file mode 100644 index 0000000..f7cfbf3 Binary files /dev/null and b/_log/bumblebee/thumb_sm.png differ diff --git a/_log/e-reader.md b/_log/e-reader.md new file mode 100644 index 0000000..cab744a --- /dev/null +++ b/_log/e-reader.md @@ -0,0 +1,94 @@ +--- +title: Experimental e-reader +date: 2023-10-24 +layout: post +project: true +thumbnail: thumb_sm.png +--- + +This project features an experimental e-reader powered by an ESP-WROOM-32 +development board and a 7.5-inch Waveshare +e-paper display built with the intention of learning about e-paper displays. + + + +## Introduction + +The prototype e-reader comprises an ESP32 microcontroller, an e-paper display +HAT, and three buttons: yellow, blue, and white for turning the page backwards, +forwards, and putting the device to sleep, respectively. The prototype does not +store books on the microcontroller. It streams books from a server over HTTP. +The e-reader employs RTC memory to record the reading progress between +sessions. + +The most formidable challenge when trying to build an e-reader with an ESP32 is +its limited memory and storage. My ESP-WROOM-32 has a total of 512KB of SRAM +and 4MB of flash memory, which the freeRTOS, ESP-IDF, and the e-reader +application must share. To put things into perspective, a Kindle Paperwhite has +at least 256MB of memory and 8GB of storage. That is 500x more memory than what +I'd have to work with. + +Despite its size, as microcontrollers go, ESP32 is a powerful system-on-a-chip +with a 160MHz dual-core processor and integrated WiFi. So, I thought it’d be +amusing to embrace the constraints and build my e-reader using 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, one for each page of the book. The +dimensions of each bitmap are equal to the size of the display. Each byte of +the bitmap encodes information for rendering eight pixels. For my display, +which has a resolution of 480x800, the bitmaps are laid out along 48KB +boundaries. This simple file format lends well to HTTP streaming, which is its +main advantage, as we will soon see. + +The pdftoebm.py script enclosed in the tarball at the end of the page converts +PDF documents to EBM files. + +## How does it work? + +As the e-reader has no storage, it can't store books locally. Instead, it +downloads pages of the EBM file over HTTP from the location pointed to by the +`EBM_ARCH_URL` setting in the Kconfig.projbuild file on demand. To read a +different book, we have to replace the old file with the new one or change the +`EBM_ARCH_URL` value. The latter requires us 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 by pressing a +button, one of the microprocessor's two cores transfers it from the buffer to +the display over a Serial Peripheral Interface (SPI). The other downloads a new +page in the background. I used the ESP-IDF task API to schedule the two tasks +on different cores of the multicore processor to make the reader more +responsive. + +I designed the EBM format with HTTP streaming in mind. Since the pages are laid +out in the EBM file along predictable boundaries, the e-reader can request +pages by specifying the offset and the chunk size in the HTTP Range header. Any +web server will process this request without custom logic. + +## Epilogue + +My fascination with e-paper began back in 2017, when I was tasked with +installing a few displays in a car park. Having no idea how they worked, I +remember watching the languid screens refresh like a Muggle witnessing magic. +This project was born out of that enduring curiosity and love of e-paper +technology. + +Why did I go to the trouble of building a rudimentary e-reader when I could +easily buy a more capable commercial e-reader? First of all, it's to prove to +myself that I can. More importantly, there's a quiet satisfaction to reading on +hardware you built yourself. You are no longer the powerless observer watching +the magic happen from the sidelines. You become the wizard who makes the +invisible particles swirl into form by whispering C to them. There's only one +way to experience that. + +Files: [source.tar.gz](source.tar.gz) diff --git a/_log/e-reader/circuit.svg b/_log/e-reader/circuit.svg new file mode 100644 index 0000000..fd7508b --- /dev/null +++ b/_log/e-reader/circuit.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + 10 kΩ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 2 + 3 + 4 + + + + + + + + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + + + + + 5 + 6 + 7 + 8 + BUS + E-Paper Display HAT + + + CS + + DC + + DIN + + CLK + + BUSY + + RST + + GND + + VCC + + ESP-WROOM-32 + + IO21 + + + IO5 + + + IO16 + + + IO23 + + + IO18 + + + IO22 + + + IO4 + + + IO2 + + + GND + + + 3V3 + + + GND + + + IO15 + + + + \ No newline at end of file diff --git a/_log/e-reader/ereader.mp4 b/_log/e-reader/ereader.mp4 new file mode 100644 index 0000000..89e05eb Binary files /dev/null and b/_log/e-reader/ereader.mp4 differ diff --git a/_log/e-reader/poster.png b/_log/e-reader/poster.png new file mode 100644 index 0000000..1e222d2 Binary files /dev/null and b/_log/e-reader/poster.png differ diff --git a/_log/e-reader/source.tar.gz b/_log/e-reader/source.tar.gz new file mode 100644 index 0000000..3e343a7 Binary files /dev/null and b/_log/e-reader/source.tar.gz differ diff --git a/_log/e-reader/thumb_sm.png b/_log/e-reader/thumb_sm.png new file mode 100644 index 0000000..7c971e8 Binary files /dev/null and b/_log/e-reader/thumb_sm.png differ diff --git a/_log/etlas.md b/_log/etlas.md new file mode 100644 index 0000000..c274401 --- /dev/null +++ b/_log/etlas.md @@ -0,0 +1,115 @@ +--- +title: "Etlas: e-paper dashboard" +date: 2024-09-05 +layout: post +project: true +thumbnail: thumb_sm.jpg +--- + +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. + + + + + + +
    frontback
    + +The top-left panel shows two weeks of end-of-day prices—the maximum the ESP32's +SRAM can hold—from the Polygon.io API. The price feed is relayed through a +FastCGI-wrapped Flask app hosted on a VPS. This lets me configure stock symbols +in its application settings. The app cycles through them as requests come in +from the ESP32. Running the Flask app as a FastCGI process while exposing it +via httpd with htpasswd authentication keeps the server code simple and secure. + +The following diagram outlines the Etlas's overall system architecture. + +![architecture](etlas_arch.png) + +The more prominent panel on the right of the display shows local and world news +from Channel NewsAsia. The MCU downloads and parses XML data from the RSS feed +directly before rendering it to the display. The character glyphs used are +stored as bitmaps in the sprites directory. I skipped the proxy for news to +avoid writing more server code, but in hindsight it limits the feeds Etlas can +handle. I will fix this in a future version. + +The middle and bottom right panels display the temperature and relative +humidity from the DHT22 sensor. The DHT22 uses pulse-width modulation to +transmit data to the host. The 26µs, 50µs, and 70µs pulses are too fast for the +ESP32 to measure reliably with standard APIs. Instead, the driver compares +relative pulse widths to differentiate zeros from ones: + +``` +static inline int dht_await_pin_state(int state, int timeout) +{ + int t; + static const uint16_t delta = 1; + + for (t = 0; t < timeout; t += delta) { + ets_delay_us(delta); + if (gpio_get_level(DHT_PIN) == state) + return t; + } + return 0; +} + +static inline int dht_get_raw_data(unsigned char buf[DHT_DATA_LEN]) +{ + int rc; + unsigned char i, pwl, pwh; + + gpio_set_level(DHT_PIN, 0); + ets_delay_us(1100); + gpio_set_level(DHT_PIN, 1); + + if (!dht_await_pin_state(0, 40)) { + rc = 1; + xQueueSend(dht_evt_queue, &rc, (TickType_t) 0); + return 0; + } + if (!dht_await_pin_state(1, 80)) { + rc = 2; + xQueueSend(dht_evt_queue, &rc, (TickType_t) 0); + return 0; + } + if (!dht_await_pin_state(0, 80)) { + rc = 3; + xQueueSend(dht_evt_queue, &rc, (TickType_t) 0); + return 0; + } + + for (i = 0; i < DHT_DATA_LEN; i++) { + if (!(pwl = dht_await_pin_state(1, 50))) { + rc = 4; + xQueueSend(dht_evt_queue, &rc, (TickType_t) 0); + return 0; + } + if (!(pwh = dht_await_pin_state(0, 70))) { + rc = 5; + xQueueSend(dht_evt_queue, &rc, (TickType_t) 0); + return 0; + } + buf[i] = pwh > pwl; + } + return 1; +} +``` + +I ported this implementation from ESP8266 +to ESP32—all credit for the algorithm belongs to them. + +Etlas is a networked embedded system. All acquisition, processing, and +rendering of data are performed on the ESP32's 160MHz microprocessor using less +than 512KB of SRAM. The embedded software that makes this possible is written +in C using ESP-IDF v5.2.1. The e-paper display driver is derived from Waveshare +examples for Arduino and STM32 +platforms. + +Etlas has been running reliably for over a year since August 2024. + +Files: [source.tar.gz](source.tar.gz) diff --git a/_log/etlas/circuit.svg b/_log/etlas/circuit.svg new file mode 100644 index 0000000..6255045 --- /dev/null +++ b/_log/etlas/circuit.svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + AM2302 + + + DATA + + GND + + VCC + E-Paper display HAT + + + CS + + DC + + DIN + + CLK + + BUSY + + RST + + PWR + + GND + + VCC + + + + + + + + + + + + + + 1 + 2 + 3 + 4 + + + + + 1 + 2 + 3 + 4 + + ESP32 Mini NodeMCU D1 + + IO19 + + + IO15 + + + GND + + + IO27 + + + IO14 + + + IO13 + + + IO25 + + + IO26 + + + IO16 + + + GND + + + 3V3 + + + \ No newline at end of file diff --git a/_log/etlas/dash.jpg b/_log/etlas/dash.jpg new file mode 100644 index 0000000..cf4efc6 Binary files /dev/null and b/_log/etlas/dash.jpg differ diff --git a/_log/etlas/etlas_arch.png b/_log/etlas/etlas_arch.png new file mode 100644 index 0000000..241e9f1 Binary files /dev/null and b/_log/etlas/etlas_arch.png differ diff --git a/_log/etlas/pcb.jpg b/_log/etlas/pcb.jpg new file mode 100644 index 0000000..fcb40fa Binary files /dev/null and b/_log/etlas/pcb.jpg differ diff --git a/_log/etlas/schematic.svg b/_log/etlas/schematic.svg new file mode 100644 index 0000000..3070dd1 --- /dev/null +++ b/_log/etlas/schematic.svg @@ -0,0 +1,4 @@ + + + +152726131425193V3GND
    ESP32 Mini NodeMCU D1
    ESP32 Mini NodeMCU D1
    DHT22
    DHT22
    E-paper HAT
    E-paper HAT
    3.3V
    3.3V
    3.3V
    3.3V
    3.3V
    3.3V
    CS
    CS
    DC
    DC
    RST
    RST
    CLK
    CLK
    MOSY
    MOSY
    BUSY
    BUSY
    VCC
    VCC
    GND
    GND
    VCC
    VCC
    GND
    GND
    DATA
    DATA
    Text is not SVG - cannot display
    \ No newline at end of file diff --git a/_log/etlas/source.tar.gz b/_log/etlas/source.tar.gz new file mode 100644 index 0000000..8b12cf6 Binary files /dev/null and b/_log/etlas/source.tar.gz differ diff --git a/_log/etlas/thumb_sm.jpg b/_log/etlas/thumb_sm.jpg new file mode 100644 index 0000000..a374879 Binary files /dev/null and b/_log/etlas/thumb_sm.jpg differ diff --git a/_log/fpm-door-lock.md b/_log/fpm-door-lock.md new file mode 100644 index 0000000..5b78b3e --- /dev/null +++ b/_log/fpm-door-lock.md @@ -0,0 +1,113 @@ +--- +title: Fingerprint door lock +date: 2025-08-18 +layout: post +project: true +thumbnail: thumb_sm.jpg +--- + +This project features a fingerprint door lock powered by an ATmega328P +microcontroller. + + + +## Overview + +The lock comprises three subsystems: the ATmega328P microcontroller, 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.8mA and 4.6mA 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, 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 microcontroller's sleep modes, 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 PCB + +For this project, I designed a custom PCB and had it fabricated by JLCPCB. Like +the software, the circuit is primarily concerned with optimizing power +consumption and extending battery life. + + + + + + + + + +
    + PCB + + Design +
    + PCB footprint +
    + +Consequently, 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 5V with a 16MHz crystal oscillator. To +further reduce power consumption, I modified the ATmega328P's fuses to run at +3.3V with an 8MHz 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 1A. The IN4007 diode in slot U2 +serves as a flyback diode, protecting the MOSFET from reverse currents +generated by the servo. + +Lastly, the 56kΩ and 10kΩ 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 +433MHz RF transceivers. 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 and 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/_log/fpm-door-lock/breadboard.jpg b/_log/fpm-door-lock/breadboard.jpg new file mode 100644 index 0000000..2bf47a9 Binary files /dev/null and b/_log/fpm-door-lock/breadboard.jpg differ diff --git a/_log/fpm-door-lock/footprint.png b/_log/fpm-door-lock/footprint.png new file mode 100644 index 0000000..5511bf1 Binary files /dev/null and b/_log/fpm-door-lock/footprint.png differ diff --git a/_log/fpm-door-lock/gerber.zip b/_log/fpm-door-lock/gerber.zip new file mode 100644 index 0000000..19a9d19 Binary files /dev/null and b/_log/fpm-door-lock/gerber.zip differ diff --git a/_log/fpm-door-lock/pcb.jpg b/_log/fpm-door-lock/pcb.jpg new file mode 100644 index 0000000..fbd800b Binary files /dev/null and b/_log/fpm-door-lock/pcb.jpg differ diff --git a/_log/fpm-door-lock/pcb1.jpg b/_log/fpm-door-lock/pcb1.jpg new file mode 100644 index 0000000..367187d Binary files /dev/null and b/_log/fpm-door-lock/pcb1.jpg differ diff --git a/_log/fpm-door-lock/source.tar.gz b/_log/fpm-door-lock/source.tar.gz new file mode 100644 index 0000000..ef23422 Binary files /dev/null and b/_log/fpm-door-lock/source.tar.gz differ diff --git a/_log/fpm-door-lock/thumb_sm.jpg b/_log/fpm-door-lock/thumb_sm.jpg new file mode 100644 index 0000000..a8fa534 Binary files /dev/null and b/_log/fpm-door-lock/thumb_sm.jpg differ diff --git a/_log/fpm-door-lock/video.mp4 b/_log/fpm-door-lock/video.mp4 new file mode 100644 index 0000000..a907a9b Binary files /dev/null and b/_log/fpm-door-lock/video.mp4 differ diff --git a/_log/matrix-digital-rain.md b/_log/matrix-digital-rain.md new file mode 100644 index 0000000..d8e2543 --- /dev/null +++ b/_log/matrix-digital-rain.md @@ -0,0 +1,96 @@ +--- +title: The Matrix digital rain +date: 2022-08-22 +layout: post +project: true +thumbnail: thumb_sm.png +--- + +"All I see is blonde, brunette, red head." The iconic digital rain from The +Matrix in C, with zero dependencies—not even ncurses. + + + +## Overview + +This is my fork of Domsson's beautiful Fakesteak. While going through his code, I +wondered what it would take to faithfully recreate the original Matrix from the +first movie without sacrificing its minimalism. + +My implementation supports: + + - Unicode characters. + - 24-bit RGB colors (truecolor). + - Glitches in the matrix. + - Ghosting effect of old monochrome CRT displays. + - Closely resembles the Matrix seen in the background during Neo and Cypher's + conversation. + +With no dependencies, compilation is trivial: + +``` +$ cc -O3 main.c -o matrix +$ ./matrix +``` + +## How does it work? + +The program tracks the state of the terminal, such as code points, background +and foreground colors, and cursor position, using multiple internal data +buffers. On each frame, it updates these buffers and repaints the screen using +ANSI escape codes: + +``` +static void term_print(const matrix *mat, size_t row, size_t col) +{ + size_t idx; + idx = mat_idx(mat, row, col); + wprintf(L"\x1b[%d;%dH\x1b[38;2;%d;%d;%dm%lc", + row, col, + mat->rgb[idx].color[R], + mat->rgb[idx].color[G], + mat->rgb[idx].color[B], + mat->code[idx]); +} +``` + +The ghosting effect is achieved by carefully scaling the RGB +channels before mixing them: + +``` +static void mat_shade(matrix *mat, size_t row, size_t col) +{ + unsigned char *color; + color = mat->rgb[mat_idx(mat, row, col)].color; + color[R] = color[R] - (color[R] - COLOR_BG_RED) / 2; + color[G] = color[G] - (color[G] - COLOR_BG_GRN) / 2; + color[B] = color[B] - (color[B] - COLOR_BG_BLU) / 2; +} +``` + +The ghosting function emulates the dim after glow by gradually transitioning +each raindrop's color towards the background color. This approach provides two +key benefits: straightforward color configuration that integrates naturally +with (Unix) ricing and high-fidelity recreation of the Matrix aesthetic. + +## Customization + +While you can alter almost every aspect, including speed, glitch frequency, and +rain density, the most common customizations are the color scheme and character +set. + +There are three color settings: head, tail, and background. You can configure +them using `COLOR_*_RED`, `COLOR_*_GRN`, and `COLOR_*_BLU` definitions found in +main.c. + +The `UNICODE_MIN` and `UNICODE_MAX` values control the Unicode block used. For +example, setting them to `0x30A1` and `0x30F6` rains Katakana, if a font that +supports Katakana is present on the system: + + + +Files: [source.tar.gz](source.tar.gz) diff --git a/_log/matrix-digital-rain/katakana.png b/_log/matrix-digital-rain/katakana.png new file mode 100644 index 0000000..b9df873 Binary files /dev/null and b/_log/matrix-digital-rain/katakana.png differ diff --git a/_log/matrix-digital-rain/matrix.mp4 b/_log/matrix-digital-rain/matrix.mp4 new file mode 100644 index 0000000..84a9839 Binary files /dev/null and b/_log/matrix-digital-rain/matrix.mp4 differ diff --git a/_log/matrix-digital-rain/poster.png b/_log/matrix-digital-rain/poster.png new file mode 100644 index 0000000..0321ad3 Binary files /dev/null and b/_log/matrix-digital-rain/poster.png differ diff --git a/_log/matrix-digital-rain/source.tar.gz b/_log/matrix-digital-rain/source.tar.gz new file mode 100644 index 0000000..fead280 Binary files /dev/null and b/_log/matrix-digital-rain/source.tar.gz differ diff --git a/_log/matrix-digital-rain/thumb_sm.png b/_log/matrix-digital-rain/thumb_sm.png new file mode 100644 index 0000000..d3f06c9 Binary files /dev/null and b/_log/matrix-digital-rain/thumb_sm.png differ diff --git a/_log/my-first-pcb.md b/_log/my-first-pcb.md new file mode 100644 index 0000000..c380ea0 --- /dev/null +++ b/_log/my-first-pcb.md @@ -0,0 +1,64 @@ +--- +title: My first PCB +date: 2025-04-26 +layout: post +thumbnail: thumb_sm.jpeg +--- + +In 2023, I started tinkering with DIY electronics as a hobby. Until now, I've +been using development boards like the Arduino Uno and ESP-32-WROOM so that I +can focus on the software. Recently, I decided to step outside of my comfort +zone and design a PCB from scratch for a door lock I'm working on. + +The lock comprises two subsystems: a fingerprint sensor in front of the door +and a servo connected to the physical lock behind the door. The fingerprint +sensor authenticates the person and signals the servo behind the door to unlock +the door over an encrypted RF channel. + + + + + + + + + + +
    + Design (front) +

    Footprint (front)

    +
    + PCB (front) +

    PCB (front)

    +
    + Design (back) +

    Footprint (back)

    +
    + PCB (back) +

    PCB (back)

    +
    + +The PCBs have two layers. A copper region serves as the ground plane. The 0.3mm +wide 1oz/ft2 copper traces can carry up to 500mA (the tracks +connecting the power source and the linear regulators have a width of 0.5mm). +Both subsystems were functional. I was able to control the servo reliably using +the fingerprint sensor. + +The designs aren't without flaws, however. The main shortcoming of the circuits +is that they draw significant amounts of quiescent currents despite employing +sleep modes. The linear regulators were a poor choice as they dissipate too +much heat. The fingerprint sensor and the servo draw 13.8mA (3.3V) and 4.6mA +(5V) respectively, as long as they are connected to the power supply. + +Although the circuit didn't draw more than 200mA without a load, the servo +under load could draw up to 600mA. I'm sailing too close to the wind with 0.3mm +copper traces. Instead, 0.4mm wide 2oz/ft2 traces would have been +safer. + +I'm working on improving the design to reduce idle current consumption and +extend the battery life. Despite its deficiencies, this was my first PCB +design, and I'm glad that it worked as well as it did. Custom PCB design marks +an important milestone in my DIY electronics journey. + +Files: [gerber_back.zip](gerber_back.zip), [gerber_front.zip](gerber_front.zip), + [source.tar.gz](source.tar.gz) diff --git a/_log/my-first-pcb/back.jpeg b/_log/my-first-pcb/back.jpeg new file mode 100644 index 0000000..f458e69 Binary files /dev/null and b/_log/my-first-pcb/back.jpeg differ diff --git a/_log/my-first-pcb/back_design.jpeg b/_log/my-first-pcb/back_design.jpeg new file mode 100644 index 0000000..b6c0f5d Binary files /dev/null and b/_log/my-first-pcb/back_design.jpeg differ diff --git a/_log/my-first-pcb/front.jpeg b/_log/my-first-pcb/front.jpeg new file mode 100644 index 0000000..2b2931f Binary files /dev/null and b/_log/my-first-pcb/front.jpeg differ diff --git a/_log/my-first-pcb/front_design.jpeg b/_log/my-first-pcb/front_design.jpeg new file mode 100644 index 0000000..f81f09c Binary files /dev/null and b/_log/my-first-pcb/front_design.jpeg differ diff --git a/_log/my-first-pcb/gerber_back.zip b/_log/my-first-pcb/gerber_back.zip new file mode 100644 index 0000000..26659ad Binary files /dev/null and b/_log/my-first-pcb/gerber_back.zip differ diff --git a/_log/my-first-pcb/gerber_front.zip b/_log/my-first-pcb/gerber_front.zip new file mode 100644 index 0000000..864334e Binary files /dev/null and b/_log/my-first-pcb/gerber_front.zip differ diff --git a/_log/my-first-pcb/source.tar.gz b/_log/my-first-pcb/source.tar.gz new file mode 100644 index 0000000..c31aa22 Binary files /dev/null and b/_log/my-first-pcb/source.tar.gz differ diff --git a/_log/my-first-pcb/thumb_sm.jpeg b/_log/my-first-pcb/thumb_sm.jpeg new file mode 100644 index 0000000..c275b12 Binary files /dev/null and b/_log/my-first-pcb/thumb_sm.jpeg differ diff --git a/_log/neo4j-a-star-search.md b/_log/neo4j-a-star-search.md deleted file mode 100644 index e52dad9..0000000 --- a/_log/neo4j-a-star-search.md +++ /dev/null @@ -1,317 +0,0 @@ ---- -title: Neo4J A* search -date: 2025-09-14 -layout: post ---- - -Back in 2018, we used Neo4J graph database to track the -movement of marine vessels. We were interested in the shortest path a ship -could take through a network of about 13,000 route points. Graph theoretic -algorithms provide optimal solutions to such problems, and the set of route -points lends itself well to graph-based modelling. - -A graph is a finite set of vertices, and a subset of vertex pairs (edges). -Edges can have weights. In the case of vessel tracking, the route points form -the vertices of a graph; the routes between them the edges; and the distances -between them the weights. For various reasons, people are interested in -minimizing (or maximizing) the weight of a path through a set of vertices, such -as the shortest path between two ports to predict a vessel's arrival time. - -Given a graph, an algorithm like Dijkstra's search could compute the shortest -path between two vertices. In fact, this was the algorithm Neo4J shipped with -at the time. One drawback of Dijkstra's algorithm is that it computes all the -shortest paths from the source to all other vertices before terminating at the -destination vertex. The time complexity of this exhaustive search prevented our -database from scaling beyond 4,000 route points. - -The following enhancement to Dijkstra's search, also known as the A* search, -employs a heuristic to steer the search in the direction of the destination -more quickly. In the case of our network of vessels, which are on the earth's -surface, spherical distance is a good candidate for a heuristic: - -``` -package org.neo4j.graphalgo.impl; - -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.neo4j.graphalgo.api.Graph; -import org.neo4j.graphalgo.core.utils.ProgressLogger; -import org.neo4j.graphalgo.core.utils.queue.IntPriorityQueue; -import org.neo4j.graphalgo.core.utils.queue.SharedIntPriorityQueue; -import org.neo4j.graphalgo.core.utils.traverse.SimpleBitSet; -import org.neo4j.graphdb.Direction; -import org.neo4j.graphdb.Node; -import org.neo4j.kernel.internal.GraphDatabaseAPI; - -import com.carrotsearch.hppc.IntArrayDeque; -import com.carrotsearch.hppc.IntDoubleMap; -import com.carrotsearch.hppc.IntDoubleScatterMap; -import com.carrotsearch.hppc.IntIntMap; -import com.carrotsearch.hppc.IntIntScatterMap; - -public class ShortestPathAStar extends Algorithm { - - private final GraphDatabaseAPI dbService; - private static final int PATH_END = -1; - - private Graph graph; - private final int nodeCount; - private IntDoubleMap gCosts; - private IntDoubleMap fCosts; - private double totalCost; - private IntPriorityQueue openNodes; - private IntIntMap path; - private IntArrayDeque shortestPath; - private SimpleBitSet closedNodes; - private final ProgressLogger progressLogger; - - public static final double NO_PATH_FOUND = -1.0; - - public ShortestPathAStar( - final Graph graph, - final GraphDatabaseAPI dbService) { - - this.graph = graph; - this.dbService = dbService; - - nodeCount = Math.toIntExact(graph.nodeCount()); - gCosts = new IntDoubleScatterMap(nodeCount); - fCosts = new IntDoubleScatterMap(nodeCount); - openNodes = SharedIntPriorityQueue.min( - nodeCount, - fCosts, - Double.MAX_VALUE); - path = new IntIntScatterMap(nodeCount); - closedNodes = new SimpleBitSet(nodeCount); - shortestPath = new IntArrayDeque(); - progressLogger = getProgressLogger(); - } - - public ShortestPathAStar compute( - final long startNode, - final long goalNode, - final String propertyKeyLat, - final String propertyKeyLon, - final Direction direction) { - - reset(); - - final int startNodeInternal = - graph.toMappedNodeId(startNode); - final double startNodeLat = - getNodeCoordinate(startNodeInternal, propertyKeyLat); - final double startNodeLon = - getNodeCoordinate(startNodeInternal, propertyKeyLon); - - final int goalNodeInternal = - graph.toMappedNodeId(goalNode); - final double goalNodeLat = - getNodeCoordinate(goalNodeInternal, propertyKeyLat); - final double goalNodeLon = - getNodeCoordinate(goalNodeInternal, propertyKeyLon); - - final double initialHeuristic = - computeHeuristic(startNodeLat, - startNodeLon, - goalNodeLat, - goalNodeLon); - - gCosts.put(startNodeInternal, 0.0); - fCosts.put(startNodeInternal, initialHeuristic); - openNodes.add(startNodeInternal, 0.0); - - run(goalNodeInternal, - propertyKeyLat, - propertyKeyLon, - direction); - - if (path.containsKey(goalNodeInternal)) { - totalCost = gCosts.get(goalNodeInternal); - int node = goalNodeInternal; - while (node != PATH_END) { - shortestPath.addFirst(node); - node = path.getOrDefault(node, PATH_END); - } - } - return this; - } - - private void run( - final int goalNodeId, - final String propertyKeyLat, - final String propertyKeyLon, - final Direction direction) { - - final double goalLat = - getNodeCoordinate(goalNodeId, propertyKeyLat); - final double goalLon = - getNodeCoordinate(goalNodeId, propertyKeyLon); - - while (!openNodes.isEmpty() && running()) { - int currentNodeId = openNodes.pop(); - if (currentNodeId == goalNodeId) { - return; - } - - closedNodes.put(currentNodeId); - - double currentNodeCost = - this.gCosts.getOrDefault( - currentNodeId, - Double.MAX_VALUE); - - graph.forEachRelationship( - currentNodeId, - direction, - (source, target, relationshipId, weight) -> { - double neighbourLat = - getNodeCoordinate(target, propertyKeyLat); - double neighbourLon = - getNodeCoordinate(target, propertyKeyLon); - double heuristic = - computeHeuristic( - neighbourLat, - neighbourLon, - goalLat, - goalLon); - - updateCosts( - source, - target, - weight + currentNodeCost, - heuristic); - - if (!closedNodes.contains(target)) { - openNodes.add(target, 0); - } - return true; - }); - - progressLogger.logProgress( - (double) currentNodeId / (nodeCount - 1)); - } - } - - private double computeHeuristic( - final double lat1, - final double lon1, - final double lat2, - final double lon2) { - - final int earthRadius = 6371; - final double kmToNM = 0.539957; - final double latDistance = Math.toRadians(lat2 - lat1); - final double lonDistance = Math.toRadians(lon2 - lon1); - final double a = Math.sin(latDistance / 2) - * Math.sin(latDistance / 2) - + Math.cos(Math.toRadians(lat1)) - * Math.cos(Math.toRadians(lat2)) - * Math.sin(lonDistance / 2) - * Math.sin(lonDistance / 2); - final double c = 2 - * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - final double distance = earthRadius * c * kmToNM; - return distance; - } - - private double getNodeCoordinate( - final int nodeId, - final String coordinateType) { - - final long neo4jId = graph.toOriginalNodeId(nodeId); - final Node node = dbService.getNodeById(neo4jId); - return (double) node.getProperty(coordinateType); - } - - private void updateCosts( - final int source, - final int target, - final double newCost, - final double heuristic) { - - final double oldCost = - gCosts.getOrDefault(target, Double.MAX_VALUE); - - if (newCost < oldCost) { - gCosts.put(target, newCost); - fCosts.put(target, newCost + heuristic); - path.put(target, source); - } - } - - private void reset() { - closedNodes.clear(); - openNodes.clear(); - gCosts.clear(); - fCosts.clear(); - path.clear(); - shortestPath.clear(); - totalCost = NO_PATH_FOUND; - } - - public Stream resultStream() { - return StreamSupport.stream( - shortestPath.spliterator(), false) - .map(cursor -> new Result( - graph.toOriginalNodeId(cursor.value), - gCosts.get(cursor.value))); - } - - public IntArrayDeque getFinalPath() { - return shortestPath; - } - - public double getTotalCost() { - return totalCost; - } - - public int getPathLength() { - return shortestPath.size(); - } - - @Override - public ShortestPathAStar me() { - return this; - } - - @Override - public ShortestPathAStar release() { - graph = null; - gCosts = null; - fCosts = null; - openNodes = null; - path = null; - shortestPath = null; - closedNodes = null; - return this; - } - - public static class Result { - - /** - * the neo4j node id - */ - public final Long nodeId; - - /** - * cost to reach the node from startNode - */ - public final Double cost; - - public Result(Long nodeId, Double cost) { - this.nodeId = nodeId; - this.cost = cost; - } - } -} -``` - -The heuristic function is domain-specific. If chosen wisely, it can -significantly speed up the search. In our case, we achieved a 300x speedup, -enabling us to expand our search from 4,000 to 13,000 route points. The v3.4.0 of the -Neo4J graph algorithms shipped with our A* search algorithm. - diff --git a/_poc/bumblebee.md b/_poc/bumblebee.md deleted file mode 100644 index cb0441d..0000000 --- a/_poc/bumblebee.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: "Bumblebee: browser automation" -date: 2025-04-02 -thumbnail: thumb_sm.png -layout: post ---- - -Bumblebee is a tool I built for one of my employers to automate the generation -of web scraping scripts. - - - -In 2024, we were tasked with collecting market data using various methods, -including scraping data from authorized websites for traders' use. - -Manual authoring of such scripts took time. The scripts were often brittle due -to the complexity of the modern web, and they lacked optimizations such as -bypassing the UI and retrieving the data files directly when possible, which -would have significantly reduced our compute costs. - -To alleviate these challenges, I, with the help of a colleague, Andy Zhang, -built Bumblebee: a web browser powered by C# Windows Forms, Microsoft Edge WebView2, and -the Scintilla.NET text editor. - -Bumblebee works by injecting a custom JavaScript program that intercepts -client-side events and sends them to Bumblebee for analysis. In addition to -front-end events, Bumblebee also captures internal browser events, which it -then interprets to generate code in real time. Note that we developed Bumblebee -before the advent of now-popular LLMs. Bumblebee supports dynamic websites, -pop-ups, developer tools, live manual override, event debouncing, and filtering -hidden elements and scripts. - -Before settling on a desktop application, we contemplated designing Bumblebee -as a browser extension. We chose the desktop app because extensions don't offer -the deep, event-based control we needed. Besides, the company's security -policy, which prohibited browser extensions, would have complicated the -deployment of an extension-based solution. My first prototype used a C# binding -of the Chromium project. WebView's more intuitive API and its seamless -integration with Windows Forms led us to choose it over the Chromium wrapper. - -What began as a personal side project to improve my own workflow enabled us to -collectively improve the quality of our web scripts at a much larger scale. -Bumblebee predictably reduced the time we spent on authoring scripts from hours -to a few minutes. - diff --git a/_poc/bumblebee/bee.mp4 b/_poc/bumblebee/bee.mp4 deleted file mode 100644 index 835600d..0000000 Binary files a/_poc/bumblebee/bee.mp4 and /dev/null differ diff --git a/_poc/bumblebee/poster.png b/_poc/bumblebee/poster.png deleted file mode 100644 index 6dc955e..0000000 Binary files a/_poc/bumblebee/poster.png and /dev/null differ diff --git a/_poc/bumblebee/thumb_sm.png b/_poc/bumblebee/thumb_sm.png deleted file mode 100644 index f7cfbf3..0000000 Binary files a/_poc/bumblebee/thumb_sm.png and /dev/null differ diff --git a/_poc/e-reader.md b/_poc/e-reader.md deleted file mode 100644 index fed283a..0000000 --- a/_poc/e-reader.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -title: Experimental e-reader -date: 2023-10-24 -thumbnail: thumb_sm.png -layout: post ---- - -This project features an experimental e-reader powered by an ESP-WROOM-32 -development board and a 7.5-inch Waveshare -e-paper display built with the intention of learning about e-paper displays. - - - -## Introduction - -The prototype e-reader comprises an ESP32 microcontroller, an e-paper display -HAT, and three buttons: yellow, blue, and white for turning the page backwards, -forwards, and putting the device to sleep, respectively. The prototype does not -store books on the microcontroller. It streams books from a server over HTTP. -The e-reader employs RTC memory to record the reading progress between -sessions. - -The most formidable challenge when trying to build an e-reader with an ESP32 is -its limited memory and storage. My ESP-WROOM-32 has a total of 512KB of SRAM -and 4MB of flash memory, which the freeRTOS, ESP-IDF, and the e-reader -application must share. To put things into perspective, a Kindle Paperwhite has -at least 256MB of memory and 8GB of storage. That is 500x more memory than what -I'd have to work with. - -Despite its size, as microcontrollers go, ESP32 is a powerful system-on-a-chip -with a 160MHz dual-core processor and integrated WiFi. So, I thought it’d be -amusing to embrace the constraints and build my e-reader using 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, one for each page of the book. The -dimensions of each bitmap are equal to the size of the display. Each byte of -the bitmap encodes information for rendering eight pixels. For my display, -which has a resolution of 480x800, the bitmaps are laid out along 48KB -boundaries. This simple file format lends well to HTTP streaming, which is its -main advantage, as we will soon see. - -The pdftoebm.py script enclosed in the tarball at the end of the page converts -PDF documents to EBM files. - -## How does it work? - -As the e-reader has no storage, it can't store books locally. Instead, it -downloads pages of the EBM file over HTTP from the location pointed to by the -`EBM_ARCH_URL` setting in the Kconfig.projbuild file on demand. To read a -different book, we have to replace the old file with the new one or change the -`EBM_ARCH_URL` value. The latter requires us 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 by pressing a -button, one of the microprocessor's two cores transfers it from the buffer to -the display over a Serial Peripheral Interface (SPI). The other downloads a new -page in the background. I used the ESP-IDF task API to schedule the two tasks -on different cores of the multicore processor to make the reader more -responsive. - -I designed the EBM format with HTTP streaming in mind. Since the pages are laid -out in the EBM file along predictable boundaries, the e-reader can request -pages by specifying the offset and the chunk size in the HTTP Range header. Any -web server will process this request without custom logic. - -## Epilogue - -My fascination with e-paper began back in 2017, when I was tasked with -installing a few displays in a car park. Having no idea how they worked, I -remember watching the languid screens refresh like a Muggle witnessing magic. -This project was born out of that enduring curiosity and love of e-paper -technology. - -Why did I go to the trouble of building a rudimentary e-reader when I could -easily buy a more capable commercial e-reader? First of all, it's to prove to -myself that I can. More importantly, there's a quiet satisfaction to reading on -hardware you built yourself. You are no longer the powerless observer watching -the magic happen from the sidelines. You become the wizard who makes the -invisible particles swirl into form by whispering C to them. There's only one -way to experience that. - -Files: [source.tar.gz](source.tar.gz) diff --git a/_poc/e-reader/circuit.svg b/_poc/e-reader/circuit.svg deleted file mode 100644 index fd7508b..0000000 --- a/_poc/e-reader/circuit.svg +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - - - - - - 10 kΩ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - 2 - 3 - 4 - - - - - - - - - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - - - - - 5 - 6 - 7 - 8 - BUS - E-Paper Display HAT - - - CS - - DC - - DIN - - CLK - - BUSY - - RST - - GND - - VCC - - ESP-WROOM-32 - - IO21 - - - IO5 - - - IO16 - - - IO23 - - - IO18 - - - IO22 - - - IO4 - - - IO2 - - - GND - - - 3V3 - - - GND - - - IO15 - - - - \ No newline at end of file diff --git a/_poc/e-reader/ereader.mp4 b/_poc/e-reader/ereader.mp4 deleted file mode 100644 index 89e05eb..0000000 Binary files a/_poc/e-reader/ereader.mp4 and /dev/null differ diff --git a/_poc/e-reader/poster.png b/_poc/e-reader/poster.png deleted file mode 100644 index 1e222d2..0000000 Binary files a/_poc/e-reader/poster.png and /dev/null differ diff --git a/_poc/e-reader/source.tar.gz b/_poc/e-reader/source.tar.gz deleted file mode 100644 index 3e343a7..0000000 Binary files a/_poc/e-reader/source.tar.gz and /dev/null differ diff --git a/_poc/e-reader/thumb_sm.png b/_poc/e-reader/thumb_sm.png deleted file mode 100644 index 7c971e8..0000000 Binary files a/_poc/e-reader/thumb_sm.png and /dev/null differ diff --git a/_poc/etlas.md b/_poc/etlas.md deleted file mode 100644 index 5eb0d25..0000000 --- a/_poc/etlas.md +++ /dev/null @@ -1,114 +0,0 @@ ---- -title: "Etlas: e-paper dashboard" -date: 2024-09-05 -thumbnail: thumb_sm.jpg -layout: post ---- - -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. - - - - - - -
    frontback
    - -The top-left panel shows two weeks of end-of-day prices—the maximum the ESP32's -SRAM can hold—from the Polygon.io API. The price feed is relayed through a -FastCGI-wrapped Flask app hosted on a VPS. This lets me configure stock symbols -in its application settings. The app cycles through them as requests come in -from the ESP32. Running the Flask app as a FastCGI process while exposing it -via httpd with htpasswd authentication keeps the server code simple and secure. - -The following diagram outlines the Etlas's overall system architecture. - -![architecture](etlas_arch.png) - -The more prominent panel on the right of the display shows local and world news -from Channel NewsAsia. The MCU downloads and parses XML data from the RSS feed -directly before rendering it to the display. The character glyphs used are -stored as bitmaps in the sprites directory. I skipped the proxy for news to -avoid writing more server code, but in hindsight it limits the feeds Etlas can -handle. I will fix this in a future version. - -The middle and bottom right panels display the temperature and relative -humidity from the DHT22 sensor. The DHT22 uses pulse-width modulation to -transmit data to the host. The 26µs, 50µs, and 70µs pulses are too fast for the -ESP32 to measure reliably with standard APIs. Instead, the driver compares -relative pulse widths to differentiate zeros from ones: - -``` -static inline int dht_await_pin_state(int state, int timeout) -{ - int t; - static const uint16_t delta = 1; - - for (t = 0; t < timeout; t += delta) { - ets_delay_us(delta); - if (gpio_get_level(DHT_PIN) == state) - return t; - } - return 0; -} - -static inline int dht_get_raw_data(unsigned char buf[DHT_DATA_LEN]) -{ - int rc; - unsigned char i, pwl, pwh; - - gpio_set_level(DHT_PIN, 0); - ets_delay_us(1100); - gpio_set_level(DHT_PIN, 1); - - if (!dht_await_pin_state(0, 40)) { - rc = 1; - xQueueSend(dht_evt_queue, &rc, (TickType_t) 0); - return 0; - } - if (!dht_await_pin_state(1, 80)) { - rc = 2; - xQueueSend(dht_evt_queue, &rc, (TickType_t) 0); - return 0; - } - if (!dht_await_pin_state(0, 80)) { - rc = 3; - xQueueSend(dht_evt_queue, &rc, (TickType_t) 0); - return 0; - } - - for (i = 0; i < DHT_DATA_LEN; i++) { - if (!(pwl = dht_await_pin_state(1, 50))) { - rc = 4; - xQueueSend(dht_evt_queue, &rc, (TickType_t) 0); - return 0; - } - if (!(pwh = dht_await_pin_state(0, 70))) { - rc = 5; - xQueueSend(dht_evt_queue, &rc, (TickType_t) 0); - return 0; - } - buf[i] = pwh > pwl; - } - return 1; -} -``` - -I ported this implementation from ESP8266 -to ESP32—all credit for the algorithm belongs to them. - -Etlas is a networked embedded system. All acquisition, processing, and -rendering of data are performed on the ESP32's 160MHz microprocessor using less -than 512KB of SRAM. The embedded software that makes this possible is written -in C using ESP-IDF v5.2.1. The e-paper display driver is derived from Waveshare -examples for Arduino and STM32 -platforms. - -Etlas has been running reliably for over a year since August 2024. - -Files: [source.tar.gz](source.tar.gz) diff --git a/_poc/etlas/circuit.svg b/_poc/etlas/circuit.svg deleted file mode 100644 index 6255045..0000000 --- a/_poc/etlas/circuit.svg +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - AM2302 - - - DATA - - GND - - VCC - E-Paper display HAT - - - CS - - DC - - DIN - - CLK - - BUSY - - RST - - PWR - - GND - - VCC - - - - - - - - - - - - - - 1 - 2 - 3 - 4 - - - - - 1 - 2 - 3 - 4 - - ESP32 Mini NodeMCU D1 - - IO19 - - - IO15 - - - GND - - - IO27 - - - IO14 - - - IO13 - - - IO25 - - - IO26 - - - IO16 - - - GND - - - 3V3 - - - \ No newline at end of file diff --git a/_poc/etlas/dash.jpg b/_poc/etlas/dash.jpg deleted file mode 100644 index cf4efc6..0000000 Binary files a/_poc/etlas/dash.jpg and /dev/null differ diff --git a/_poc/etlas/etlas_arch.png b/_poc/etlas/etlas_arch.png deleted file mode 100644 index 241e9f1..0000000 Binary files a/_poc/etlas/etlas_arch.png and /dev/null differ diff --git a/_poc/etlas/pcb.jpg b/_poc/etlas/pcb.jpg deleted file mode 100644 index fcb40fa..0000000 Binary files a/_poc/etlas/pcb.jpg and /dev/null differ diff --git a/_poc/etlas/schematic.svg b/_poc/etlas/schematic.svg deleted file mode 100644 index 3070dd1..0000000 --- a/_poc/etlas/schematic.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -152726131425193V3GND
    ESP32 Mini NodeMCU D1
    ESP32 Mini NodeMCU D1
    DHT22
    DHT22
    E-paper HAT
    E-paper HAT
    3.3V
    3.3V
    3.3V
    3.3V
    3.3V
    3.3V
    CS
    CS
    DC
    DC
    RST
    RST
    CLK
    CLK
    MOSY
    MOSY
    BUSY
    BUSY
    VCC
    VCC
    GND
    GND
    VCC
    VCC
    GND
    GND
    DATA
    DATA
    Text is not SVG - cannot display
    \ No newline at end of file diff --git a/_poc/etlas/source.tar.gz b/_poc/etlas/source.tar.gz deleted file mode 100644 index 8b12cf6..0000000 Binary files a/_poc/etlas/source.tar.gz and /dev/null differ diff --git a/_poc/etlas/thumb_sm.jpg b/_poc/etlas/thumb_sm.jpg deleted file mode 100644 index a374879..0000000 Binary files a/_poc/etlas/thumb_sm.jpg and /dev/null differ diff --git a/_poc/fpm-door-lock.md b/_poc/fpm-door-lock.md deleted file mode 100644 index 1241232..0000000 --- a/_poc/fpm-door-lock.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -title: Fingerprint door lock -date: 2025-10-03 -thumbnail: thumb_sm.jpg -layout: post ---- - -This project features a fingerprint door lock powered by an ATmega328P -microcontroller. - - - -## Overview - -The lock comprises three subsystems: the ATmega328P microcontroller, 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.8mA and 4.6mA 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, 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 microcontroller's sleep modes, 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 PCB - -For this project, I designed a custom PCB and had it fabricated by JLCPCB. Like -the software, the circuit is primarily concerned with optimizing power -consumption and extending battery life. - - - - - - - - - -
    - PCB - - Design -
    - PCB footprint -
    - -Consequently, 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 5V with a 16MHz crystal oscillator. To -further reduce power consumption, I modified the ATmega328P's fuses to run at -3.3V with an 8MHz 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 1A. The IN4007 diode in slot U2 -serves as a flyback diode, protecting the MOSFET from reverse currents -generated by the servo. - -Lastly, the 56kΩ and 10kΩ 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 -433MHz RF transceivers. 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 and 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/_poc/fpm-door-lock/breadboard.jpg b/_poc/fpm-door-lock/breadboard.jpg deleted file mode 100644 index 2bf47a9..0000000 Binary files a/_poc/fpm-door-lock/breadboard.jpg and /dev/null differ diff --git a/_poc/fpm-door-lock/footprint.png b/_poc/fpm-door-lock/footprint.png deleted file mode 100644 index 5511bf1..0000000 Binary files a/_poc/fpm-door-lock/footprint.png and /dev/null differ diff --git a/_poc/fpm-door-lock/gerber.zip b/_poc/fpm-door-lock/gerber.zip deleted file mode 100644 index 19a9d19..0000000 Binary files a/_poc/fpm-door-lock/gerber.zip and /dev/null differ diff --git a/_poc/fpm-door-lock/pcb.jpg b/_poc/fpm-door-lock/pcb.jpg deleted file mode 100644 index fbd800b..0000000 Binary files a/_poc/fpm-door-lock/pcb.jpg and /dev/null differ diff --git a/_poc/fpm-door-lock/pcb1.jpg b/_poc/fpm-door-lock/pcb1.jpg deleted file mode 100644 index 367187d..0000000 Binary files a/_poc/fpm-door-lock/pcb1.jpg and /dev/null differ diff --git a/_poc/fpm-door-lock/source.tar.gz b/_poc/fpm-door-lock/source.tar.gz deleted file mode 100644 index ef23422..0000000 Binary files a/_poc/fpm-door-lock/source.tar.gz and /dev/null differ diff --git a/_poc/fpm-door-lock/thumb_sm.jpg b/_poc/fpm-door-lock/thumb_sm.jpg deleted file mode 100644 index a8fa534..0000000 Binary files a/_poc/fpm-door-lock/thumb_sm.jpg and /dev/null differ diff --git a/_poc/fpm-door-lock/video.mp4 b/_poc/fpm-door-lock/video.mp4 deleted file mode 100644 index a907a9b..0000000 Binary files a/_poc/fpm-door-lock/video.mp4 and /dev/null differ diff --git a/_poc/matrix-digital-rain.md b/_poc/matrix-digital-rain.md deleted file mode 100644 index 6d15008..0000000 --- a/_poc/matrix-digital-rain.md +++ /dev/null @@ -1,95 +0,0 @@ ---- -title: The Matrix digital rain -date: 2024-01-12 -thumbnail: thumb_sm.png -layout: post ---- - -"All I see is blonde, brunette, red head." The iconic digital rain from The -Matrix in C, with zero dependencies—not even ncurses. - - - -## Overview - -This is my fork of Domsson's beautiful Fakesteak. While going through his code, I -wondered what it would take to faithfully recreate the original Matrix from the -first movie without sacrificing its minimalism. - -My implementation supports: - - - Unicode characters. - - 24-bit RGB colors (truecolor). - - Glitches in the matrix. - - Ghosting effect of old monochrome CRT displays. - - Closely resembles the Matrix seen in the background during Neo and Cypher's - conversation. - -With no dependencies, compilation is trivial: - -``` -$ cc -O3 main.c -o matrix -$ ./matrix -``` - -## How does it work? - -The program tracks the state of the terminal, such as code points, background -and foreground colors, and cursor position, using multiple internal data -buffers. On each frame, it updates these buffers and repaints the screen using -ANSI escape codes: - -``` -static void term_print(const matrix *mat, size_t row, size_t col) -{ - size_t idx; - idx = mat_idx(mat, row, col); - wprintf(L"\x1b[%d;%dH\x1b[38;2;%d;%d;%dm%lc", - row, col, - mat->rgb[idx].color[R], - mat->rgb[idx].color[G], - mat->rgb[idx].color[B], - mat->code[idx]); -} -``` - -The ghosting effect is achieved by carefully scaling the RGB -channels before mixing them: - -``` -static void mat_shade(matrix *mat, size_t row, size_t col) -{ - unsigned char *color; - color = mat->rgb[mat_idx(mat, row, col)].color; - color[R] = color[R] - (color[R] - COLOR_BG_RED) / 2; - color[G] = color[G] - (color[G] - COLOR_BG_GRN) / 2; - color[B] = color[B] - (color[B] - COLOR_BG_BLU) / 2; -} -``` - -The ghosting function emulates the dim after glow by gradually transitioning -each raindrop's color towards the background color. This approach provides two -key benefits: straightforward color configuration that integrates naturally -with (Unix) ricing and high-fidelity recreation of the Matrix aesthetic. - -## Customization - -While you can alter almost every aspect, including speed, glitch frequency, and -rain density, the most common customizations are the color scheme and character -set. - -There are three color settings: head, tail, and background. You can configure -them using `COLOR_*_RED`, `COLOR_*_GRN`, and `COLOR_*_BLU` definitions found in -main.c. - -The `UNICODE_MIN` and `UNICODE_MAX` values control the Unicode block used. For -example, setting them to `0x30A1` and `0x30F6` rains Katakana, if a font that -supports Katakana is present on the system: - - - -Files: [source.tar.gz](source.tar.gz) diff --git a/_poc/matrix-digital-rain/katakana.png b/_poc/matrix-digital-rain/katakana.png deleted file mode 100644 index b9df873..0000000 Binary files a/_poc/matrix-digital-rain/katakana.png and /dev/null differ diff --git a/_poc/matrix-digital-rain/matrix.mp4 b/_poc/matrix-digital-rain/matrix.mp4 deleted file mode 100644 index 84a9839..0000000 Binary files a/_poc/matrix-digital-rain/matrix.mp4 and /dev/null differ diff --git a/_poc/matrix-digital-rain/poster.png b/_poc/matrix-digital-rain/poster.png deleted file mode 100644 index 0321ad3..0000000 Binary files a/_poc/matrix-digital-rain/poster.png and /dev/null differ diff --git a/_poc/matrix-digital-rain/source.tar.gz b/_poc/matrix-digital-rain/source.tar.gz deleted file mode 100644 index fead280..0000000 Binary files a/_poc/matrix-digital-rain/source.tar.gz and /dev/null differ diff --git a/_poc/matrix-digital-rain/thumb_sm.png b/_poc/matrix-digital-rain/thumb_sm.png deleted file mode 100644 index d3f06c9..0000000 Binary files a/_poc/matrix-digital-rain/thumb_sm.png and /dev/null differ diff --git a/_poc/my-first-pcb.md b/_poc/my-first-pcb.md deleted file mode 100644 index 9a88243..0000000 --- a/_poc/my-first-pcb.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -title: My first PCB -date: 2025-07-14 -layout: post -thumbnail: thumb_sm.jpeg ---- - -In 2023, I started tinkering with DIY electronics as a hobby. Until now, I've -been using development boards like the Arduino Uno and ESP-32-WROOM so that I -can focus on the software. Recently, I decided to step outside of my comfort -zone and design a PCB from scratch for a door lock I'm working on. - -The lock comprises two subsystems: a fingerprint sensor in front of the door -and a servo connected to the physical lock behind the door. The fingerprint -sensor authenticates the person and signals the servo behind the door to unlock -the door over an encrypted RF channel. - - - - - - - - - - -
    - Design (front) -

    Footprint (front)

    -
    - PCB (front) -

    PCB (front)

    -
    - Design (back) -

    Footprint (back)

    -
    - PCB (back) -

    PCB (back)

    -
    - -The PCBs have two layers. A copper region serves as the ground plane. The 0.3mm -wide 1oz/ft2 copper traces can carry up to 500mA (the tracks -connecting the power source and the linear regulators have a width of 0.5mm). -Both subsystems were functional. I was able to control the servo reliably using -the fingerprint sensor. - -The designs aren't without flaws, however. The main shortcoming of the circuits -is that they draw significant amounts of quiescent currents despite employing -sleep modes. The linear regulators were a poor choice as they dissipate too -much heat. The fingerprint sensor and the servo draw 13.8mA (3.3V) and 4.6mA -(5V) respectively, as long as they are connected to the power supply. - -Although the circuit didn't draw more than 200mA without a load, the servo -under load could draw up to 600mA. I'm sailing too close to the wind with 0.3mm -copper traces. Instead, 0.4mm wide 2oz/ft2 traces would have been -safer. - -I'm working on improving the design to reduce idle current consumption and -extend the battery life. Despite its deficiencies, this was my first PCB -design, and I'm glad that it worked as well as it did. Custom PCB design marks -an important milestone in my DIY electronics journey. - -Files: [gerber_back.zip](gerber_back.zip), [gerber_front.zip](gerber_front.zip), - [source.tar.gz](source.tar.gz) diff --git a/_poc/my-first-pcb/back.jpeg b/_poc/my-first-pcb/back.jpeg deleted file mode 100644 index f458e69..0000000 Binary files a/_poc/my-first-pcb/back.jpeg and /dev/null differ diff --git a/_poc/my-first-pcb/back_design.jpeg b/_poc/my-first-pcb/back_design.jpeg deleted file mode 100644 index b6c0f5d..0000000 Binary files a/_poc/my-first-pcb/back_design.jpeg and /dev/null differ diff --git a/_poc/my-first-pcb/front.jpeg b/_poc/my-first-pcb/front.jpeg deleted file mode 100644 index 2b2931f..0000000 Binary files a/_poc/my-first-pcb/front.jpeg and /dev/null differ diff --git a/_poc/my-first-pcb/front_design.jpeg b/_poc/my-first-pcb/front_design.jpeg deleted file mode 100644 index f81f09c..0000000 Binary files a/_poc/my-first-pcb/front_design.jpeg and /dev/null differ diff --git a/_poc/my-first-pcb/gerber_back.zip b/_poc/my-first-pcb/gerber_back.zip deleted file mode 100644 index 26659ad..0000000 Binary files a/_poc/my-first-pcb/gerber_back.zip and /dev/null differ diff --git a/_poc/my-first-pcb/gerber_front.zip b/_poc/my-first-pcb/gerber_front.zip deleted file mode 100644 index 864334e..0000000 Binary files a/_poc/my-first-pcb/gerber_front.zip and /dev/null differ diff --git a/_poc/my-first-pcb/source.tar.gz b/_poc/my-first-pcb/source.tar.gz deleted file mode 100644 index c31aa22..0000000 Binary files a/_poc/my-first-pcb/source.tar.gz and /dev/null differ diff --git a/_poc/my-first-pcb/thumb_sm.jpeg b/_poc/my-first-pcb/thumb_sm.jpeg deleted file mode 100644 index c275b12..0000000 Binary files a/_poc/my-first-pcb/thumb_sm.jpeg and /dev/null differ diff --git a/_site/404.html b/_site/404.html index 07b0843..00df26d 100644 --- a/_site/404.html +++ b/_site/404.html @@ -23,7 +23,7 @@ log
  • - poc + poc
  • abt diff --git a/_site/about/index.html b/_site/about/index.html index 4b4e827..2bb5f64 100644 --- a/_site/about/index.html +++ b/_site/about/index.html @@ -23,7 +23,7 @@ log
  • - poc + poc
  • abt diff --git a/_site/feed.xml b/_site/feed.xml index 3c01d71..dd2f441 100644 --- a/_site/feed.xml +++ b/_site/feed.xml @@ -1 +1 @@ -Jekyll2025-12-20T11:31:17+08:00/feed.xmlASCIIMX | PocW. D. Sadeep MadurangeFingerprint door lock2025-10-03T00:00:00+08:002025-10-03T00:00:00+08:00/poc/fpm-door-lockW. D. Sadeep MadurangeMy first PCB2025-07-14T00:00:00+08:002025-07-14T00:00:00+08:00/poc/my-first-pcbW. D. Sadeep MadurangeBumblebee: browser automation2025-04-02T00:00:00+08:002025-04-02T00:00:00+08:00/poc/bumblebeeW. D. Sadeep MadurangeEtlas: e-paper dashboard2024-09-05T00:00:00+08:002024-09-05T00:00:00+08:00/poc/etlasW. D. Sadeep MadurangeThe Matrix digital rain2024-01-12T00:00:00+08:002024-01-12T00:00:00+08:00/poc/matrix-digital-rainW. D. Sadeep MadurangeExperimental e-reader2023-10-24T00:00:00+08:002023-10-24T00:00:00+08:00/poc/e-readerW. D. Sadeep Madurange \ No newline at end of file +Jekyll2025-12-20T21:35:40+08:00http://localhost:4000/feed.xmlASCIIMX | LogW. D. Sadeep MadurangeHow to manage Suckless software installations2025-11-30T00:00:00+08:002025-11-30T00:00:00+08:00http://localhost:4000/log/suckless-softwareW. D. Sadeep MadurangeFingerprint door lock2025-08-18T00:00:00+08:002025-08-18T00:00:00+08:00http://localhost:4000/log/fpm-door-lockW. D. Sadeep MadurangeOn the use of MOSFETs as electronic switches2025-06-22T00:00:00+08:002025-06-22T00:00:00+08:00http://localhost:4000/log/mosfet-switchesW. D. Sadeep MadurangeHow to configure ATmega328P microcontrollers to run at 3.3V and 5V2025-06-10T00:00:00+08:002025-06-10T00:00:00+08:00http://localhost:4000/log/arduino-unoW. D. Sadeep MadurangeMy first PCB2025-04-26T00:00:00+08:002025-04-26T00:00:00+08:00http://localhost:4000/log/my-first-pcbW. D. Sadeep MadurangeBumblebee: browser automation2025-04-02T00:00:00+08:002025-04-02T00:00:00+08:00http://localhost:4000/log/bumblebeeW. D. Sadeep MadurangeHow to set up ATSAM3X8E microcontrollers for bare-metal programming in C2024-09-16T00:00:00+08:002024-09-16T00:00:00+08:00http://localhost:4000/log/arduino-dueW. D. Sadeep MadurangeEtlas: e-paper dashboard2024-09-05T00:00:00+08:002024-09-05T00:00:00+08:00http://localhost:4000/log/etlasW. D. Sadeep MadurangeExperimental e-reader2023-10-24T00:00:00+08:002023-10-24T00:00:00+08:00http://localhost:4000/log/e-readerW. D. Sadeep MadurangeThe Matrix digital rain2022-08-22T00:00:00+08:002022-08-22T00:00:00+08:00http://localhost:4000/log/matrix-digital-rainW. D. Sadeep Madurange \ No newline at end of file diff --git a/_site/index.html b/_site/index.html index a7f0029..f279bc5 100644 --- a/_site/index.html +++ b/_site/index.html @@ -23,7 +23,7 @@ log
  • - poc + poc
  • abt @@ -69,11 +69,11 @@ - Neo4J A* search + Fingerprint door lock @@ -99,7 +99,33 @@ + + + + + + + + My first PCB + + + + + + + + + + + Bumblebee: browser automation + + + @@ -112,7 +138,46 @@ + + + + + + + + Etlas: e-paper dashboard + + + + + + + + + + + Experimental e-reader + + + + + + + + + + + The Matrix digital rain + + + diff --git a/_site/log/arduino-due/index.html b/_site/log/arduino-due/index.html index c8bbc55..d2248bc 100644 --- a/_site/log/arduino-due/index.html +++ b/_site/log/arduino-due/index.html @@ -27,7 +27,7 @@ log
  • - poc + poc
  • abt @@ -42,7 +42,7 @@

    HOW TO SET UP ATSAM3X8E MICROCONTROLLERS FOR BARE-METAL PROGRAMMING IN C

    -
    05 OCTOBER 2024
    +
    16 SEPTEMBER 2024

    This article is a step-by-step guide for programming bare-metal ATSAM3X8E chips found on Arduino Due boards. It also includes notes on the chip’s memory layout diff --git a/_site/log/arduino-uno/index.html b/_site/log/arduino-uno/index.html index 612854f..45f6456 100644 --- a/_site/log/arduino-uno/index.html +++ b/_site/log/arduino-uno/index.html @@ -27,7 +27,7 @@ log

  • - poc + poc
  • abt @@ -42,7 +42,7 @@

    HOW TO CONFIGURE ATMEGA328P MICROCONTROLLERS TO RUN AT 3.3V AND 5V

    -
    10 APRIL 2025
    +
    10 JUNE 2025

    This is a quick reference for wiring up ATmega328P ICs to run at 5V and 3.3V. While the 5V configuration is common, the 3.3V configuration can be useful in diff --git a/_site/log/bumblebee/bee.mp4 b/_site/log/bumblebee/bee.mp4 new file mode 100644 index 0000000..835600d Binary files /dev/null and b/_site/log/bumblebee/bee.mp4 differ diff --git a/_site/log/bumblebee/index.html b/_site/log/bumblebee/index.html new file mode 100644 index 0000000..7fabd41 --- /dev/null +++ b/_site/log/bumblebee/index.html @@ -0,0 +1,103 @@ + + + + + Bumblebee: browser automation + + + + + Bumblebee: browser automation + + + + + + + + + +

    + + + +
    +
    +
    +

    BUMBLEBEE: BROWSER AUTOMATION

    +
    02 APRIL 2025
    +
    +

    Bumblebee is a tool I built for one of my employers to automate the generation +of web scraping scripts.

    + + + +

    In 2024, we were tasked with collecting market data using various methods, +including scraping data from authorized websites for traders’ use.

    + +

    Manual authoring of such scripts took time. The scripts were often brittle due +to the complexity of the modern web, and they lacked optimizations such as +bypassing the UI and retrieving the data files directly when possible, which +would have significantly reduced our compute costs.

    + +

    To alleviate these challenges, I, with the help of a colleague, Andy Zhang, +built Bumblebee: a web browser powered by C# Windows Forms, Microsoft Edge WebView2, and +the Scintilla.NET text editor.

    + +

    Bumblebee works by injecting a custom JavaScript program that intercepts +client-side events and sends them to Bumblebee for analysis. In addition to +front-end events, Bumblebee also captures internal browser events, which it +then interprets to generate code in real time. Note that we developed Bumblebee +before the advent of now-popular LLMs. Bumblebee supports dynamic websites, +pop-ups, developer tools, live manual override, event debouncing, and filtering +hidden elements and scripts.

    + +

    Before settling on a desktop application, we contemplated designing Bumblebee +as a browser extension. We chose the desktop app because extensions don’t offer +the deep, event-based control we needed. Besides, the company’s security +policy, which prohibited browser extensions, would have complicated the +deployment of an extension-based solution. My first prototype used a C# binding +of the Chromium project. WebView’s more intuitive API and its seamless +integration with Windows Forms led us to choose it over the Chromium wrapper.

    + +

    What began as a personal side project to improve my own workflow enabled us to +collectively improve the quality of our web scripts at a much larger scale. +Bumblebee predictably reduced the time we spent on authoring scripts from hours +to a few minutes.

    + +
    + +
    +
    +
    + + + + + + diff --git a/_site/log/bumblebee/poster.png b/_site/log/bumblebee/poster.png new file mode 100644 index 0000000..6dc955e Binary files /dev/null and b/_site/log/bumblebee/poster.png differ diff --git a/_site/log/bumblebee/thumb_sm.png b/_site/log/bumblebee/thumb_sm.png new file mode 100644 index 0000000..f7cfbf3 Binary files /dev/null and b/_site/log/bumblebee/thumb_sm.png differ diff --git a/_site/log/e-reader/circuit.svg b/_site/log/e-reader/circuit.svg new file mode 100644 index 0000000..fd7508b --- /dev/null +++ b/_site/log/e-reader/circuit.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + 10 kΩ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 2 + 3 + 4 + + + + + + + + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + + + + + 5 + 6 + 7 + 8 + BUS + E-Paper Display HAT + + + CS + + DC + + DIN + + CLK + + BUSY + + RST + + GND + + VCC + + ESP-WROOM-32 + + IO21 + + + IO5 + + + IO16 + + + IO23 + + + IO18 + + + IO22 + + + IO4 + + + IO2 + + + GND + + + 3V3 + + + GND + + + IO15 + + + + \ No newline at end of file diff --git a/_site/log/e-reader/ereader.mp4 b/_site/log/e-reader/ereader.mp4 new file mode 100644 index 0000000..89e05eb Binary files /dev/null and b/_site/log/e-reader/ereader.mp4 differ diff --git a/_site/log/e-reader/index.html b/_site/log/e-reader/index.html new file mode 100644 index 0000000..3d61417 --- /dev/null +++ b/_site/log/e-reader/index.html @@ -0,0 +1,148 @@ + + + + + Experimental e-reader + + + + + Experimental e-reader + + + + + + + + + + + + + +
    +
    +
    +

    EXPERIMENTAL E-READER

    +
    24 OCTOBER 2023
    +
    +

    This project features an experimental e-reader powered by an ESP-WROOM-32 +development board and a 7.5-inch Waveshare +e-paper display built with the intention of learning about e-paper displays.

    + + + +

    Introduction

    + +

    The prototype e-reader comprises an ESP32 microcontroller, an e-paper display +HAT, and three buttons: yellow, blue, and white for turning the page backwards, +forwards, and putting the device to sleep, respectively. The prototype does not +store books on the microcontroller. It streams books from a server over HTTP. +The e-reader employs RTC memory to record the reading progress between +sessions.

    + +

    The most formidable challenge when trying to build an e-reader with an ESP32 is +its limited memory and storage. My ESP-WROOM-32 has a total of 512KB of SRAM +and 4MB of flash memory, which the freeRTOS, ESP-IDF, and the e-reader +application must share. To put things into perspective, a Kindle Paperwhite has +at least 256MB of memory and 8GB of storage. That is 500x more memory than what +I’d have to work with.

    + +

    Despite its size, as microcontrollers go, ESP32 is a powerful system-on-a-chip +with a 160MHz dual-core processor and integrated WiFi. So, I thought it’d be +amusing to embrace the constraints and build my e-reader using 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, one for each page of the book. The +dimensions of each bitmap are equal to the size of the display. Each byte of +the bitmap encodes information for rendering eight pixels. For my display, +which has a resolution of 480x800, the bitmaps are laid out along 48KB +boundaries. This simple file format lends well to HTTP streaming, which is its +main advantage, as we will soon see.

    + +

    The pdftoebm.py script enclosed in the tarball at the end of the page converts +PDF documents to EBM files.

    + +

    How does it work?

    + +

    As the e-reader has no storage, it can’t store books locally. Instead, it +downloads pages of the EBM file over HTTP from the location pointed to by the +EBM_ARCH_URL setting in the Kconfig.projbuild file on demand. To read a +different book, we have to replace the old file with the new one or change the +EBM_ARCH_URL value. The latter requires us 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 by pressing a +button, one of the microprocessor’s two cores transfers it from the buffer to +the display over a Serial Peripheral Interface (SPI). The other downloads a new +page in the background. I used the ESP-IDF task API to schedule the two tasks +on different cores of the multicore processor to make the reader more +responsive.

    + +

    I designed the EBM format with HTTP streaming in mind. Since the pages are laid +out in the EBM file along predictable boundaries, the e-reader can request +pages by specifying the offset and the chunk size in the HTTP Range header. Any +web server will process this request without custom logic.

    + +

    Epilogue

    + +

    My fascination with e-paper began back in 2017, when I was tasked with +installing a few displays in a car park. Having no idea how they worked, I +remember watching the languid screens refresh like a Muggle witnessing magic. +This project was born out of that enduring curiosity and love of e-paper +technology.

    + +

    Why did I go to the trouble of building a rudimentary e-reader when I could +easily buy a more capable commercial e-reader? First of all, it’s to prove to +myself that I can. More importantly, there’s a quiet satisfaction to reading on +hardware you built yourself. You are no longer the powerless observer watching +the magic happen from the sidelines. You become the wizard who makes the +invisible particles swirl into form by whispering C to them. There’s only one +way to experience that.

    + +

    Files: source.tar.gz

    +
    + +
    +
    +
    + + + + + + diff --git a/_site/log/e-reader/poster.png b/_site/log/e-reader/poster.png new file mode 100644 index 0000000..1e222d2 Binary files /dev/null and b/_site/log/e-reader/poster.png differ diff --git a/_site/log/e-reader/source.tar.gz b/_site/log/e-reader/source.tar.gz new file mode 100644 index 0000000..3e343a7 Binary files /dev/null and b/_site/log/e-reader/source.tar.gz differ diff --git a/_site/log/e-reader/thumb_sm.png b/_site/log/e-reader/thumb_sm.png new file mode 100644 index 0000000..7c971e8 Binary files /dev/null and b/_site/log/e-reader/thumb_sm.png differ diff --git a/_site/log/etlas/circuit.svg b/_site/log/etlas/circuit.svg new file mode 100644 index 0000000..6255045 --- /dev/null +++ b/_site/log/etlas/circuit.svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + AM2302 + + + DATA + + GND + + VCC + E-Paper display HAT + + + CS + + DC + + DIN + + CLK + + BUSY + + RST + + PWR + + GND + + VCC + + + + + + + + + + + + + + 1 + 2 + 3 + 4 + + + + + 1 + 2 + 3 + 4 + + ESP32 Mini NodeMCU D1 + + IO19 + + + IO15 + + + GND + + + IO27 + + + IO14 + + + IO13 + + + IO25 + + + IO26 + + + IO16 + + + GND + + + 3V3 + + + \ No newline at end of file diff --git a/_site/log/etlas/dash.jpg b/_site/log/etlas/dash.jpg new file mode 100644 index 0000000..cf4efc6 Binary files /dev/null and b/_site/log/etlas/dash.jpg differ diff --git a/_site/log/etlas/etlas_arch.png b/_site/log/etlas/etlas_arch.png new file mode 100644 index 0000000..241e9f1 Binary files /dev/null and b/_site/log/etlas/etlas_arch.png differ diff --git a/_site/log/etlas/index.html b/_site/log/etlas/index.html new file mode 100644 index 0000000..e4a4a9e --- /dev/null +++ b/_site/log/etlas/index.html @@ -0,0 +1,166 @@ + + + + + Etlas: e-paper dashboard + + + + + Etlas: e-paper dashboard + + + + + + + + + + + + + +
    +
    +
    +

    ETLAS: E-PAPER DASHBOARD

    +
    05 SEPTEMBER 2024
    +
    +

    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.

    + + + + + + +
    frontback
    + +

    The top-left panel shows two weeks of end-of-day prices—the maximum the ESP32’s +SRAM can hold—from the Polygon.io API. The price feed is relayed through a +FastCGI-wrapped Flask app hosted on a VPS. This lets me configure stock symbols +in its application settings. The app cycles through them as requests come in +from the ESP32. Running the Flask app as a FastCGI process while exposing it +via httpd with htpasswd authentication keeps the server code simple and secure.

    + +

    The following diagram outlines the Etlas’s overall system architecture.

    + +

    architecture

    + +

    The more prominent panel on the right of the display shows local and world news +from Channel NewsAsia. The MCU downloads and parses XML data from the RSS feed +directly before rendering it to the display. The character glyphs used are +stored as bitmaps in the sprites directory. I skipped the proxy for news to +avoid writing more server code, but in hindsight it limits the feeds Etlas can +handle. I will fix this in a future version.

    + +

    The middle and bottom right panels display the temperature and relative +humidity from the DHT22 sensor. The DHT22 uses pulse-width modulation to +transmit data to the host. The 26µs, 50µs, and 70µs pulses are too fast for the +ESP32 to measure reliably with standard APIs. Instead, the driver compares +relative pulse widths to differentiate zeros from ones:

    + +
    static inline int dht_await_pin_state(int state, int timeout)
    +{
    +    int t;
    +    static const uint16_t delta = 1;
    +
    +    for (t = 0; t < timeout; t += delta) {
    +        ets_delay_us(delta);
    +        if (gpio_get_level(DHT_PIN) == state)
    +          return t;
    +    }
    +    return 0;
    +}
    +
    +static inline int dht_get_raw_data(unsigned char buf[DHT_DATA_LEN])
    +{
    +    int rc;
    +    unsigned char i, pwl, pwh;
    +
    +    gpio_set_level(DHT_PIN, 0);
    +    ets_delay_us(1100);
    +    gpio_set_level(DHT_PIN, 1);
    +
    +    if (!dht_await_pin_state(0, 40)) {
    +        rc = 1;
    +        xQueueSend(dht_evt_queue, &rc, (TickType_t) 0);
    +        return 0;
    +    }
    +    if (!dht_await_pin_state(1, 80)) {
    +        rc = 2;
    +        xQueueSend(dht_evt_queue, &rc, (TickType_t) 0);
    +        return 0;
    +    }
    +    if (!dht_await_pin_state(0, 80)) {
    +        rc = 3;
    +        xQueueSend(dht_evt_queue, &rc, (TickType_t) 0);
    +        return 0;
    +    }
    +
    +    for (i = 0; i < DHT_DATA_LEN; i++) {
    +        if (!(pwl = dht_await_pin_state(1, 50))) {
    +            rc = 4;
    +            xQueueSend(dht_evt_queue, &rc, (TickType_t) 0);
    +            return 0;
    +        }
    +        if (!(pwh = dht_await_pin_state(0, 70))) {
    +            rc = 5;
    +            xQueueSend(dht_evt_queue, &rc, (TickType_t) 0);
    +            return 0;
    +        }
    +        buf[i] = pwh > pwl;
    +    }
    +    return 1;
    +}
    +
    + +

    I ported this implementation from ESP8266 +to ESP32—all credit for the algorithm belongs to them.

    + +

    Etlas is a networked embedded system. All acquisition, processing, and +rendering of data are performed on the ESP32’s 160MHz microprocessor using less +than 512KB of SRAM. The embedded software that makes this possible is written +in C using ESP-IDF v5.2.1. The e-paper display driver is derived from Waveshare +examples for Arduino and STM32 +platforms.

    + +

    Etlas has been running reliably for over a year since August 2024.

    + +

    Files: source.tar.gz

    +
    + +
    +
    +
    + + + + + + diff --git a/_site/log/etlas/pcb.jpg b/_site/log/etlas/pcb.jpg new file mode 100644 index 0000000..fcb40fa Binary files /dev/null and b/_site/log/etlas/pcb.jpg differ diff --git a/_site/log/etlas/schematic.svg b/_site/log/etlas/schematic.svg new file mode 100644 index 0000000..3070dd1 --- /dev/null +++ b/_site/log/etlas/schematic.svg @@ -0,0 +1,4 @@ + + + +152726131425193V3GND
    ESP32 Mini NodeMCU D1
    ESP32 Mini NodeMCU D1
    DHT22
    DHT22
    E-paper HAT
    E-paper HAT
    3.3V
    3.3V
    3.3V
    3.3V
    3.3V
    3.3V
    CS
    CS
    DC
    DC
    RST
    RST
    CLK
    CLK
    MOSY
    MOSY
    BUSY
    BUSY
    VCC
    VCC
    GND
    GND
    VCC
    VCC
    GND
    GND
    DATA
    DATA
    Text is not SVG - cannot display
    \ No newline at end of file diff --git a/_site/log/etlas/source.tar.gz b/_site/log/etlas/source.tar.gz new file mode 100644 index 0000000..8b12cf6 Binary files /dev/null and b/_site/log/etlas/source.tar.gz differ diff --git a/_site/log/etlas/thumb_sm.jpg b/_site/log/etlas/thumb_sm.jpg new file mode 100644 index 0000000..a374879 Binary files /dev/null and b/_site/log/etlas/thumb_sm.jpg differ diff --git a/_site/log/fpm-door-lock/breadboard.jpg b/_site/log/fpm-door-lock/breadboard.jpg new file mode 100644 index 0000000..2bf47a9 Binary files /dev/null and b/_site/log/fpm-door-lock/breadboard.jpg differ diff --git a/_site/log/fpm-door-lock/footprint.png b/_site/log/fpm-door-lock/footprint.png new file mode 100644 index 0000000..5511bf1 Binary files /dev/null and b/_site/log/fpm-door-lock/footprint.png differ diff --git a/_site/log/fpm-door-lock/gerber.zip b/_site/log/fpm-door-lock/gerber.zip new file mode 100644 index 0000000..19a9d19 Binary files /dev/null and b/_site/log/fpm-door-lock/gerber.zip differ diff --git a/_site/log/fpm-door-lock/index.html b/_site/log/fpm-door-lock/index.html new file mode 100644 index 0000000..1460053 --- /dev/null +++ b/_site/log/fpm-door-lock/index.html @@ -0,0 +1,168 @@ + + + + + Fingerprint door lock + + + + + Fingerprint door lock + + + + + + + + + + + + + +
    +
    +
    +

    FINGERPRINT DOOR LOCK

    +
    18 AUGUST 2025
    +
    +

    This project features a fingerprint door lock powered by an ATmega328P +microcontroller.

    + + + +

    Overview

    + +

    The lock comprises three subsystems: the ATmega328P microcontroller, 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.8mA and 4.6mA 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, 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 microcontroller’s sleep modes, 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 PCB

    + +

    For this project, I designed a custom PCB and had it fabricated by JLCPCB. Like +the software, the circuit is primarily concerned with optimizing power +consumption and extending battery life.

    + + + + + + + + + +
    + PCB + + Design +
    + PCB footprint +
    + +

    Consequently, 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 5V with a 16MHz crystal oscillator. To +further reduce power consumption, I modified the ATmega328P’s fuses to run at +3.3V with an 8MHz 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 1A. The IN4007 diode in slot U2 +serves as a flyback diode, protecting the MOSFET from reverse currents +generated by the servo.

    + +

    Lastly, the 56kΩ and 10kΩ 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 +433MHz RF transceivers. 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 and 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, gerber.zip

    +
    + +
    +
    +
    + + + + + + diff --git a/_site/log/fpm-door-lock/pcb.jpg b/_site/log/fpm-door-lock/pcb.jpg new file mode 100644 index 0000000..fbd800b Binary files /dev/null and b/_site/log/fpm-door-lock/pcb.jpg differ diff --git a/_site/log/fpm-door-lock/pcb1.jpg b/_site/log/fpm-door-lock/pcb1.jpg new file mode 100644 index 0000000..367187d Binary files /dev/null and b/_site/log/fpm-door-lock/pcb1.jpg differ diff --git a/_site/log/fpm-door-lock/source.tar.gz b/_site/log/fpm-door-lock/source.tar.gz new file mode 100644 index 0000000..ef23422 Binary files /dev/null and b/_site/log/fpm-door-lock/source.tar.gz differ diff --git a/_site/log/fpm-door-lock/thumb_sm.jpg b/_site/log/fpm-door-lock/thumb_sm.jpg new file mode 100644 index 0000000..a8fa534 Binary files /dev/null and b/_site/log/fpm-door-lock/thumb_sm.jpg differ diff --git a/_site/log/fpm-door-lock/video.mp4 b/_site/log/fpm-door-lock/video.mp4 new file mode 100644 index 0000000..a907a9b Binary files /dev/null and b/_site/log/fpm-door-lock/video.mp4 differ diff --git a/_site/log/index.html b/_site/log/index.html index fb9802a..181a379 100644 --- a/_site/log/index.html +++ b/_site/log/index.html @@ -23,7 +23,7 @@ log
  • - poc + poc
  • abt @@ -59,11 +59,11 @@ - Neo4J A* search + Fingerprint door lock @@ -89,7 +89,33 @@ + + + + + + + + My first PCB + + + + + + + + + + + Bumblebee: browser automation + + + @@ -102,7 +128,46 @@ + + + + + + + + Etlas: e-paper dashboard + + + + + + + + + + + Experimental e-reader + + + + + + + + + + + The Matrix digital rain + + + diff --git a/_site/log/matrix-digital-rain/index.html b/_site/log/matrix-digital-rain/index.html new file mode 100644 index 0000000..26244b3 --- /dev/null +++ b/_site/log/matrix-digital-rain/index.html @@ -0,0 +1,148 @@ + + + + + The Matrix digital rain + + + + + The Matrix digital rain + + + + + + + + + + + + + +
    +
    +
    +

    THE MATRIX DIGITAL RAIN

    +
    22 AUGUST 2022
    +
    +

    “All I see is blonde, brunette, red head.” The iconic digital rain from The +Matrix in C, with zero dependencies—not even ncurses.

    + + + +

    Overview

    + +

    This is my fork of Domsson’s beautiful Fakesteak. While going through his code, I +wondered what it would take to faithfully recreate the original Matrix from the +first movie without sacrificing its minimalism.

    + +

    My implementation supports:

    + +
      +
    • Unicode characters.
    • +
    • 24-bit RGB colors (truecolor).
    • +
    • Glitches in the matrix.
    • +
    • Ghosting effect of old monochrome CRT displays.
    • +
    • Closely resembles the Matrix seen in the background during Neo and Cypher’s +conversation.
    • +
    + +

    With no dependencies, compilation is trivial:

    + +
    $ cc -O3 main.c -o matrix
    +$ ./matrix
    +
    + +

    How does it work?

    + +

    The program tracks the state of the terminal, such as code points, background +and foreground colors, and cursor position, using multiple internal data +buffers. On each frame, it updates these buffers and repaints the screen using +ANSI escape codes:

    + +
    static void term_print(const matrix *mat, size_t row, size_t col)
    +{
    +    size_t idx;
    +    idx = mat_idx(mat, row, col);
    +    wprintf(L"\x1b[%d;%dH\x1b[38;2;%d;%d;%dm%lc",
    +        row, col,
    +        mat->rgb[idx].color[R],
    +        mat->rgb[idx].color[G],
    +        mat->rgb[idx].color[B],
    +        mat->code[idx]);
    +}
    +
    + +

    The ghosting effect is achieved by carefully scaling the RGB +channels before mixing them:

    + +
    static void mat_shade(matrix *mat, size_t row, size_t col) 
    +{
    +    unsigned char *color;
    +    color = mat->rgb[mat_idx(mat, row, col)].color;
    +    color[R] = color[R] - (color[R] - COLOR_BG_RED) / 2;
    +    color[G] = color[G] - (color[G] - COLOR_BG_GRN) / 2;
    +    color[B] = color[B] - (color[B] - COLOR_BG_BLU) / 2;
    +}
    +
    + +

    The ghosting function emulates the dim after glow by gradually transitioning +each raindrop’s color towards the background color. This approach provides two +key benefits: straightforward color configuration that integrates naturally +with (Unix) ricing and high-fidelity recreation of the Matrix aesthetic.

    + +

    Customization

    + +

    While you can alter almost every aspect, including speed, glitch frequency, and +rain density, the most common customizations are the color scheme and character +set.

    + +

    There are three color settings: head, tail, and background. You can configure +them using COLOR_*_RED, COLOR_*_GRN, and COLOR_*_BLU definitions found in +main.c.

    + +

    The UNICODE_MIN and UNICODE_MAX values control the Unicode block used. For +example, setting them to 0x30A1 and 0x30F6 rains Katakana, if a font that +supports Katakana is present on the system:

    + +

    + +

    Files: source.tar.gz

    +
    + +
    +
    +
    + + + + + + diff --git a/_site/log/matrix-digital-rain/katakana.png b/_site/log/matrix-digital-rain/katakana.png new file mode 100644 index 0000000..b9df873 Binary files /dev/null and b/_site/log/matrix-digital-rain/katakana.png differ diff --git a/_site/log/matrix-digital-rain/matrix.mp4 b/_site/log/matrix-digital-rain/matrix.mp4 new file mode 100644 index 0000000..84a9839 Binary files /dev/null and b/_site/log/matrix-digital-rain/matrix.mp4 differ diff --git a/_site/log/matrix-digital-rain/poster.png b/_site/log/matrix-digital-rain/poster.png new file mode 100644 index 0000000..0321ad3 Binary files /dev/null and b/_site/log/matrix-digital-rain/poster.png differ diff --git a/_site/log/matrix-digital-rain/source.tar.gz b/_site/log/matrix-digital-rain/source.tar.gz new file mode 100644 index 0000000..fead280 Binary files /dev/null and b/_site/log/matrix-digital-rain/source.tar.gz differ diff --git a/_site/log/matrix-digital-rain/thumb_sm.png b/_site/log/matrix-digital-rain/thumb_sm.png new file mode 100644 index 0000000..d3f06c9 Binary files /dev/null and b/_site/log/matrix-digital-rain/thumb_sm.png differ diff --git a/_site/log/mosfet-switches/index.html b/_site/log/mosfet-switches/index.html index 9bf9d30..3b8f83b 100644 --- a/_site/log/mosfet-switches/index.html +++ b/_site/log/mosfet-switches/index.html @@ -27,7 +27,7 @@ log
  • - poc + poc
  • abt diff --git a/_site/log/my-first-pcb/back.jpeg b/_site/log/my-first-pcb/back.jpeg new file mode 100644 index 0000000..f458e69 Binary files /dev/null and b/_site/log/my-first-pcb/back.jpeg differ diff --git a/_site/log/my-first-pcb/back_design.jpeg b/_site/log/my-first-pcb/back_design.jpeg new file mode 100644 index 0000000..b6c0f5d Binary files /dev/null and b/_site/log/my-first-pcb/back_design.jpeg differ diff --git a/_site/log/my-first-pcb/front.jpeg b/_site/log/my-first-pcb/front.jpeg new file mode 100644 index 0000000..2b2931f Binary files /dev/null and b/_site/log/my-first-pcb/front.jpeg differ diff --git a/_site/log/my-first-pcb/front_design.jpeg b/_site/log/my-first-pcb/front_design.jpeg new file mode 100644 index 0000000..f81f09c Binary files /dev/null and b/_site/log/my-first-pcb/front_design.jpeg differ diff --git a/_site/log/my-first-pcb/gerber_back.zip b/_site/log/my-first-pcb/gerber_back.zip new file mode 100644 index 0000000..26659ad Binary files /dev/null and b/_site/log/my-first-pcb/gerber_back.zip differ diff --git a/_site/log/my-first-pcb/gerber_front.zip b/_site/log/my-first-pcb/gerber_front.zip new file mode 100644 index 0000000..864334e Binary files /dev/null and b/_site/log/my-first-pcb/gerber_front.zip differ diff --git a/_site/log/my-first-pcb/index.html b/_site/log/my-first-pcb/index.html new file mode 100644 index 0000000..735a1da --- /dev/null +++ b/_site/log/my-first-pcb/index.html @@ -0,0 +1,120 @@ + + + + + My first PCB + + + + + My first PCB + + + + + + + + + + + + + +
    +
    +
    +

    MY FIRST PCB

    +
    26 APRIL 2025
    +
    +

    In 2023, I started tinkering with DIY electronics as a hobby. Until now, I’ve +been using development boards like the Arduino Uno and ESP-32-WROOM so that I +can focus on the software. Recently, I decided to step outside of my comfort +zone and design a PCB from scratch for a door lock I’m working on.

    + +

    The lock comprises two subsystems: a fingerprint sensor in front of the door +and a servo connected to the physical lock behind the door. The fingerprint +sensor authenticates the person and signals the servo behind the door to unlock +the door over an encrypted RF channel.

    + + + + + + + + + + +
    + Design (front) +

    Footprint (front)

    +
    + PCB (front) +

    PCB (front)

    +
    + Design (back) +

    Footprint (back)

    +
    + PCB (back) +

    PCB (back)

    +
    + +

    The PCBs have two layers. A copper region serves as the ground plane. The 0.3mm +wide 1oz/ft2 copper traces can carry up to 500mA (the tracks +connecting the power source and the linear regulators have a width of 0.5mm). +Both subsystems were functional. I was able to control the servo reliably using +the fingerprint sensor.

    + +

    The designs aren’t without flaws, however. The main shortcoming of the circuits +is that they draw significant amounts of quiescent currents despite employing +sleep modes. The linear regulators were a poor choice as they dissipate too +much heat. The fingerprint sensor and the servo draw 13.8mA (3.3V) and 4.6mA +(5V) respectively, as long as they are connected to the power supply.

    + +

    Although the circuit didn’t draw more than 200mA without a load, the servo +under load could draw up to 600mA. I’m sailing too close to the wind with 0.3mm +copper traces. Instead, 0.4mm wide 2oz/ft2 traces would have been +safer.

    + +

    I’m working on improving the design to reduce idle current consumption and +extend the battery life. Despite its deficiencies, this was my first PCB +design, and I’m glad that it worked as well as it did. Custom PCB design marks +an important milestone in my DIY electronics journey.

    + +

    Files: gerber_back.zip, gerber_front.zip, + source.tar.gz

    +
    + +
    +
    +
    + + + + + + diff --git a/_site/log/my-first-pcb/source.tar.gz b/_site/log/my-first-pcb/source.tar.gz new file mode 100644 index 0000000..c31aa22 Binary files /dev/null and b/_site/log/my-first-pcb/source.tar.gz differ diff --git a/_site/log/my-first-pcb/thumb_sm.jpeg b/_site/log/my-first-pcb/thumb_sm.jpeg new file mode 100644 index 0000000..c275b12 Binary files /dev/null and b/_site/log/my-first-pcb/thumb_sm.jpeg differ diff --git a/_site/log/neo4j-a-star-search/index.html b/_site/log/neo4j-a-star-search/index.html deleted file mode 100644 index 9ea0751..0000000 --- a/_site/log/neo4j-a-star-search/index.html +++ /dev/null @@ -1,370 +0,0 @@ - - - - - Neo4J A* search - - - - - Neo4J A* search - - - - - - - - - - - - - -
    -
    -
    -

    NEO4J A* SEARCH

    -
    14 SEPTEMBER 2025
    -
    -

    Back in 2018, we used Neo4J graph database to track the -movement of marine vessels. We were interested in the shortest path a ship -could take through a network of about 13,000 route points. Graph theoretic -algorithms provide optimal solutions to such problems, and the set of route -points lends itself well to graph-based modelling.

    - -

    A graph is a finite set of vertices, and a subset of vertex pairs (edges). -Edges can have weights. In the case of vessel tracking, the route points form -the vertices of a graph; the routes between them the edges; and the distances -between them the weights. For various reasons, people are interested in -minimizing (or maximizing) the weight of a path through a set of vertices, such -as the shortest path between two ports to predict a vessel’s arrival time.

    - -

    Given a graph, an algorithm like Dijkstra’s search could compute the shortest -path between two vertices. In fact, this was the algorithm Neo4J shipped with -at the time. One drawback of Dijkstra’s algorithm is that it computes all the -shortest paths from the source to all other vertices before terminating at the -destination vertex. The time complexity of this exhaustive search prevented our -database from scaling beyond 4,000 route points.

    - -

    The following enhancement to Dijkstra’s search, also known as the A* search, -employs a heuristic to steer the search in the direction of the destination -more quickly. In the case of our network of vessels, which are on the earth’s -surface, spherical distance is a good candidate for a heuristic:

    - -
    package org.neo4j.graphalgo.impl;
    -
    -import java.util.stream.Stream;
    -import java.util.stream.StreamSupport;
    -
    -import org.neo4j.graphalgo.api.Graph;
    -import org.neo4j.graphalgo.core.utils.ProgressLogger;
    -import org.neo4j.graphalgo.core.utils.queue.IntPriorityQueue;
    -import org.neo4j.graphalgo.core.utils.queue.SharedIntPriorityQueue;
    -import org.neo4j.graphalgo.core.utils.traverse.SimpleBitSet;
    -import org.neo4j.graphdb.Direction;
    -import org.neo4j.graphdb.Node;
    -import org.neo4j.kernel.internal.GraphDatabaseAPI;
    -
    -import com.carrotsearch.hppc.IntArrayDeque;
    -import com.carrotsearch.hppc.IntDoubleMap;
    -import com.carrotsearch.hppc.IntDoubleScatterMap;
    -import com.carrotsearch.hppc.IntIntMap;
    -import com.carrotsearch.hppc.IntIntScatterMap;
    -
    -public class ShortestPathAStar extends Algorithm<ShortestPathAStar> {
    -    
    -    private final GraphDatabaseAPI dbService;
    -    private static final int PATH_END = -1;
    -    
    -    private Graph graph;
    -    private final int nodeCount;
    -    private IntDoubleMap gCosts;
    -    private IntDoubleMap fCosts;
    -    private double totalCost;
    -    private IntPriorityQueue openNodes;
    -    private IntIntMap path;
    -    private IntArrayDeque shortestPath;
    -    private SimpleBitSet closedNodes;
    -    private final ProgressLogger progressLogger;
    -    
    -    public static final double NO_PATH_FOUND = -1.0;
    -    
    -    public ShortestPathAStar(
    -        final Graph graph,
    -        final GraphDatabaseAPI dbService) {
    -
    -        this.graph = graph;
    -        this.dbService = dbService;
    -
    -        nodeCount = Math.toIntExact(graph.nodeCount());
    -        gCosts = new IntDoubleScatterMap(nodeCount);
    -        fCosts = new IntDoubleScatterMap(nodeCount);
    -        openNodes = SharedIntPriorityQueue.min(
    -            nodeCount,
    -            fCosts,
    -            Double.MAX_VALUE);
    -        path = new IntIntScatterMap(nodeCount);
    -        closedNodes = new SimpleBitSet(nodeCount);
    -        shortestPath = new IntArrayDeque();
    -        progressLogger = getProgressLogger();
    -    }
    -    
    -    public ShortestPathAStar compute(
    -        final long startNode,
    -        final long goalNode,
    -        final String propertyKeyLat,
    -        final String propertyKeyLon,
    -        final Direction direction) {
    -
    -        reset();
    -
    -        final int startNodeInternal = 
    -            graph.toMappedNodeId(startNode);
    -        final double startNodeLat =
    -            getNodeCoordinate(startNodeInternal, propertyKeyLat);
    -        final double startNodeLon = 
    -            getNodeCoordinate(startNodeInternal, propertyKeyLon);
    -
    -        final int goalNodeInternal =
    -            graph.toMappedNodeId(goalNode);
    -        final double goalNodeLat = 
    -            getNodeCoordinate(goalNodeInternal, propertyKeyLat);
    -        final double goalNodeLon = 
    -            getNodeCoordinate(goalNodeInternal, propertyKeyLon);
    -
    -        final double initialHeuristic = 
    -            computeHeuristic(startNodeLat,
    -                startNodeLon,
    -                goalNodeLat,
    -                goalNodeLon);
    -
    -        gCosts.put(startNodeInternal, 0.0);
    -        fCosts.put(startNodeInternal, initialHeuristic);
    -        openNodes.add(startNodeInternal, 0.0);
    -
    -        run(goalNodeInternal,
    -            propertyKeyLat,
    -            propertyKeyLon,
    -            direction);
    -
    -        if (path.containsKey(goalNodeInternal)) {
    -            totalCost = gCosts.get(goalNodeInternal);
    -            int node = goalNodeInternal;
    -            while (node != PATH_END) {
    -                shortestPath.addFirst(node);
    -                node = path.getOrDefault(node, PATH_END);
    -            }
    -        }
    -        return this;
    -    }
    -    
    -    private void run(
    -        final int goalNodeId,
    -        final String propertyKeyLat,
    -        final String propertyKeyLon,
    -        final Direction direction) {
    -
    -        final double goalLat = 
    -            getNodeCoordinate(goalNodeId, propertyKeyLat);
    -        final double goalLon =
    -            getNodeCoordinate(goalNodeId, propertyKeyLon);
    -
    -        while (!openNodes.isEmpty() && running()) {
    -            int currentNodeId = openNodes.pop();
    -            if (currentNodeId == goalNodeId) {
    -                return;
    -            }
    -
    -            closedNodes.put(currentNodeId);
    -
    -            double currentNodeCost = 
    -                this.gCosts.getOrDefault(
    -                    currentNodeId, 
    -                    Double.MAX_VALUE);
    -
    -            graph.forEachRelationship(
    -                currentNodeId,
    -                direction,
    -                (source, target, relationshipId, weight) -> {
    -                    double neighbourLat = 
    -                        getNodeCoordinate(target, propertyKeyLat);
    -                    double neighbourLon = 
    -                        getNodeCoordinate(target, propertyKeyLon);
    -                    double heuristic = 
    -                        computeHeuristic(
    -                            neighbourLat, 
    -                            neighbourLon, 
    -                            goalLat,
    -                            goalLon);
    -
    -                    updateCosts(
    -                        source,
    -                        target,
    -                        weight + currentNodeCost,
    -                        heuristic);
    -
    -                    if (!closedNodes.contains(target)) {
    -                        openNodes.add(target, 0);
    -                    }
    -                    return true;
    -                });
    -
    -            progressLogger.logProgress(
    -                (double) currentNodeId / (nodeCount - 1));
    -        }
    -    }
    -    
    -    private double computeHeuristic(
    -        final double lat1,
    -        final double lon1,
    -        final double lat2,
    -        final double lon2) {
    -
    -        final int earthRadius = 6371;
    -        final double kmToNM = 0.539957;
    -        final double latDistance = Math.toRadians(lat2 - lat1);
    -        final double lonDistance = Math.toRadians(lon2 - lon1);
    -        final double a = Math.sin(latDistance / 2)
    -            * Math.sin(latDistance / 2)
    -            + Math.cos(Math.toRadians(lat1))
    -            * Math.cos(Math.toRadians(lat2))
    -            * Math.sin(lonDistance / 2)
    -            * Math.sin(lonDistance / 2);
    -        final double c = 2
    -            * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    -        final double distance = earthRadius * c * kmToNM;
    -        return distance;
    -    }
    -    
    -    private double getNodeCoordinate(
    -        final int nodeId,
    -        final String coordinateType) {
    -
    -        final long neo4jId = graph.toOriginalNodeId(nodeId);
    -        final Node node = dbService.getNodeById(neo4jId);
    -        return (double) node.getProperty(coordinateType);
    -    }
    -    
    -    private void updateCosts(
    -        final int source, 
    -        final int target, 
    -        final double newCost,
    -        final double heuristic) {
    -
    -        final double oldCost = 
    -            gCosts.getOrDefault(target, Double.MAX_VALUE);
    -
    -        if (newCost < oldCost) {
    -            gCosts.put(target, newCost);
    -            fCosts.put(target, newCost + heuristic);
    -            path.put(target, source);
    -        }
    -    }
    -    
    -    private void reset() {
    -        closedNodes.clear();
    -        openNodes.clear();
    -        gCosts.clear();
    -        fCosts.clear();
    -        path.clear();
    -        shortestPath.clear();
    -        totalCost = NO_PATH_FOUND;
    -    }
    -    
    -    public Stream<Result> resultStream() {
    -        return StreamSupport.stream(
    -            shortestPath.spliterator(), false)
    -                .map(cursor -> new Result(
    -                    graph.toOriginalNodeId(cursor.value),
    -                    gCosts.get(cursor.value)));
    -    }
    -
    -    public IntArrayDeque getFinalPath() {
    -        return shortestPath;
    -    }
    -    
    -    public double getTotalCost() {
    -        return totalCost;
    -    }
    -
    -    public int getPathLength() {
    -        return shortestPath.size();
    -    }
    -    
    -    @Override
    -    public ShortestPathAStar me() {
    -        return this;
    -    }
    -
    -    @Override
    -    public ShortestPathAStar release() {
    -        graph = null;
    -        gCosts = null;
    -        fCosts = null;
    -        openNodes = null;
    -        path = null;
    -        shortestPath = null;
    -        closedNodes = null;
    -        return this;
    -    }
    -    
    -    public static class Result {
    -
    -        /**
    -         * the neo4j node id
    -         */
    -        public final Long nodeId;
    -
    -        /**
    -         * cost to reach the node from startNode
    -         */
    -        public final Double cost;
    -
    -        public Result(Long nodeId, Double cost) {
    -            this.nodeId = nodeId;
    -            this.cost = cost;
    -        }
    -    }
    -}
    -
    - -

    The heuristic function is domain-specific. If chosen wisely, it can -significantly speed up the search. In our case, we achieved a 300x speedup, -enabling us to expand our search from 4,000 to 13,000 route points. The v3.4.0 of the -Neo4J graph algorithms shipped with our A* search algorithm.

    - -
    - -
    -
    -
    - - - - - - diff --git a/_site/log/suckless-software/index.html b/_site/log/suckless-software/index.html index 30e9fe1..f9e10e4 100644 --- a/_site/log/suckless-software/index.html +++ b/_site/log/suckless-software/index.html @@ -27,7 +27,7 @@ log
  • - poc + poc
  • abt diff --git a/_site/poc/bumblebee/bee.mp4 b/_site/poc/bumblebee/bee.mp4 deleted file mode 100644 index 835600d..0000000 Binary files a/_site/poc/bumblebee/bee.mp4 and /dev/null differ diff --git a/_site/poc/bumblebee/index.html b/_site/poc/bumblebee/index.html deleted file mode 100644 index 6ac52f6..0000000 --- a/_site/poc/bumblebee/index.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - Bumblebee: browser automation - - - - - Bumblebee: browser automation - - - - - - - - - - - - - -
    -
    -
    -

    BUMBLEBEE: BROWSER AUTOMATION

    -
    02 APRIL 2025
    -
    -

    Bumblebee is a tool I built for one of my employers to automate the generation -of web scraping scripts.

    - - - -

    In 2024, we were tasked with collecting market data using various methods, -including scraping data from authorized websites for traders’ use.

    - -

    Manual authoring of such scripts took time. The scripts were often brittle due -to the complexity of the modern web, and they lacked optimizations such as -bypassing the UI and retrieving the data files directly when possible, which -would have significantly reduced our compute costs.

    - -

    To alleviate these challenges, I, with the help of a colleague, Andy Zhang, -built Bumblebee: a web browser powered by C# Windows Forms, Microsoft Edge WebView2, and -the Scintilla.NET text editor.

    - -

    Bumblebee works by injecting a custom JavaScript program that intercepts -client-side events and sends them to Bumblebee for analysis. In addition to -front-end events, Bumblebee also captures internal browser events, which it -then interprets to generate code in real time. Note that we developed Bumblebee -before the advent of now-popular LLMs. Bumblebee supports dynamic websites, -pop-ups, developer tools, live manual override, event debouncing, and filtering -hidden elements and scripts.

    - -

    Before settling on a desktop application, we contemplated designing Bumblebee -as a browser extension. We chose the desktop app because extensions don’t offer -the deep, event-based control we needed. Besides, the company’s security -policy, which prohibited browser extensions, would have complicated the -deployment of an extension-based solution. My first prototype used a C# binding -of the Chromium project. WebView’s more intuitive API and its seamless -integration with Windows Forms led us to choose it over the Chromium wrapper.

    - -

    What began as a personal side project to improve my own workflow enabled us to -collectively improve the quality of our web scripts at a much larger scale. -Bumblebee predictably reduced the time we spent on authoring scripts from hours -to a few minutes.

    - -
    - -
    -
    -
    - - - - - - diff --git a/_site/poc/bumblebee/poster.png b/_site/poc/bumblebee/poster.png deleted file mode 100644 index 6dc955e..0000000 Binary files a/_site/poc/bumblebee/poster.png and /dev/null differ diff --git a/_site/poc/bumblebee/thumb_sm.png b/_site/poc/bumblebee/thumb_sm.png deleted file mode 100644 index f7cfbf3..0000000 Binary files a/_site/poc/bumblebee/thumb_sm.png and /dev/null differ diff --git a/_site/poc/e-reader/circuit.svg b/_site/poc/e-reader/circuit.svg deleted file mode 100644 index fd7508b..0000000 --- a/_site/poc/e-reader/circuit.svg +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - - - - - - 10 kΩ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - 2 - 3 - 4 - - - - - - - - - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - - - - - 5 - 6 - 7 - 8 - BUS - E-Paper Display HAT - - - CS - - DC - - DIN - - CLK - - BUSY - - RST - - GND - - VCC - - ESP-WROOM-32 - - IO21 - - - IO5 - - - IO16 - - - IO23 - - - IO18 - - - IO22 - - - IO4 - - - IO2 - - - GND - - - 3V3 - - - GND - - - IO15 - - - - \ No newline at end of file diff --git a/_site/poc/e-reader/ereader.mp4 b/_site/poc/e-reader/ereader.mp4 deleted file mode 100644 index 89e05eb..0000000 Binary files a/_site/poc/e-reader/ereader.mp4 and /dev/null differ diff --git a/_site/poc/e-reader/index.html b/_site/poc/e-reader/index.html deleted file mode 100644 index 6f52897..0000000 --- a/_site/poc/e-reader/index.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - Experimental e-reader - - - - - Experimental e-reader - - - - - - - - - - - - - -
    -
    -
    -

    EXPERIMENTAL E-READER

    -
    24 OCTOBER 2023
    -
    -

    This project features an experimental e-reader powered by an ESP-WROOM-32 -development board and a 7.5-inch Waveshare -e-paper display built with the intention of learning about e-paper displays.

    - - - -

    Introduction

    - -

    The prototype e-reader comprises an ESP32 microcontroller, an e-paper display -HAT, and three buttons: yellow, blue, and white for turning the page backwards, -forwards, and putting the device to sleep, respectively. The prototype does not -store books on the microcontroller. It streams books from a server over HTTP. -The e-reader employs RTC memory to record the reading progress between -sessions.

    - -

    The most formidable challenge when trying to build an e-reader with an ESP32 is -its limited memory and storage. My ESP-WROOM-32 has a total of 512KB of SRAM -and 4MB of flash memory, which the freeRTOS, ESP-IDF, and the e-reader -application must share. To put things into perspective, a Kindle Paperwhite has -at least 256MB of memory and 8GB of storage. That is 500x more memory than what -I’d have to work with.

    - -

    Despite its size, as microcontrollers go, ESP32 is a powerful system-on-a-chip -with a 160MHz dual-core processor and integrated WiFi. So, I thought it’d be -amusing to embrace the constraints and build my e-reader using 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, one for each page of the book. The -dimensions of each bitmap are equal to the size of the display. Each byte of -the bitmap encodes information for rendering eight pixels. For my display, -which has a resolution of 480x800, the bitmaps are laid out along 48KB -boundaries. This simple file format lends well to HTTP streaming, which is its -main advantage, as we will soon see.

    - -

    The pdftoebm.py script enclosed in the tarball at the end of the page converts -PDF documents to EBM files.

    - -

    How does it work?

    - -

    As the e-reader has no storage, it can’t store books locally. Instead, it -downloads pages of the EBM file over HTTP from the location pointed to by the -EBM_ARCH_URL setting in the Kconfig.projbuild file on demand. To read a -different book, we have to replace the old file with the new one or change the -EBM_ARCH_URL value. The latter requires us 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 by pressing a -button, one of the microprocessor’s two cores transfers it from the buffer to -the display over a Serial Peripheral Interface (SPI). The other downloads a new -page in the background. I used the ESP-IDF task API to schedule the two tasks -on different cores of the multicore processor to make the reader more -responsive.

    - -

    I designed the EBM format with HTTP streaming in mind. Since the pages are laid -out in the EBM file along predictable boundaries, the e-reader can request -pages by specifying the offset and the chunk size in the HTTP Range header. Any -web server will process this request without custom logic.

    - -

    Epilogue

    - -

    My fascination with e-paper began back in 2017, when I was tasked with -installing a few displays in a car park. Having no idea how they worked, I -remember watching the languid screens refresh like a Muggle witnessing magic. -This project was born out of that enduring curiosity and love of e-paper -technology.

    - -

    Why did I go to the trouble of building a rudimentary e-reader when I could -easily buy a more capable commercial e-reader? First of all, it’s to prove to -myself that I can. More importantly, there’s a quiet satisfaction to reading on -hardware you built yourself. You are no longer the powerless observer watching -the magic happen from the sidelines. You become the wizard who makes the -invisible particles swirl into form by whispering C to them. There’s only one -way to experience that.

    - -

    Files: source.tar.gz

    -
    - -
    -
    -
    - - - - - - diff --git a/_site/poc/e-reader/poster.png b/_site/poc/e-reader/poster.png deleted file mode 100644 index 1e222d2..0000000 Binary files a/_site/poc/e-reader/poster.png and /dev/null differ diff --git a/_site/poc/e-reader/source.tar.gz b/_site/poc/e-reader/source.tar.gz deleted file mode 100644 index 3e343a7..0000000 Binary files a/_site/poc/e-reader/source.tar.gz and /dev/null differ diff --git a/_site/poc/e-reader/thumb_sm.png b/_site/poc/e-reader/thumb_sm.png deleted file mode 100644 index 7c971e8..0000000 Binary files a/_site/poc/e-reader/thumb_sm.png and /dev/null differ diff --git a/_site/poc/etlas/circuit.svg b/_site/poc/etlas/circuit.svg deleted file mode 100644 index 6255045..0000000 --- a/_site/poc/etlas/circuit.svg +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - AM2302 - - - DATA - - GND - - VCC - E-Paper display HAT - - - CS - - DC - - DIN - - CLK - - BUSY - - RST - - PWR - - GND - - VCC - - - - - - - - - - - - - - 1 - 2 - 3 - 4 - - - - - 1 - 2 - 3 - 4 - - ESP32 Mini NodeMCU D1 - - IO19 - - - IO15 - - - GND - - - IO27 - - - IO14 - - - IO13 - - - IO25 - - - IO26 - - - IO16 - - - GND - - - 3V3 - - - \ No newline at end of file diff --git a/_site/poc/etlas/dash.jpg b/_site/poc/etlas/dash.jpg deleted file mode 100644 index cf4efc6..0000000 Binary files a/_site/poc/etlas/dash.jpg and /dev/null differ diff --git a/_site/poc/etlas/etlas_arch.png b/_site/poc/etlas/etlas_arch.png deleted file mode 100644 index 241e9f1..0000000 Binary files a/_site/poc/etlas/etlas_arch.png and /dev/null differ diff --git a/_site/poc/etlas/index.html b/_site/poc/etlas/index.html deleted file mode 100644 index d2b60bd..0000000 --- a/_site/poc/etlas/index.html +++ /dev/null @@ -1,166 +0,0 @@ - - - - - Etlas: e-paper dashboard - - - - - Etlas: e-paper dashboard - - - - - - - - - - - - - -
    -
    -
    -

    ETLAS: E-PAPER DASHBOARD

    -
    05 SEPTEMBER 2024
    -
    -

    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.

    - - - - - - -
    frontback
    - -

    The top-left panel shows two weeks of end-of-day prices—the maximum the ESP32’s -SRAM can hold—from the Polygon.io API. The price feed is relayed through a -FastCGI-wrapped Flask app hosted on a VPS. This lets me configure stock symbols -in its application settings. The app cycles through them as requests come in -from the ESP32. Running the Flask app as a FastCGI process while exposing it -via httpd with htpasswd authentication keeps the server code simple and secure.

    - -

    The following diagram outlines the Etlas’s overall system architecture.

    - -

    architecture

    - -

    The more prominent panel on the right of the display shows local and world news -from Channel NewsAsia. The MCU downloads and parses XML data from the RSS feed -directly before rendering it to the display. The character glyphs used are -stored as bitmaps in the sprites directory. I skipped the proxy for news to -avoid writing more server code, but in hindsight it limits the feeds Etlas can -handle. I will fix this in a future version.

    - -

    The middle and bottom right panels display the temperature and relative -humidity from the DHT22 sensor. The DHT22 uses pulse-width modulation to -transmit data to the host. The 26µs, 50µs, and 70µs pulses are too fast for the -ESP32 to measure reliably with standard APIs. Instead, the driver compares -relative pulse widths to differentiate zeros from ones:

    - -
    static inline int dht_await_pin_state(int state, int timeout)
    -{
    -    int t;
    -    static const uint16_t delta = 1;
    -
    -    for (t = 0; t < timeout; t += delta) {
    -        ets_delay_us(delta);
    -        if (gpio_get_level(DHT_PIN) == state)
    -          return t;
    -    }
    -    return 0;
    -}
    -
    -static inline int dht_get_raw_data(unsigned char buf[DHT_DATA_LEN])
    -{
    -    int rc;
    -    unsigned char i, pwl, pwh;
    -
    -    gpio_set_level(DHT_PIN, 0);
    -    ets_delay_us(1100);
    -    gpio_set_level(DHT_PIN, 1);
    -
    -    if (!dht_await_pin_state(0, 40)) {
    -        rc = 1;
    -        xQueueSend(dht_evt_queue, &rc, (TickType_t) 0);
    -        return 0;
    -    }
    -    if (!dht_await_pin_state(1, 80)) {
    -        rc = 2;
    -        xQueueSend(dht_evt_queue, &rc, (TickType_t) 0);
    -        return 0;
    -    }
    -    if (!dht_await_pin_state(0, 80)) {
    -        rc = 3;
    -        xQueueSend(dht_evt_queue, &rc, (TickType_t) 0);
    -        return 0;
    -    }
    -
    -    for (i = 0; i < DHT_DATA_LEN; i++) {
    -        if (!(pwl = dht_await_pin_state(1, 50))) {
    -            rc = 4;
    -            xQueueSend(dht_evt_queue, &rc, (TickType_t) 0);
    -            return 0;
    -        }
    -        if (!(pwh = dht_await_pin_state(0, 70))) {
    -            rc = 5;
    -            xQueueSend(dht_evt_queue, &rc, (TickType_t) 0);
    -            return 0;
    -        }
    -        buf[i] = pwh > pwl;
    -    }
    -    return 1;
    -}
    -
    - -

    I ported this implementation from ESP8266 -to ESP32—all credit for the algorithm belongs to them.

    - -

    Etlas is a networked embedded system. All acquisition, processing, and -rendering of data are performed on the ESP32’s 160MHz microprocessor using less -than 512KB of SRAM. The embedded software that makes this possible is written -in C using ESP-IDF v5.2.1. The e-paper display driver is derived from Waveshare -examples for Arduino and STM32 -platforms.

    - -

    Etlas has been running reliably for over a year since August 2024.

    - -

    Files: source.tar.gz

    -
    - -
    -
    -
    - - - - - - diff --git a/_site/poc/etlas/pcb.jpg b/_site/poc/etlas/pcb.jpg deleted file mode 100644 index fcb40fa..0000000 Binary files a/_site/poc/etlas/pcb.jpg and /dev/null differ diff --git a/_site/poc/etlas/schematic.svg b/_site/poc/etlas/schematic.svg deleted file mode 100644 index 3070dd1..0000000 --- a/_site/poc/etlas/schematic.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -152726131425193V3GND
    ESP32 Mini NodeMCU D1
    ESP32 Mini NodeMCU D1
    DHT22
    DHT22
    E-paper HAT
    E-paper HAT
    3.3V
    3.3V
    3.3V
    3.3V
    3.3V
    3.3V
    CS
    CS
    DC
    DC
    RST
    RST
    CLK
    CLK
    MOSY
    MOSY
    BUSY
    BUSY
    VCC
    VCC
    GND
    GND
    VCC
    VCC
    GND
    GND
    DATA
    DATA
    Text is not SVG - cannot display
    \ No newline at end of file diff --git a/_site/poc/etlas/source.tar.gz b/_site/poc/etlas/source.tar.gz deleted file mode 100644 index 8b12cf6..0000000 Binary files a/_site/poc/etlas/source.tar.gz and /dev/null differ diff --git a/_site/poc/etlas/thumb_sm.jpg b/_site/poc/etlas/thumb_sm.jpg deleted file mode 100644 index a374879..0000000 Binary files a/_site/poc/etlas/thumb_sm.jpg and /dev/null differ diff --git a/_site/poc/fpm-door-lock/breadboard.jpg b/_site/poc/fpm-door-lock/breadboard.jpg deleted file mode 100644 index 2bf47a9..0000000 Binary files a/_site/poc/fpm-door-lock/breadboard.jpg and /dev/null differ diff --git a/_site/poc/fpm-door-lock/footprint.png b/_site/poc/fpm-door-lock/footprint.png deleted file mode 100644 index 5511bf1..0000000 Binary files a/_site/poc/fpm-door-lock/footprint.png and /dev/null differ diff --git a/_site/poc/fpm-door-lock/gerber.zip b/_site/poc/fpm-door-lock/gerber.zip deleted file mode 100644 index 19a9d19..0000000 Binary files a/_site/poc/fpm-door-lock/gerber.zip and /dev/null differ diff --git a/_site/poc/fpm-door-lock/index.html b/_site/poc/fpm-door-lock/index.html deleted file mode 100644 index 9fbf003..0000000 --- a/_site/poc/fpm-door-lock/index.html +++ /dev/null @@ -1,168 +0,0 @@ - - - - - Fingerprint door lock - - - - - Fingerprint door lock - - - - - - - - - - - - - -
    -
    -
    -

    FINGERPRINT DOOR LOCK

    -
    03 OCTOBER 2025
    -
    -

    This project features a fingerprint door lock powered by an ATmega328P -microcontroller.

    - - - -

    Overview

    - -

    The lock comprises three subsystems: the ATmega328P microcontroller, 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.8mA and 4.6mA 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, 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 microcontroller’s sleep modes, 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 PCB

    - -

    For this project, I designed a custom PCB and had it fabricated by JLCPCB. Like -the software, the circuit is primarily concerned with optimizing power -consumption and extending battery life.

    - - - - - - - - - -
    - PCB - - Design -
    - PCB footprint -
    - -

    Consequently, 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 5V with a 16MHz crystal oscillator. To -further reduce power consumption, I modified the ATmega328P’s fuses to run at -3.3V with an 8MHz 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 1A. The IN4007 diode in slot U2 -serves as a flyback diode, protecting the MOSFET from reverse currents -generated by the servo.

    - -

    Lastly, the 56kΩ and 10kΩ 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 -433MHz RF transceivers. 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 and 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, gerber.zip

    -
    - -
    -
    -
    - - - - - - diff --git a/_site/poc/fpm-door-lock/pcb.jpg b/_site/poc/fpm-door-lock/pcb.jpg deleted file mode 100644 index fbd800b..0000000 Binary files a/_site/poc/fpm-door-lock/pcb.jpg and /dev/null differ diff --git a/_site/poc/fpm-door-lock/pcb1.jpg b/_site/poc/fpm-door-lock/pcb1.jpg deleted file mode 100644 index 367187d..0000000 Binary files a/_site/poc/fpm-door-lock/pcb1.jpg and /dev/null differ diff --git a/_site/poc/fpm-door-lock/source.tar.gz b/_site/poc/fpm-door-lock/source.tar.gz deleted file mode 100644 index ef23422..0000000 Binary files a/_site/poc/fpm-door-lock/source.tar.gz and /dev/null differ diff --git a/_site/poc/fpm-door-lock/thumb_sm.jpg b/_site/poc/fpm-door-lock/thumb_sm.jpg deleted file mode 100644 index a8fa534..0000000 Binary files a/_site/poc/fpm-door-lock/thumb_sm.jpg and /dev/null differ diff --git a/_site/poc/fpm-door-lock/video.mp4 b/_site/poc/fpm-door-lock/video.mp4 deleted file mode 100644 index a907a9b..0000000 Binary files a/_site/poc/fpm-door-lock/video.mp4 and /dev/null differ diff --git a/_site/poc/index.html b/_site/poc/index.html deleted file mode 100644 index 62b5b75..0000000 --- a/_site/poc/index.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - Projects - - - - - - - - - - - - -
    -
    - - - - - - diff --git a/_site/poc/matrix-digital-rain/index.html b/_site/poc/matrix-digital-rain/index.html deleted file mode 100644 index dfcc274..0000000 --- a/_site/poc/matrix-digital-rain/index.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - The Matrix digital rain - - - - - The Matrix digital rain - - - - - - - - - - - - - -
    -
    -
    -

    THE MATRIX DIGITAL RAIN

    -
    12 JANUARY 2024
    -
    -

    “All I see is blonde, brunette, red head.” The iconic digital rain from The -Matrix in C, with zero dependencies—not even ncurses.

    - - - -

    Overview

    - -

    This is my fork of Domsson’s beautiful Fakesteak. While going through his code, I -wondered what it would take to faithfully recreate the original Matrix from the -first movie without sacrificing its minimalism.

    - -

    My implementation supports:

    - -
      -
    • Unicode characters.
    • -
    • 24-bit RGB colors (truecolor).
    • -
    • Glitches in the matrix.
    • -
    • Ghosting effect of old monochrome CRT displays.
    • -
    • Closely resembles the Matrix seen in the background during Neo and Cypher’s -conversation.
    • -
    - -

    With no dependencies, compilation is trivial:

    - -
    $ cc -O3 main.c -o matrix
    -$ ./matrix
    -
    - -

    How does it work?

    - -

    The program tracks the state of the terminal, such as code points, background -and foreground colors, and cursor position, using multiple internal data -buffers. On each frame, it updates these buffers and repaints the screen using -ANSI escape codes:

    - -
    static void term_print(const matrix *mat, size_t row, size_t col)
    -{
    -    size_t idx;
    -    idx = mat_idx(mat, row, col);
    -    wprintf(L"\x1b[%d;%dH\x1b[38;2;%d;%d;%dm%lc",
    -        row, col,
    -        mat->rgb[idx].color[R],
    -        mat->rgb[idx].color[G],
    -        mat->rgb[idx].color[B],
    -        mat->code[idx]);
    -}
    -
    - -

    The ghosting effect is achieved by carefully scaling the RGB -channels before mixing them:

    - -
    static void mat_shade(matrix *mat, size_t row, size_t col) 
    -{
    -    unsigned char *color;
    -    color = mat->rgb[mat_idx(mat, row, col)].color;
    -    color[R] = color[R] - (color[R] - COLOR_BG_RED) / 2;
    -    color[G] = color[G] - (color[G] - COLOR_BG_GRN) / 2;
    -    color[B] = color[B] - (color[B] - COLOR_BG_BLU) / 2;
    -}
    -
    - -

    The ghosting function emulates the dim after glow by gradually transitioning -each raindrop’s color towards the background color. This approach provides two -key benefits: straightforward color configuration that integrates naturally -with (Unix) ricing and high-fidelity recreation of the Matrix aesthetic.

    - -

    Customization

    - -

    While you can alter almost every aspect, including speed, glitch frequency, and -rain density, the most common customizations are the color scheme and character -set.

    - -

    There are three color settings: head, tail, and background. You can configure -them using COLOR_*_RED, COLOR_*_GRN, and COLOR_*_BLU definitions found in -main.c.

    - -

    The UNICODE_MIN and UNICODE_MAX values control the Unicode block used. For -example, setting them to 0x30A1 and 0x30F6 rains Katakana, if a font that -supports Katakana is present on the system:

    - -

    - -

    Files: source.tar.gz

    -
    - -
    -
    -
    - - - - - - diff --git a/_site/poc/matrix-digital-rain/katakana.png b/_site/poc/matrix-digital-rain/katakana.png deleted file mode 100644 index b9df873..0000000 Binary files a/_site/poc/matrix-digital-rain/katakana.png and /dev/null differ diff --git a/_site/poc/matrix-digital-rain/matrix.mp4 b/_site/poc/matrix-digital-rain/matrix.mp4 deleted file mode 100644 index 84a9839..0000000 Binary files a/_site/poc/matrix-digital-rain/matrix.mp4 and /dev/null differ diff --git a/_site/poc/matrix-digital-rain/poster.png b/_site/poc/matrix-digital-rain/poster.png deleted file mode 100644 index 0321ad3..0000000 Binary files a/_site/poc/matrix-digital-rain/poster.png and /dev/null differ diff --git a/_site/poc/matrix-digital-rain/source.tar.gz b/_site/poc/matrix-digital-rain/source.tar.gz deleted file mode 100644 index fead280..0000000 Binary files a/_site/poc/matrix-digital-rain/source.tar.gz and /dev/null differ diff --git a/_site/poc/matrix-digital-rain/thumb_sm.png b/_site/poc/matrix-digital-rain/thumb_sm.png deleted file mode 100644 index d3f06c9..0000000 Binary files a/_site/poc/matrix-digital-rain/thumb_sm.png and /dev/null differ diff --git a/_site/poc/my-first-pcb/back.jpeg b/_site/poc/my-first-pcb/back.jpeg deleted file mode 100644 index f458e69..0000000 Binary files a/_site/poc/my-first-pcb/back.jpeg and /dev/null differ diff --git a/_site/poc/my-first-pcb/back_design.jpeg b/_site/poc/my-first-pcb/back_design.jpeg deleted file mode 100644 index b6c0f5d..0000000 Binary files a/_site/poc/my-first-pcb/back_design.jpeg and /dev/null differ diff --git a/_site/poc/my-first-pcb/front.jpeg b/_site/poc/my-first-pcb/front.jpeg deleted file mode 100644 index 2b2931f..0000000 Binary files a/_site/poc/my-first-pcb/front.jpeg and /dev/null differ diff --git a/_site/poc/my-first-pcb/front_design.jpeg b/_site/poc/my-first-pcb/front_design.jpeg deleted file mode 100644 index f81f09c..0000000 Binary files a/_site/poc/my-first-pcb/front_design.jpeg and /dev/null differ diff --git a/_site/poc/my-first-pcb/gerber_back.zip b/_site/poc/my-first-pcb/gerber_back.zip deleted file mode 100644 index 26659ad..0000000 Binary files a/_site/poc/my-first-pcb/gerber_back.zip and /dev/null differ diff --git a/_site/poc/my-first-pcb/gerber_front.zip b/_site/poc/my-first-pcb/gerber_front.zip deleted file mode 100644 index 864334e..0000000 Binary files a/_site/poc/my-first-pcb/gerber_front.zip and /dev/null differ diff --git a/_site/poc/my-first-pcb/index.html b/_site/poc/my-first-pcb/index.html deleted file mode 100644 index 55dc58d..0000000 --- a/_site/poc/my-first-pcb/index.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - My first PCB - - - - - My first PCB - - - - - - - - - - - - - -
    -
    -
    -

    MY FIRST PCB

    -
    14 JULY 2025
    -
    -

    In 2023, I started tinkering with DIY electronics as a hobby. Until now, I’ve -been using development boards like the Arduino Uno and ESP-32-WROOM so that I -can focus on the software. Recently, I decided to step outside of my comfort -zone and design a PCB from scratch for a door lock I’m working on.

    - -

    The lock comprises two subsystems: a fingerprint sensor in front of the door -and a servo connected to the physical lock behind the door. The fingerprint -sensor authenticates the person and signals the servo behind the door to unlock -the door over an encrypted RF channel.

    - - - - - - - - - - -
    - Design (front) -

    Footprint (front)

    -
    - PCB (front) -

    PCB (front)

    -
    - Design (back) -

    Footprint (back)

    -
    - PCB (back) -

    PCB (back)

    -
    - -

    The PCBs have two layers. A copper region serves as the ground plane. The 0.3mm -wide 1oz/ft2 copper traces can carry up to 500mA (the tracks -connecting the power source and the linear regulators have a width of 0.5mm). -Both subsystems were functional. I was able to control the servo reliably using -the fingerprint sensor.

    - -

    The designs aren’t without flaws, however. The main shortcoming of the circuits -is that they draw significant amounts of quiescent currents despite employing -sleep modes. The linear regulators were a poor choice as they dissipate too -much heat. The fingerprint sensor and the servo draw 13.8mA (3.3V) and 4.6mA -(5V) respectively, as long as they are connected to the power supply.

    - -

    Although the circuit didn’t draw more than 200mA without a load, the servo -under load could draw up to 600mA. I’m sailing too close to the wind with 0.3mm -copper traces. Instead, 0.4mm wide 2oz/ft2 traces would have been -safer.

    - -

    I’m working on improving the design to reduce idle current consumption and -extend the battery life. Despite its deficiencies, this was my first PCB -design, and I’m glad that it worked as well as it did. Custom PCB design marks -an important milestone in my DIY electronics journey.

    - -

    Files: gerber_back.zip, gerber_front.zip, - source.tar.gz

    -
    - -
    -
    -
    - - - - - - diff --git a/_site/poc/my-first-pcb/source.tar.gz b/_site/poc/my-first-pcb/source.tar.gz deleted file mode 100644 index c31aa22..0000000 Binary files a/_site/poc/my-first-pcb/source.tar.gz and /dev/null differ diff --git a/_site/poc/my-first-pcb/thumb_sm.jpeg b/_site/poc/my-first-pcb/thumb_sm.jpeg deleted file mode 100644 index c275b12..0000000 Binary files a/_site/poc/my-first-pcb/thumb_sm.jpeg and /dev/null differ diff --git a/_site/posts.xml b/_site/posts.xml index 5a31ca1..7b61271 100644 --- a/_site/posts.xml +++ b/_site/posts.xml @@ -1 +1 @@ -Jekyll2025-12-20T11:31:17+08:00/posts.xmlASCIIMXW. D. Sadeep Madurange \ No newline at end of file +Jekyll2025-12-20T21:35:40+08:00http://localhost:4000/posts.xmlASCIIMXW. D. Sadeep Madurange \ No newline at end of file diff --git a/_site/projects/index.html b/_site/projects/index.html new file mode 100644 index 0000000..6cd47f3 --- /dev/null +++ b/_site/projects/index.html @@ -0,0 +1,123 @@ + + + + + + + Projects + + + + + + + + + + + + +
    +

    Projects

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + Fingerprint door lock +
    Fingerprint door lock
    +
    +
    + + + Bumblebee: browser automation +
    Bumblebee: browser automation
    +
    +
    + + + Etlas: e-paper dashboard +
    Etlas: e-paper dashboard
    +
    +
    + + + Experimental e-reader +
    Experimental e-reader
    +
    +
    + + + The Matrix digital rain +
    The Matrix digital rain
    +
    +
    +
    +
    + + + + + + diff --git a/_site/robots.txt b/_site/robots.txt index e087884..d297064 100644 --- a/_site/robots.txt +++ b/_site/robots.txt @@ -1 +1 @@ -Sitemap: /sitemap.xml +Sitemap: http://localhost:4000/sitemap.xml diff --git a/_site/sitemap.xml b/_site/sitemap.xml index 94ba9d9..442c589 100644 --- a/_site/sitemap.xml +++ b/_site/sitemap.xml @@ -1,59 +1,55 @@ -/log/arduino-due/ -2024-10-05T00:00:00+08:00 +http://localhost:4000/log/matrix-digital-rain/ +2022-08-22T00:00:00+08:00 -/log/arduino-uno/ -2025-04-10T00:00:00+08:00 - - -/log/mosfet-switches/ -2025-06-22T00:00:00+08:00 +http://localhost:4000/log/e-reader/ +2023-10-24T00:00:00+08:00 -/log/neo4j-a-star-search/ -2025-09-14T00:00:00+08:00 +http://localhost:4000/log/etlas/ +2024-09-05T00:00:00+08:00 -/log/suckless-software/ -2025-11-30T00:00:00+08:00 +http://localhost:4000/log/arduino-due/ +2024-09-16T00:00:00+08:00 -/poc/e-reader/ -2023-10-24T00:00:00+08:00 +http://localhost:4000/log/bumblebee/ +2025-04-02T00:00:00+08:00 -/poc/matrix-digital-rain/ -2024-01-12T00:00:00+08:00 +http://localhost:4000/log/my-first-pcb/ +2025-04-26T00:00:00+08:00 -/poc/etlas/ -2024-09-05T00:00:00+08:00 +http://localhost:4000/log/arduino-uno/ +2025-06-10T00:00:00+08:00 -/poc/bumblebee/ -2025-04-02T00:00:00+08:00 +http://localhost:4000/log/mosfet-switches/ +2025-06-22T00:00:00+08:00 -/poc/my-first-pcb/ -2025-07-14T00:00:00+08:00 +http://localhost:4000/log/fpm-door-lock/ +2025-08-18T00:00:00+08:00 -/poc/fpm-door-lock/ -2025-10-03T00:00:00+08:00 +http://localhost:4000/log/suckless-software/ +2025-11-30T00:00:00+08:00 -/about/ +http://localhost:4000/about/ -/ +http://localhost:4000/ -/log/ +http://localhost:4000/log/ -/poc/ +http://localhost:4000/projects/ diff --git a/poc.html b/poc.html deleted file mode 100644 index e2e773d..0000000 --- a/poc.html +++ /dev/null @@ -1,26 +0,0 @@ ---- -layout: default -title: Projects ---- - -
    -

    {{ page.title }}

    - - {% assign n = 2 %} - {% assign rows = site.poc.size | divided_by: n | ceil %} - {% for i in (1..rows) %} - - {% assign offset = forloop.index0 | times: n %} - {% for item in site.poc limit:n offset:offset %} - - {% endfor %} - - {% endfor %} -
    - {% assign parts = item.url | split: '/' %} - - {{ item.title }} -
    {{ item.title }}
    -
    -
    -
    diff --git a/projects.html b/projects.html new file mode 100644 index 0000000..5ebc056 --- /dev/null +++ b/projects.html @@ -0,0 +1,42 @@ +--- +layout: default +title: Projects +--- + +
    +

    {{ page.title }}

    + + {% assign n = 2 %} + {% assign projects = site.log | where: "project", true | sort: 'date' | reverse %} + {% assign rows = projects.size | divided_by: n %} + + {% assign remainder = projects.size | modulo: n %} + {% if remainder != 0 %} + {% assign rows = rows | plus: 1 %} + {% endif %} + + {% for i in (1..rows) %} + + {% assign offset = forloop.index0 | times: n %} + {% for item in projects limit:n offset:offset %} + + {% endfor %} + + {%- comment -%}Fill empty cells if this is the last row and has odd number of items{%- endcomment -%} + {% if forloop.last %} + {% assign items_in_last_row = projects.size | minus: offset %} + {% assign empty_cells = n | minus: items_in_last_row %} + {% for j in (1..empty_cells) %} + + {% endfor %} + {% endif %} + + {% endfor %} +
    + {% assign parts = item.url | split: '/' %} + + {{ item.title }} +
    {{ item.title }}
    +
    +
    +
    -- cgit v1.2.3