<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>E-Paper on Making Clean Tech</title><link>https://nunrg.eu/tags/e-paper/</link><description>Recent content in E-Paper on Making Clean Tech</description><generator>Hugo -- 0.136.5</generator><language>en-us</language><copyright>2024 Franci Kopač</copyright><lastBuildDate>Thu, 20 Mar 2025 12:58:48 +0100</lastBuildDate><atom:link href="https://nunrg.eu/tags/e-paper/index.xml" rel="self" type="application/rss+xml"/><item><title>Solar System Monitor Using Three Analog Dials On E-paper</title><link>https://nunrg.eu/posts/epaper-servo-gauge/</link><pubDate>Thu, 20 Mar 2025 12:58:48 +0100</pubDate><guid>https://nunrg.eu/posts/epaper-servo-gauge/</guid><description>A solar-system monitor that shows live data on three analog-style dials drawn on an e-paper display, driven by an ESP32.</description><content:encoded><![CDATA[<p>You can support me by <a href="https://medium.com/@francikopa/solar-system-monitor-using-three-analog-dials-on-e-paper-fc45434353f0">reading this article on Medium</a> or
you can buy me a beer <a href="https://paypal.me/FranciKopac?country.x=SI&amp;locale.x=en_US">here</a>.</p>
<h2 id="introduction">Introduction</h2>
<p>Last fall I came across an interesting article in <a href="https://hackaday.com/2024/11/25/e-ink-screen-combined-with-analog-dial-is-epic-win/">Hackaday</a>, where <a href="https://arnweb.nl/gitea/arne/EinkAnalog">Arne van Itersen</a> made an interesting solar power display with an analog gauge and an e-paper screen.</p>
<p>It immediately grabbed my attention because it solves the biggest problem with e-paper displays—they need a few seconds to refresh, making them unsuitable for displaying continuously changing data like solar power. A simple analog needle takes care of that problem but is limited to displaying one thing only. But if you put the needle in front of an e-paper screen, you get continuous power information from the needle. You can then use the e-paper display for displaying things that only change every few minutes, like storage battery level, daily production, and system operation mode.</p>
<p>But using an analog dial from a multimeter seemed a bit fiddly to me, and I thought about how to improve on that. Using a small servo instead would be better, because I could use needles of any size. Controlling the servos is quite simple too, I needed the ESP32 to control the e-paper display anyway. It took a weekend to put together a simple prototype for a one gauge display, which I used for my DIY EV-charger.</p>
<figure>
    <img src="/images/epaper/1semafor.jpg" alt="EV-charger display using a needle over an e-paper display">
    <figcaption>The first try, a 1-gauge display for my EV charger</figcaption>
  </figure>
  
<p>The thing worked like a charm and got me thinking about how to improve it. I was thinking about updating my solar system display for a while now, and this seemed like a good solution.</p>
<h2 id="thinking-about-the-design">Thinking About The Design</h2>
<p>One needle wasn&rsquo;t going to cut it this time—I wanted one for solar power, one for power coming from the grid, and one for the power going into the house. After thinking about it for a while, I decided to put the grid and house power servos on the left and right side of the display. The solar servo would be on the bottom. This provides some spatial logic too—power flows from the grid (left) or solar (bottom) to the house (right). I guess putting the solar need on top could also work, but that didn&rsquo;t look right to me for some reason.</p>
<p>I wanted to put the display outside, where it would be exposed to the elements, so it needed a weatherproof housing. So I had to think a bit on how to make a housing that would be both weatherproof and easy to assemble. It&rsquo;s easy to build a two part shell, but how to design it in a way you can assemble and then disassemble it easily while keeping it watertight? I decided to approach it differently. I designed a mounting plate for the servos and the e-paper display first and gave it a flange on the sides and on the bottom. Then I designed a one-piece housing with a window and a simple top cover. This housing had simple guides to hold the flanges of the mounting plate. The window can be closed using some acrylic glued with silicone to make it weatherproof. The top cover is designed to have rainproof downslopes and only needs a drop of superglue on each side to keep it in place. This makes the assembly and testing really simple, you can do everything with the mounting plate alone.</p>
<figure>
    <img src="/images/epaper/mountplate.jpg" alt="The mounting plate with installed servos, e-paper display, and connected ESP32">
    <figcaption>The assembled mounting plate, ready for testing, only the needles are missing</figcaption>
  </figure>
  
<h2 id="the-software">The Software</h2>
<p>I&rsquo;m used to Arduino, so this is what I used. E-paper in Arduino is IMO best used with the GxEPD2 library, which includes the Adafruit GFX functions, the servos are controlled using the Servo library, data exchange is done using Adafruit MQTT, and network connectivity uses WiFi.</p>
<p>The program is subscribed to several MQTT topics, containing the values for grid power, house power, solar power, solar production, battery level, and system mode. Each of these topics is linked to a callback function that triggers on topic updates. The callback functions for grid, house and solar power all move the respective servo needles. The callbacks for battery level and solar production only update the relevant global variables. Screen refresh is only directly triggered by a system mode topic change or every 10 minutes, but this updates the display with the latest data stored in the globals.</p>
<p>Startup makes a full sweep of all the needles, serving as a kind of servo calibration check, then starts the main loop. The main loop is really short and only keeps the MQTT connection alive and resets the whole thing if Wi-Fi drops.</p>
<h3 id="getting-the-data-to-the-topics">Getting the Data To the Topics</h3>
<p>The one remaining job on the software side was to get the data from my solar system to the MQTT topics. The solar system is connected to my Home Assistant installation, so it was really easy to set up some automations that publish the relevant data to MQTT topics when the relevant sensor states change. I used state changes for triggers and the MQTT Publish service to send the data to the MQTT topic. I already had a MQTT broker set up for my other projects, so I just used that one.</p>
<h2 id="build">Build</h2>
<p>Once I was satisfied with the results, it was plain sailing:</p>
<ul>
<li>3D-print the enclosure using ABS</li>
<li>Cut the acrylic to size and glue it to the enclosure using some silicone glue</li>
<li>Drill a hole for the power cable on the appropriate spot, then use some mounting glue to fix the enclosure to the wall</li>
<li>Connect the power and insert the mounting plate in the housing.</li>
</ul>
<figure>
    <img src="/images/epaper/3semafor.jpg" alt="The finished and closed enclosure">
    <figcaption>The end product on a sunny day while charging my EV. Note to self—it&#39;s really easy to scratch acrylic with screw heads 🤦‍♂️</figcaption>
  </figure>
  
<p>You can find all the relevant code and files on <a href="https://github.com/FrenkK/servo-epaper-gauge">Github</a></p>
<p>If you found this article useful, you can buy me a beer <a href="https://paypal.me/FranciKopac?country.x=SI&amp;locale.x=en_US">here</a>.</p>
]]></content:encoded></item><item><title>A Minimalist Digital Photo Frame Using 7-color E-paper</title><link>https://nunrg.eu/posts/epaper_7color/</link><pubDate>Wed, 13 Nov 2024 12:12:09 +0100</pubDate><guid>https://nunrg.eu/posts/epaper_7color/</guid><description>How I built a minimalist digital photo frame using a 7-colour e-paper display, a Raspberry Pi and Floyd-Steinberg dithering.</description><content:encoded><![CDATA[<p>You can support me by <a href="https://medium.com/@francikopa/a-minimalist-digital-photo-frame-using-7-color-e-paper-9dacde085464">reading this article on Medium</a></p>
<p><img loading="lazy" src="/images/epaper/7color.jpg" alt="Picture of the finished device"  />
</p>
<p>When researching the e-paper displays for the art project described <a href="https://nunrg.eu/posts/lowpowerepaper/">here</a>, I bought a <a href="https://www.waveshare.com/5.65inch-e-paper-module-f.htm">Waveshare ECP 600x488 7-color E-paper Display</a> (just a normal link, I&rsquo;m not making any money with this), but ended up never using it. I really try to avoid stuff piling up in my drawers, so I decided to use an old (vintage: 2011 😀) Raspberry Pi 1B I had lying around too and build a simple photo frame.</p>
<h2 id="using-raspberry-pi-to-control-the-e-paper-display">Using Raspberry Pi To Control the E-paper Display</h2>
<p>First, <a href="https://www.tomshardware.com/reviews/raspberry-pi-headless-setup-how-to,6028.html">set up a headless Pi</a> using Raspberry Pi OS Lite.</p>
<p>Next, you have to wire the display to the Pi header. The <a href="https://www.waveshare.com/wiki/5.65inch_e-Paper_Module_(F)_Manual#Working_With_Raspberry_Pi">Waveshare Wiki</a> instructions are pretty good and the setup should work out of the box for most people.</p>
<p>I had to make a few changes for my setup, because I wanted to use my old Raspberry Pi 1B to keep it from the e-waste bin. But it has different GPIO pins, so I ended up using the SPI pins on the Pi 1 and GPIO0, GPIO2, and GPIO3 for the DC, RST, and BUSY signals. This required a change in the <code>/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epdconfig.py</code> file. Under <code># Pin definition</code> I changed the defs this way:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span> <span style="color:#75715e"># Pin definition</span>
</span></span><span style="display:flex;"><span>    RST_PIN  <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;WPI2&#34;</span>
</span></span><span style="display:flex;"><span>    DC_PIN   <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;WPI0&#34;</span>
</span></span><span style="display:flex;"><span>    CS_PIN   <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;WPI10&#34;</span>
</span></span><span style="display:flex;"><span>    BUSY_PIN <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;WPI3&#34;</span>
</span></span><span style="display:flex;"><span>    PWR_PIN  <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;WPI1&#34;</span>
</span></span><span style="display:flex;"><span>    MOSI_PIN <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;WPI12&#34;</span>
</span></span><span style="display:flex;"><span>    SCLK_PIN <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;WPI14&#34;</span>
</span></span></code></pre></div><p>This enabled the Waveshare e-paper library to work with my old Pi. If you will be using a newer Pi (2 or higher, with the 40-pin header), you don&rsquo;t need to do this.</p>
<p>Now just run the <code>/RaspberryPi_JetsonNano/python/examples/epd_5in65f_test.py</code> file to see if everything works.</p>
<h3 id="preparing-the-image-files-for-display">Preparing the Image Files for Display</h3>
<p>The 7-color e-paper display works differently than the normal LCD displays. Instead of every pixel having an RGB value, each pixel can only display one of the seven available colors: black, white, green, blue, violet, yellow, and red. Then there is the fixed 600x488 pixel resolution. Finally, the Waveshare library is written to accept bitmap (*.bmp) files only.</p>
<h4 id="color-conversion">Color Conversion</h4>
<p>The 7 color limitation means we have to do a color conversion to be able to display the pictures on this display. Selecting simply the nearest color for each pixel will give pretty underwhelming results, so we need a better method, called Floyd-Steinberg dithering. <a href="https://scipython.com/blog/floyd-steinberg-dithering/">This is a very good explanation of it.</a></p>
<p>You could code this yourself, but there is a lovely program called <a href="https://www.imagemagick.org/">ImageMagick</a> that will take care of this for us. But first we need an file that contains only the colors we need. Luckily, the Waveshare library comes with example pictures and you only need this one: <code>RaspberryPi_JetsonNano/python/pic/5in65_n0.bmp</code>, just copy it to the folder you will be using.</p>
<h5 id="select-the-files">Select the Files</h5>
<p>This is a pretty obvious first step—collect all the pictures you want displayed on your photo frame. In my case, I did a keyword search in my Google Photo account and simply exported all the results to a temporary directory. Then I did a quick scroll through to delete some photos I did not want to include. Since I was dealing with about 7000 photos, &ldquo;quick&rdquo; was a relative term, but it only took a couple of hours.</p>
<h5 id="do-the-conversion">Do the Conversion</h5>
<p>First install ImageMagick (I used <code>winget install ImageMagick.Q16-HDRI</code> on my windows PC). Next, run the following command-line command (replace the folder name accordingly):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>magick *.jpg -resize 600x488 -dither FloydSteinberg -define dither:diffusion-amount<span style="color:#f92672">=</span>85% -remap <span style="color:#e6db74">&#34;folder/your/color/definition/file/is/in/5in65_n0.bmp&#34;</span> BMP3:images.bmp
</span></span></code></pre></div><p>This opens all the JPEG files in your folder, resizes them to max 600x488 pixels (leaving the unused space blank, so the aspect ratio stays correct), applies the Floyd-Steinberg dithering using the specified file, and, finally, saves them as bitmap files using the given filename, to which the sequence number of the file is appended.</p>
<p>Of course, there are many other ways of doing this, but this worked for me.</p>
<h5 id="doing-the-conversion-in-batches">Doing the Conversion in Batches</h5>
<p>ImageMagick is a very powerful program, but once you convert more than about 1000 pics at once, it will create a crazy amount of temporary files and the conversion itself will take a LONG time. So it&rsquo;s best to limit the conversion to batches of about 500-2000 files. This depends on your computer: 2000 pics took almost an hour on my rather new and quite powerful computer, producing over <strong>hundred GBs</strong> of temporary files in the process.</p>
<p>I think the best way is to copy all the image files to a folder called <code>todo</code> first. Then you copy the first batch of, say, 1000 files to your working folder. You then run the conversion, wait until it completes, and then copy everything but the resulting BMP files to the <code>done</code> folder. Then copy the next 1000 files, run the conversion again, but add &ldquo;_1&rdquo; to the result filename. Repeat, incrementing the suffix, until the <code>todo</code> folder is empty.</p>
<p>Congratulations, you now have a  large pile of bitmap files suitable for displaying on the 7 color e-paper display 😀 All you have to do now is copy it to a folder of your choice on the Raspberry Pi.</p>
<h3 id="the-python-code">The Python Code</h3>
<p>The code to display the files we just prepared is quite trivial, I simply copied the <code>/RaspberryPi_JetsonNano/python/examples/epd_5in65f_test.py</code> demo code and removed all the unnecessary parts. Then I added the code to be able to display bitmaps that don&rsquo;t fill the whole screen. Finally, I added random selection to keep the files from repeating too quickly. The code uses Pillow and you should take care to use proper file handling or you will have problems.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> sys
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> os
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> random
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> logging
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> time
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> PIL <span style="color:#f92672">import</span> Image
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> traceback
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Imports the Waveshare library</span>
</span></span><span style="display:flex;"><span>libdir <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>path<span style="color:#f92672">.</span>join(os<span style="color:#f92672">.</span>path<span style="color:#f92672">.</span>dirname(os<span style="color:#f92672">.</span>path<span style="color:#f92672">.</span>dirname(os<span style="color:#f92672">.</span>path<span style="color:#f92672">.</span>realpath(__file__))), <span style="color:#e6db74">&#39;lib&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> os<span style="color:#f92672">.</span>path<span style="color:#f92672">.</span>exists(libdir):
</span></span><span style="display:flex;"><span>    sys<span style="color:#f92672">.</span>path<span style="color:#f92672">.</span>append(libdir)
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> waveshare_epd <span style="color:#f92672">import</span> epd5in65f
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Let&#39;s keep the log short and only collect warnings. Use levels &#34;INFO&#34; or &#34;DEBUG&#34; for setup.</span>
</span></span><span style="display:flex;"><span>logging<span style="color:#f92672">.</span>basicConfig(level<span style="color:#f92672">=</span>logging<span style="color:#f92672">.</span>WARN)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>    logging<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;Random files&#34;</span>)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Initialize the display driver</span>
</span></span><span style="display:flex;"><span>    epd <span style="color:#f92672">=</span> epd5in65f<span style="color:#f92672">.</span>EPD()
</span></span><span style="display:flex;"><span>    logging<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;init&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#The forever loop</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span>:
</span></span><span style="display:flex;"><span>        logging<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;read bmp file&#34;</span>)
</span></span><span style="display:flex;"><span>        image_folder<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/the/folder/with/the/bitmaps/&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">#Randomly select the file from the folder</span>
</span></span><span style="display:flex;"><span>        random_file<span style="color:#f92672">=</span>random<span style="color:#f92672">.</span>choice(os<span style="color:#f92672">.</span>listdir(image_folder))
</span></span><span style="display:flex;"><span>        logging<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;Chosen file: &#34;</span> <span style="color:#f92672">+</span> image_folder <span style="color:#f92672">+</span> random_file)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">#This is important: if you don&#39;t use proper file handling using &#34;with&#34;,</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">#the file stays open and displaying the next file will fail</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">with</span> Image<span style="color:#f92672">.</span>open(image_folder <span style="color:#f92672">+</span> random_file)<span style="color:#f92672">.</span>convert(<span style="color:#e6db74">&#39;RGBA&#39;</span>) <span style="color:#66d9ef">as</span> bitmap:
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">#First set up a white background</span>
</span></span><span style="display:flex;"><span>            Himage <span style="color:#f92672">=</span> Image<span style="color:#f92672">.</span>new(<span style="color:#e6db74">&#39;RGB&#39;</span>, (epd<span style="color:#f92672">.</span>width, epd<span style="color:#f92672">.</span>height), <span style="color:#ae81ff">0xffffff</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">#Get image dimensions from the bitmap file</span>
</span></span><span style="display:flex;"><span>            width, height <span style="color:#f92672">=</span> bitmap<span style="color:#f92672">.</span>size
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">#Paste the image on the middle of the white background</span>
</span></span><span style="display:flex;"><span>            Himage<span style="color:#f92672">.</span>paste(bitmap, (int((<span style="color:#ae81ff">600</span><span style="color:#f92672">-</span>width)<span style="color:#f92672">/</span><span style="color:#ae81ff">2</span>), int((<span style="color:#ae81ff">488</span><span style="color:#f92672">-</span>height)<span style="color:#f92672">/</span><span style="color:#ae81ff">2</span>), int((<span style="color:#ae81ff">600</span><span style="color:#f92672">-</span>width)<span style="color:#f92672">/</span><span style="color:#ae81ff">2</span>)<span style="color:#f92672">+</span>width, int((<span style="color:#ae81ff">488</span><span style="color:#f92672">-</span>height)<span style="color:#f92672">/</span><span style="color:#ae81ff">2</span><span style="color:#f92672">+</span>height)))
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">#Initialize the display</span>
</span></span><span style="display:flex;"><span>        epd<span style="color:#f92672">.</span>init()    
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">#Display the bitmap on background</span>
</span></span><span style="display:flex;"><span>        epd<span style="color:#f92672">.</span>display(epd<span style="color:#f92672">.</span>getbuffer(Himage))
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">#Turn off the display internals and wait 10 minutes before you show the next pic</span>
</span></span><span style="display:flex;"><span>        logging<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;Goto Sleep...&#34;</span>)
</span></span><span style="display:flex;"><span>        epd<span style="color:#f92672">.</span>sleep()
</span></span><span style="display:flex;"><span>        time<span style="color:#f92672">.</span>sleep(<span style="color:#ae81ff">600</span>)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">except</span> <span style="color:#a6e22e">Exception</span> <span style="color:#66d9ef">as</span> e:
</span></span><span style="display:flex;"><span>    logging<span style="color:#f92672">.</span>info(e)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">except</span> <span style="color:#a6e22e">KeyboardInterrupt</span>:    
</span></span><span style="display:flex;"><span>    logging<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;ctrl + c:&#34;</span>)
</span></span><span style="display:flex;"><span>    epd5in65f<span style="color:#f92672">.</span>epdconfig<span style="color:#f92672">.</span>module_exit(cleanup<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    exit()
</span></span></code></pre></div><p>Insert the image path as needed, then run and check if it works.</p>
<h3 id="set-up-the-pi-to-run-unattended">Set Up the Pi To Run Unattended</h3>
<p>If everything works as intended, <a href="https://www.dexterindustries.com/howto/run-a-program-on-your-raspberry-pi-at-startup/">set up the Pi to auto-run the code on startup using this handy guide</a>. I like the SYSTEMD method best.</p>
<p>Once everything works well after you reboot the Pi, don&rsquo;t forget to <a href="https://learn.adafruit.com/read-only-raspberry-pi/overview">turn on the Overlay File System</a> to prevent SD card corruption if the power goes out unexpectedly.</p>
<h2 id="putting-everything-together">Putting Everything Together</h2>
<p>You now have an e-paper display showing beautiful pictures next to a Raspberry Pi. But it&rsquo;s not a digital photo frame yet, it needs some kind of a housing first. I decided to use a simple <a href="https://www.thingiverse.com/thing:922740">3D-printed VESA 75 mm case</a> for my Pi and then designed a <a href="https://www.printables.com/model/1072040-stand-for-565-7-color-waveshare-e-paper-with-vesa">simple stand to hold everything together</a>.</p>
<figure>
    <img src="/images/epaper/stand.png" alt="The stand from the back">
    <figcaption>You need to glue the two parts together using a drop of superglue</figcaption>
  </figure>
  
<h2 id="conclusion">Conclusion</h2>
<p>The first thing anybody will probably ask is: Why? Why use a strange display with a rather low resolution and only 7 colors to display your family photos? Without even keeping it low-power?</p>
<p>The answer is a bit complicated, but I can explain it best like this:</p>
<ul>
<li>Constraints spur creativity.</li>
<li>It&rsquo;s not about the resolution or color fidelity, it&rsquo;s about the pictures.</li>
<li>The grainy dithered images have a nice rough and timeless vintage feel to them. They give a new quality to the images I saw many times before.</li>
<li>The slow tempo of changing images does not demand your attention. It is a decor item, not another gadget to waste time with.</li>
</ul>
<p>And I got to rescue an 13-year old Raspberry Pi from the drawer in the process 😄</p>
<p>If you found this article useful, you can buy me a beer <a href="https://paypal.me/FranciKopac?country.x=SI&amp;locale.x=en_US">here</a>.</p>
]]></content:encoded></item><item><title>How To Use Minimal Power To Control an E-paper Display With an ESP32</title><link>https://nunrg.eu/posts/lowpowerepaper/</link><pubDate>Sat, 14 Sep 2024 00:00:00 +0000</pubDate><guid>https://nunrg.eu/posts/lowpowerepaper/</guid><description>How to drive an e-paper display on minimal power with an ESP32 — deep sleep, an energy harvester and a supercapacitor instead of a battery.</description><content:encoded><![CDATA[<p>You can support me by <a href="https://medium.com/p/0f0f993d9ee0">reading this article on Medium</a></p>
<h2 id="what-do-i-mean-by-low-power">What Do I Mean by Low Power?</h2>
<p>I was involved in an art project where we wanted to use a smallish (10 x 15cm) solar panel to power an e-paper display inside a gallery, controlled by an ESP32 micro controller.</p>
<p>This would be a pretty trivial job outdoors: the panel has 5W peak power and I could just put a small DC-DC converter on it to get stable 3.3V power from it during normal daylight.</p>
<p>Indoors, the game changes completely, due to two main issues: low illuminance and low solar panel efficiency at low illuminance.</p>
<p>Outdoors, the illuminance will be anywhere from 1000 to 100000lux during the day. On the other hand, normal indoor lighting would normally range from 100 to 500lux. This alone reduces the available power from the solar cell by a factor of 200 or more. 5W turns into 25mW.</p>
<p>But there is another problem: The solar panel efficiency drops at low light. At full illuminance, the total power conversion efficiency of a modern solar panel can exceed 20%, but the same panel will have a much lower efficiency at low light due to interior current leakage. The expected 25mW turns into 5 or 10mW at most with normal indoor lighting.</p>
<p>To put this in perspective: ESP32 will consume somewhere around 900 mW transferring data via WiFi and some 20 to 70 mW with radio off. See <a href="https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/">this helpful article</a>.</p>
<p>So is there no way to power the ESP32 using our solar panel?</p>
<h2 id="the-solution">The Solution</h2>
<p>Our project involves refreshing an e-paper display at certain intervals and e-paper consumes power only during the refresh (drawing). So the solution is to somehow store the miniscule amount of available energy until there is enough to draw the next picture, turn everything on, draw the picture, save the data for the next one, and power off.</p>
<h3 id="the-hardware">The Hardware</h3>
<p>On the hardware side, I achieved this using a solar harvester chip <a href="https://e-peas.com/product/aem10941/">AEM10941</a> evaluation board that very efficiently charges a single super-capacitor. This super-capacitor in turn powers a <a href="https://www.pololu.com/product/2561">low-power, low-voltage boost converter</a> and this powers the ESP32.</p>
<figure>
    <img src="/images/epaper/powerpath.png" alt="The Power Harvesting System">
    <figcaption>The Power Harvesting System</figcaption>
  </figure>
  
<h3 id="the-software">The Software</h3>
<p>But the hardware alone is not the solution yet: I needed a way to turn off everything so the harvester has the chance to charge the capacitor up, do the drawing only when the capacitor is full and then turn off everything again until there is enough power available.</p>
<h4 id="esp32-deep-sleep">ESP32 Deep Sleep</h4>
<p>The solution is to implement the deep sleep on the ESP32. While in deep sleep, the ESP32 chip only consumes 10μA, but it powers off almost everything, leaving only a small portion of slow RTC RAM and a counter that periodically wakes it up.</p>
<p>So how does it all work?</p>
<ol>
<li>
<p>The system spends most of the time in deep sleep, where the combined power usage of the power harvesting system, converter and ESP32 never exceeds about 150μW.</p>
</li>
<li>
<p>Every 5 or 10 seconds it wakes up for a fraction of a second to measure the capacitor voltage:</p>
<p>2a. If the voltage is not high enough, it goes to deep sleep again.</p>
<p>2b. If the voltage is high enough, it draws the picture, stores the sequence number into the RTC RAM and goes to deep sleep again.</p>
</li>
<li>
<p>Rinse and repeat.</p>
</li>
</ol>
<h4 id="pick-the-right-esp32-board">Pick the Right ESP32 Board!</h4>
<p>There is a possible pitfall here: Some ESP32 development boards, like the basic ESP32 Dev Module, power additional circuitry (level converters, USB-to-RS232 chips etc.) from the 3.3V rail. This means they will always use at least 10mW of power, regardless of the deep-sleep mode, which is useless for our use case. I had really good experience with the <a href="https://www.olimex.com/Products/IoT/ESP32/ESP32-DevKit-LiPo/open-source-hardware">Olimex ESP32-DevKit-LiPo</a> boards in this respect, but there are other well built development boards out there.</p>
<h4 id="there-are-always-unintended-consequences">There Are Always Unintended Consequences</h4>
<p>Remember how the chip powers off almost everything when in deep sleep? All the regular RAM contents are lost too, only the sequence number in RTC RAM remains.
So when I try to draw the next picture, the system doesn&rsquo;t know a picture was already drawn previously and simply draws over it. The results are really ugly:</p>
<p><img loading="lazy" src="/images/epaper/ugly.gif" alt="Video of the flawed drawing"  />
</p>
<p>I spent way too much time thinking how to fix this&hellip;</p>
<p>One option would be to use a full refresh, but that would entail the annoying blinking of the whole display. Then I thought about adapting the library and storing some of it&rsquo;s variables in the RTC RAM. This seemed like a sure way to lose all my remaining hair, though.</p>
<p>After a lot of thinking, I finally thought of a nice hack. The system doesn&rsquo;t know what was displayed previously? I should draw the exact same picture as the last time to &ldquo;refresh it&rsquo;s memory&rdquo; and right afterwards draw the new one.</p>
<p>This worked beautifully:</p>
<p><img loading="lazy" src="/images/epaper/nice.gif" alt="Video of the corrected drawing"  />
</p>
<h2 id="conclusion">Conclusion</h2>
<p>Super-capacitors are a wonderful way to power small devices that work at intervals when there is not much power available. They are preferable to batteries due to their much better cycling ability. I like things that you can put on a wall and expect them to work for years and years instead of becoming electronic waste soon.</p>
<p>And if you use deep sleep with ESP32, always keep in mind the lost RAM contents when the chip wakes up again.</p>
<h2 id="the-code">The Code</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&lt;GxEPD2_BW.h&gt;</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&#34;GxEPD2_display_selection_new_style.h&#34;</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&#34;bitmaps/images.h&#34;</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&lt;pgmspace.h&gt;</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&lt;driver/adc.h&gt;</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&lt;esp_sleep.h&gt;</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>RTC_DATA_ATTR <span style="color:#66d9ef">int</span> pic_index <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">int</span> adcPin <span style="color:#f92672">=</span> <span style="color:#ae81ff">34</span>; <span style="color:#75715e">// GPIO34 (ADC1_CH6)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">int</span> sleepTimeSeconds <span style="color:#f92672">=</span> <span style="color:#ae81ff">3</span>; <span style="color:#75715e">// Sleep time in seconds
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">int</span> array_size <span style="color:#f92672">=</span> <span style="color:#66d9ef">sizeof</span>(images)<span style="color:#f92672">/</span><span style="color:#66d9ef">sizeof</span>(images[<span style="color:#ae81ff">0</span>]);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">int</span> refresh_size <span style="color:#f92672">=</span> <span style="color:#ae81ff">30</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">setup</span>()
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// Configure ADC
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">adc1_config_width</span>(ADC_WIDTH_BIT_12);
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">adc1_config_channel_atten</span>(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// Read the voltage on pin 34
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">int</span> adcValue <span style="color:#f92672">=</span> <span style="color:#a6e22e">adc1_get_raw</span>(ADC1_CHANNEL_6);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">float</span> voltage <span style="color:#f92672">=</span> adcValue <span style="color:#f92672">*</span> (<span style="color:#ae81ff">3.3</span> <span style="color:#f92672">/</span> <span style="color:#ae81ff">4095.0</span>); <span style="color:#75715e">// Convert ADC reading to voltage
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (voltage<span style="color:#f92672">&gt;</span><span style="color:#ae81ff">2.45</span>) { <span style="color:#75715e">// Only if voltage is high enough
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">if</span> (pic_index<span style="color:#f92672">%</span>refresh_size <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">||</span> pic_index<span style="color:#f92672">%</span>array_size <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>) {
</span></span><span style="display:flex;"><span>      display.<span style="color:#a6e22e">init</span>(<span style="color:#ae81ff">0</span>, true, <span style="color:#ae81ff">2</span>, true); <span style="color:#75715e">// first update should be full refresh
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>      display.<span style="color:#a6e22e">init</span>(<span style="color:#ae81ff">0</span>, false, <span style="color:#ae81ff">2</span>, true); <span style="color:#75715e">// following updates should be normal refresh + draw previous picture to avoid artifacts
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>      display.<span style="color:#a6e22e">drawImage</span>(images[(pic_index<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)<span style="color:#f92672">%</span>array_size], <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">400</span>, <span style="color:#ae81ff">300</span>, false, false, true);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    display.<span style="color:#a6e22e">drawImage</span>(images[pic_index<span style="color:#f92672">%</span>array_size], <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">400</span>, <span style="color:#ae81ff">300</span>, false, false, true);
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>    display.<span style="color:#a6e22e">hibernate</span>(); <span style="color:#75715e">//Turn off voltage converters inside the display to save power
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>    pic_index<span style="color:#f92672">++</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// Configure the ESP32 to wake up after a fixed delay
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">esp_sleep_enable_timer_wakeup</span>(sleepTimeSeconds <span style="color:#f92672">*</span> <span style="color:#ae81ff">1000000</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">esp_deep_sleep_start</span>();
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">loop</span>()
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>If you found this article useful, you can buy me a beer <a href="https://paypal.me/FranciKopac?country.x=SI&amp;locale.x=en_US">here</a>.</p>
]]></content:encoded></item><item><title>Some Notes On Using E-paper Displays</title><link>https://nunrg.eu/posts/epaperesp32/</link><pubDate>Sat, 14 Sep 2024 00:00:00 +0000</pubDate><guid>https://nunrg.eu/posts/epaperesp32/</guid><description>Practical notes on using e-paper displays: how they work, their advantages and drawbacks, and tooling for ESP32 development.</description><content:encoded><![CDATA[<h2 id="what-is-e-paper">What Is E-paper?</h2>
<p>E-paper displays are a niche display technology that <a href="https://en.wikipedia.org/wiki/Electronic_paper#Microencapsulated_electrophoretic_display">uses electric fields to move colored particles in a controlled way</a> to make a very paper-like display.</p>
<p>Most people first saw it in the smart price tags in supermarkets, another widespread use is for e-book readers, and there are used more and more in signage.</p>
<figure>
    <img src="/images/epaper/pricetag.png" alt="A modern e-paper price tag">
    <figcaption>A modern e-paper price tag (credit: Minew)</figcaption>
  </figure>
  
<figure>
    <img src="/images/epaper/kindle.jpg" alt="A Kindle e-book reader">
    <figcaption>A Kindle E-book Reader (Credit: Amazon)</figcaption>
  </figure>
  
<figure>
    <img src="/images/epaper/busstop.png" alt="A bus stop sign using e-paper">
    <figcaption>A bus stop sign using e-paper (credit: Papercast)</figcaption>
  </figure>
  
<p>These are all very good ways to use a technology with some pronounced advantages and disadvantages:</p>
<h3 id="advantages">Advantages:</h3>
<ul>
<li><strong>Extremely low power requirements:</strong> They only need power during refresh and consume no power once the image is displayed. This is perfect e.g. for a price tag that normally only needs to be changed once a day or less.</li>
<li><strong>Paper-like display:</strong> They really do look like printed paper with all the advantages this brings: no need for backlight, extremely broad viewing angle, no eye strain.</li>
</ul>
<h3 id="disadvantages">Disadvantages:</h3>
<ul>
<li><strong>Low refresh rate:</strong> You can forget playing video games on e-paper, it takes anywhere from hundreds of milliseconds (smaller black and white) to tens of seconds (large multi-color) to refresh the display.</li>
<li><strong>Limited color display options:</strong> E-paper display are monochrome (black and white) by default. Lately, gray-scale (4 or 16 level) and multicolor e-paper displays became available, but these are still limited to 3, 4 or 7 colors. You can&rsquo;t control them in the same way as RGB displays, so these displays are best for art that looks like comics, i.e. with clear lines and large single-colored surfaces.</li>
<li><strong>Full refresh involves blinking:</strong> When you initialize e-paper, this involves fast blinking; the duration depends on number of colors and size. Black and white displays can do partial refresh with no blinking, but even these must do a blinking full refresh from time to time.</li>
<li><strong>Not a mainstream technology yet:</strong>
When you decide to use e-paper, there are resources available, from libraries to displays themselves. It is a technology that has seen some widespread use, especially in e-readers. But e-paper displays are much less standardized and more diverse than other display technologies. One display might allow partial refresh, the other doesn&rsquo;t. A display that supports partial refresh might make a clean update or blink just once. The libraries I&rsquo;ve seen are sometimes poorly documented. You might stumble upon unforeseen problems and will have to study some issues for sure. It&rsquo;s doable, but using an OLED or LCD display is much more straightforward.</li>
</ul>
<h2 id="how-to-use-it">How To Use It?</h2>
<p>E-paper is normally controlled by a small, low-power micro controller to take advantage of the low power requirements. It does not require much computing power and I&rsquo;ve seen systems running on a simple Arduino Nano. Most systems will use a modern micro controller like a Pico or ESP32, but some people also use a Raspberry Pi, eg. to display computer- or AI-generated art.</p>
<p>I chose to use the ESP32 micro controller.</p>
<h3 id="why-use-an-esp32-for-e-paper">Why Use an ESP32 for E-paper?</h3>
<ul>
<li>It&rsquo;s ubiquitous: There are many development tools available and a big community of developers to help you on your way.</li>
<li>It has all the periphery you need: WiFi, Bluetooth, SPI, a lot of flash</li>
<li>It will work with very low power in deep sleep. This enables compact battery- and solar-powered systems with no need for extra wiring and power supply.</li>
</ul>
<h3 id="development-tools">Development Tools</h3>
<p>There are several options to develop apps for ESP 32:</p>
<ul>
<li>The <a href="https://docs.espressif.com/projects/esp-idf/en/stable/esp32/get-started/index.html">ESP IDF</a>: A very powerful development environment that includes an IDE, but you can use it in VSCode and Platformio too.</li>
<li>The <a href="https://docs.espressif.com/projects/arduino-esp32/en/latest/">Arduino ESP32 core</a>: An Arduino core for ESP32 that you can use in Arduino and Platformio environments.</li>
</ul>
<h4 id="pros-and-cons-of-esp-idf">Pros and Cons of ESP IDF</h4>
<p>ESP IDF is the native development environment of the manufacturer, so you can use all the options the chips offer. I would prefer to use it, but there is a problem: Libraries for e-paper are limited.</p>
<p>I found only one promising library for e-paper, the <a href="https://github.com/martinberlin/cale-idf">CALE EPD</a>, but the GitHub project is not active lately (since 2023). This is a problem due to changes being introduced in the ESP IDF by ongoing development which mean you will have to fork and repair the library. The range of e-papers seems to be somewhat limited as well, even if it is still the most versatile option. In the end, I wasn&rsquo;t able to get it working, which is mostly due to me not being an experienced developer, but should still tell you something.</p>
<h4 id="pros-and-cons-of-the-arduino-core">Pros and Cons of the Arduino Core</h4>
<p>It looks like the Arduino core is not prioritized by Espressif, but it is reasonably up to date and does not have many limitations. Arduino is a mature environment that puts a lot of emphasis on being backwards compatible, which I really like, but the main advantage in this case is the fact you have a great e-paper library available:</p>
<p><a href="https://github.com/ZinggJM/GxEPD2">GxEPD2</a> is a really comprehensive e-paper library with very broad coverage of e-paper displays. It builds on Adafruit GFX, so the interface should be somewhat familiar if you worked with displays in Arduino before. It should work with all the chips Arduino supports, from the old Atmel ones, to the STM, Pico, ESP32 etc.</p>
<p>It&rsquo;s main disadvantage is the lack of comprehensive documentation There are extensive examples that will enable you to get where you are going, but there will be pitfalls.</p>
<p>The very best thing about this library is that you simply take the example project, wire the display, and select the options in the configuration files: it should work on the first try. There is also a lot of information about it in the internet.</p>
<p>If you found this article useful, you can buy me a beer <a href="https://paypal.me/FranciKopac?country.x=SI&amp;locale.x=en_US">here</a>.</p>
]]></content:encoded></item></channel></rss>