From 669f3bf7b63a50d09b4004f405697eb6d227c40f Mon Sep 17 00:00:00 2001 From: uvok Date: Sun, 10 Aug 2025 18:31:06 +0200 Subject: Publish! --- _drafts/building-an-epaper-badge.md | 1217 ----------------------------------- 1 file changed, 1217 deletions(-) delete mode 100644 _drafts/building-an-epaper-badge.md (limited to '_drafts/building-an-epaper-badge.md') diff --git a/_drafts/building-an-epaper-badge.md b/_drafts/building-an-epaper-badge.md deleted file mode 100644 index 39f3203..0000000 --- a/_drafts/building-an-epaper-badge.md +++ /dev/null @@ -1,1217 +0,0 @@ ---- -layout: post -title: Building an ePaper badge -date: 2025-08-06 19:53 +0200 -lang: en -categories: tech ---- - -## Foreword - -Two weeks before [Awoostria]({% post_url 2025-07-30-awoostria-con-report %}): - -> Hey, I should build something for my Tinkering Projects Show And Tell panel! - -So it begins… The story how I built myself an ePaper badge. - -Actually, the story begins way earlier (unrelated, when I still had a physical -Raspberry Pi running stuff in my home network). I wanted to tinker around a bit -and bought myself a Waveshare ePaper. These are simple black-and-white displays -which maintain their content when the power switches off. They are also inside -eBook readers. - -This was also when I wanted to build myself an electronic door sign for the -EAST convention with these, and I wanted to go "as minimal as possible". I -wanted to use one of the MSP430 controllers I had laying around, and I wanted -to change motives via MiFare RFID transponders (using an MFRC5xx reader). Work -on that development never really took off. (Ugh, too little flash for all the -pictures, too much stuff to code myself!) - -## Requirements - -So this time, I simply said "fuck it", and threw an ESP32 on the problem. -Also, I decided to use [PlatformIO](https://platformio.org/), a -toolchain/SDK/library manager. I started with the Arduino framework, which is… -pretty wasteful in terms of resources (Flash, RAM, CPU etc.), but speeds up -development significantly. - -I had a simple ESP32 devboard, and one of the Waveshare modules, and started -coding. - -… - -But wait, what do I even want to achieve? Well, I wanted to "mood badge", i.e. -show my current mood with funny pictures. I couldn't get one on previous -conventions, so I was just gonna build one myself. - -This involves several sub-problems: - -* [Control the ePaper display](#control) -* [Get the pictures on the display](#pic) -* [Set what is displayed](#setdisp) -* [Power-saving](#powersave) -* [Attach the badge to myself](#attach) - - -## Control the display - -Usually, you never talk to the displays themselves, but to a display -controller. You talk to these via a digital interface, e.g. SPI. There are -different display controllers with different command sets. - -But why bother with implementing this myself? There are ready-made libraries. -For myself, I decided to use [GxEPD2](https://github.com/ZinggJM/GxEPD2). They -support *some* Waveshare displays. The problem with Waveshare displays is, they -don't disclose which display controller they use. So it's kind of an -trial-or-error procedure. Or rather, you can look at their example code, figure -out which commands they are using, and compare what commands GxEPD2 uses. -That's a bit cumbersome. But still better than writing everything myself. Also, -it supports graphics primitives! - - -## Get the picture on the display - -You can't just simply throw a JPEG onto the display. The display doesn't -understand that. It only understands pixel data. Also, the display can only -draw black and white pixels. I also have a display with yellow color support, -but that makes it even more complicated, actually. Even when you don't use it, -refresh is slow. - -So, you definitely can't throw a color picture on the display, nor a monochrome -one. There are displays which support a few gray-levels, but I don't have one -of those. - -So. What to? The solution is "dithering". I.e. you trick your eye into -perceiving grey by having clusters of black and white pixels. There is some -technical background to dithering (see the Reference section), but I simply -used either GIMP with the Floyd-something algorithm, or one of the "ordered" -modes of ImageMagick. It was a bit of trial-and-error and -seeing-what-looks-best. - -The result, then, looks like this: - -a dithered image of my fursona - -Now, about the image format… GxEPD2 supports "XBitmaps", or XBMs, which are -basically just a C array declaration, so you can GCC that file and throw in the -array into the GxEPD2 function call. And voilá, it works. You need to set the -rotation first, though. - - -## How to attach the badge to myself - -I have a Waveshare module/PiHat (which is too heavy), and a simple "ePaper -sheet" including a plastic housing for it. The housing can only fit the ePaper, -not the devboard, though. Also, it would be too cumbersome to attach to the -devboard - loose wires! So, at this point, I decided to switch from the -prototyping platform onto something better. - -Fortunately, Elecrow provides a -[CrowPanel](https://www.elecrow.com/wiki/CrowPanel_ESP32_E-paper_2.9-inch_HMI_Display.html), -which is exactly what I need. It has a display, a built-in ESP32 controller, a -housing, and even some switches! As a huge plus, they even specify which -display controller they use. I had to try some of the GxEPD2 display classes, -but finally found one working. - -I decided to glue magnets onto the housing, and attach the display via magnets -on the inner side of my shirt — not ideal. I positioned the magnets in -the (vertical) middle of the housing, so it wobbles and is not readable. Also, -I accidentally washed the shirt after Awoostria with the magnets still sticking -inside — and now there's a hole in it :( . This problem is still -unsolved. I can kinda attach the magnets to the housing screws at the top, but -that's not *very* stable. - -When starting to work with the Elecrow display, at first nothing would work. -When I looked at their example code I noticed there's an additional power pin -that needs to be toggled. - - -## Set what is displayed - -In addition to these switches, which allow choosing the motive, I wanted -something "more direct", so I added the -[NimBLE-Arduino](https://github.com/h2zero/NimBLE-Arduino) library. With a bit -of coding, I added a service and some characteristics, so the available motives -could be read via BLE. Also, the motive could be selected via another -characteristic. I started writing the characteristic with *nRF Connect For -Mobile*, but started writing an app [later](#theapp). - -Actually, the switch-selection was a bit troublesome. Redrawing the whole -display takes around 2 seconds — but that is not acceptable when -navigating the presets one-by-one. By looking at the API, I found out you can -select a "partial region". - -What I didn't mention yet, the text that shows the mood is drawn at runtime, -not integrated into the picture. So, I simply update a region in the -vertical-center-right of the display with the mood text. The picture stays the -same, but the text reflects the selected mood. The selection is confirmed my -pressing the rotary switch. - -This is still not ideal. The first update after power-up must be a full one, -and I don't save the selected preset in NVS — I don't want to destroy the -flash by lots of write cycles. I don't have a solution to this, yet. Maybe I'm -gonna integrate an microSD card (there's a slot for that in the CrowPanel). -I'm gonna research a good wear-levelling file system for that. Probably not -FAT. That doesn't need to be readable on the PC. (And even if, simple stuff -can be written in FUSE). Alternatively, drawing the first motive on power-up -would also be an option, but I don't like it that much. - - -## Power saving - -I first measured the current and was shocked. The whole thing draws around -80-100 mA. Not a big surprise, given that Arduino basically calls `loop()` over -and over again. - -Using a bit of experimenting, and failing to cancel light sleep with a GPIO -interrupt, I implemented power saving by using a timer-based light sleep (10 -ms, gives good user response) and reducing the CPU frequency to 80 MHz. And lo -and behold, my USB current measuring equipment (resolution 10mA) showed 0 mA. -Success. - -Actually, reducing the CPU frequency was a requirement! If I didn't do that, -the CPU would constantly crash when entering or exiting light sleep. No idea -why! - -Apropos of powering: The badge is normally unpowered and only powered if I need -to change the motive. I don't want to have an USB cable hanging on me the whole -time! Using a battery might be an option, but I had problems with mismatched -connectors — the Elecrow display doesn't have a standard JST connector -like the LiPo battery I bought (Li-Ion might even be the safer option? I have -no clue about this stuff. With a quick search, I only found these cylindrical -Li-Ion batteries and have no idea how I would connect them). The badge seems to -have a "mini" variant of that connector. - -For the time being, I power it with a USB power bank, but this I still consider -an unsolved problem. - - -## The app - -Writing the BLE characteristics with nRF Connect is all and well, but not -really user-friendly. I didn't want to install the Android SDK, so I looked at -cloud based development for a start. I found [MIT -AppInventor](https://appinventor.mit.edu/). First, I was disgusted, because -apparently they require Login with Google. But I found [an alternative -way](https://code2.appinventor.mit.edu/) by which you simply get a -"Passphrase-like" codeword you use for login. - -The graphical programming is unusual to me. I used Scratch shortly in the past, -so it was not completely foreign. Actually, it was kinda fun coding this, in -"event style", once I figured out how to to like stripping, list filtering, -etc. - -Screenshot of AppInventor showing part of the program. - -This was good enough for a while, but then I decided I wanted to actually have -the source code available. So I looked again at development options, and -settled for Flutter. - -Again, this was completely new to me. I started off with a popular BLE library, -which turned out to be an unfortunate choice, as Linux support had a few -quirks. That was probably a good thing in hindsight, as this made me abstract -away the BLE stuff in implementation classes, so I could easily try out -different libraries, and only use the abstract base classes in the code. Well, -you can see what the code looks like, I linked my repo below. This is what the -motive selection looks like: - -Screenshot of the Flutter app, allowing selection from
-  sleepy, hungry, hugs?, uvok, overstim, contact, games? - -## Resources - -- [surma.dev about dithering](https://surma.dev/things/ditherpunk/) -- [git repo with badge source code](https://git.uvok.de/espadge/) -- [git repo with app source code](https://git.uvok.de/espadge-flutter/) - - -- cgit v1.2.3