tag:blogger.com,1999:blog-55522555048692530752024-02-19T07:42:29.724-08:00Random SequenceRandom Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.comBlogger139125tag:blogger.com,1999:blog-5552255504869253075.post-72327449189094300482022-08-26T05:04:00.002-07:002022-08-26T05:10:26.147-07:00Cooling mod for the X96 Air #2<p>Previously, I <a href="https://www.randseq.org/2022/04/cooling-mod-for-x96-air.html?showComment=1654048571477#c3687721706454478638" target="_blank">added a USB cooling fan to the X96 Air TV box</a>. The problem with this mod is that the fan is always running, and it runs at full speed. Ideally, the fan should kick in only when the CPU temperature is above a certain threshold. It would be even better if there is a way to control the fan speed.</p><p>Dan McDonald left me a comment pointing to <a href="https://github.com/danboid/CH340-fan-control" target="_blank">his project on Github</a>. He basically connected the fan to a USB relay that can be controlled by Python script. His project inspired me to make a similar mod that would make use of the spare D1 Mini boards I have lying around.</p><p>The plan is to hook up the fan to a MOSFET (2N7000) and control it via PWM. Here's the very simple circuit:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieHtUTJSqxqdmrgyfoq3p3AuJsknK8YfuVyOG3md-_vRtdL4D0Aj7Fbg_hYSr-dhKNf3q_Cr6YEKbyg1Mcsz7f5Tywn4klnr97ulaNRl0mk9CaSUbWnF6sU46RvxrCSHKyVj3PVrQ7pR8DkxT4ThxCpA4f3CVVmVmzNv0JOEZnyzEHYSRMw2Vfc-QX/s601/circuit.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="601" data-original-width="521" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieHtUTJSqxqdmrgyfoq3p3AuJsknK8YfuVyOG3md-_vRtdL4D0Aj7Fbg_hYSr-dhKNf3q_Cr6YEKbyg1Mcsz7f5Tywn4klnr97ulaNRl0mk9CaSUbWnF6sU46RvxrCSHKyVj3PVrQ7pR8DkxT4ThxCpA4f3CVVmVmzNv0JOEZnyzEHYSRMw2Vfc-QX/w346-h400/circuit.png" width="346" /></a></div>The code simply reads a single character from the serial port (0 - 9). 0 will turn the fan off, while 1 - 9 will generate a proportional PWM to drive the fan, with 1 being the lowest and 9 being the highest.<div><br /></div><div>Here's the Arduino code:</div>
<pre style="background-color: #0f0f0f; color: #f0f0f0; font-family: "Lucida Console"; font-size: 10pt; white-space: pre-wrap;"><span style="color: #afdf87;">#include <Arduino.h></span>
<span style="color: #66ff99;">void</span> <span style="color: #ff99aa;">setup</span><span style="color: #ff00cc;">() {</span>
Serial<span style="color: #ff00cc;">.</span><span style="color: #ff99aa;">begin</span><span style="color: #ff00cc;">(</span><span style="color: #dfaf87;">9600</span><span style="color: #ff00cc;">);</span>
<span style="color: #ff99aa;">pinMode</span><span style="color: #ff00cc;">(</span>D1<span style="color: #ff00cc;">,</span> OUTPUT<span style="color: #ff00cc;">);</span>
<span style="color: #ff00cc;">}</span>
<span style="color: #66ff99;">void</span> <span style="color: #ff99aa;">loop</span><span style="color: #ff00cc;">() {</span>
<span style="color: #66aaff;">if</span> <span style="color: #ff00cc;">(</span>Serial<span style="color: #ff00cc;">.</span><span style="color: #ff99aa;">available</span><span style="color: #ff00cc;">() ></span> <span style="color: #dfaf87;">0</span><span style="color: #ff00cc;">) {</span>
<span style="color: #66ff99;">char</span> cmd <span style="color: #ff00cc;">=</span> Serial<span style="color: #ff00cc;">.</span><span style="color: #ff99aa;">read</span><span style="color: #ff00cc;">();</span>
<span style="color: #66ff99;">int</span> level <span style="color: #ff00cc;">= (</span><span style="color: #66ff99;">int</span><span style="color: #ff00cc;">)(</span>cmd <span style="color: #ff00cc;">-</span> <span style="color: #ffff77;">'0'</span><span style="color: #ff00cc;">);</span>
<span style="color: #66aaff;">if</span> <span style="color: #ff00cc;">(</span>level <span style="color: #ff00cc;"><</span> <span style="color: #dfaf87;">0</span> <span style="color: #ff00cc;">||</span> level <span style="color: #ff00cc;">></span> <span style="color: #dfaf87;">9</span><span style="color: #ff00cc;">)</span> level <span style="color: #ff00cc;">=</span> <span style="color: #dfaf87;">0</span><span style="color: #ff00cc;">;</span>
<span style="color: #ff99aa;">analogWrite</span><span style="color: #ff00cc;">(</span>D1<span style="color: #ff00cc;">,</span> level<span style="color: #ff00cc;">/</span><span style="color: #dfaf87;">9.0</span><span style="color: #ff00cc;">*</span><span style="color: #dfaf87;">255</span><span style="color: #ff00cc;">);</span>
<span style="color: #ff00cc;">}</span>
<span style="color: #ff99aa;">delay</span><span style="color: #ff00cc;">(</span><span style="color: #dfaf87;">100</span><span style="color: #ff00cc;">);</span>
<span style="color: #ff00cc;">}</span>
</pre>
<div>The code was verified to work correctly with a breadboard circuit and the serial monitor.</div><div><br /></div><div>Here's the prototype board that I put together in a hurry:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYOdxXX5Ts2gZeoURaer4O5FHDFO9q5Ub9naDIitKEpB5vpxzPNJ3L1dPEjr9X5ylhYIRMmWM6G8SmzeyIlZIrfAlrEIfDuRe63veiwsWUI9MSwH3ghDcgrXmHAltqSqMAlJrtX5qKm70NuFScirWrH-VHzEwPeEbrAi6-UTUBH-d6W3EzxNY1HXNm/s4000/prototype.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="3000" data-original-width="4000" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYOdxXX5Ts2gZeoURaer4O5FHDFO9q5Ub9naDIitKEpB5vpxzPNJ3L1dPEjr9X5ylhYIRMmWM6G8SmzeyIlZIrfAlrEIfDuRe63veiwsWUI9MSwH3ghDcgrXmHAltqSqMAlJrtX5qKm70NuFScirWrH-VHzEwPeEbrAi6-UTUBH-d6W3EzxNY1HXNm/s320/prototype.jpg" width="320" /></a></div><br /><div>I re-used the same fan, but connected a 2-pin JST header to it instead.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0BxzqN9GD3AGirlIKjaC3zDYwxExf8QIxohLU7wiXlZjkbunM92ou8b7XMoWE2sAedSA8al2WLLQ8roTQRgmCudygnZ0T1bagpsSVTJCiY7eIWHKFQS_NIzDPqc1aATCJn89mrztLTUCoznwwwsXTufCNljWfw7s1SsxuMqIf4Rd0oOFsXz-Q9E-m/s4000/fan_with_jst_header.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="3000" data-original-width="4000" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0BxzqN9GD3AGirlIKjaC3zDYwxExf8QIxohLU7wiXlZjkbunM92ou8b7XMoWE2sAedSA8al2WLLQ8roTQRgmCudygnZ0T1bagpsSVTJCiY7eIWHKFQS_NIzDPqc1aATCJn89mrztLTUCoznwwwsXTufCNljWfw7s1SsxuMqIf4Rd0oOFsXz-Q9E-m/s320/fan_with_jst_header.jpg" width="320" /></a></div><br /><div>Here it is sitting under the X96 Air:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUpZdw1brae08LoADRGYwtIVymx92WbJDSMxeC6ZhW1MfeJN8WifM6Sjg6PSeEGyV4gQjqiPLe2gs2E4mt25pGsnP8pnyiHcNGe-2BVPOE4qqO4hccjS_ZJ5QXmWS0pXt6_pTFw0kp81rfcCbclYo2SX7aV_1BDjtdb01LOLPMdXESpS2h-p6s9xkQ/s4000/fan_under_x96_air.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="3000" data-original-width="4000" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUpZdw1brae08LoADRGYwtIVymx92WbJDSMxeC6ZhW1MfeJN8WifM6Sjg6PSeEGyV4gQjqiPLe2gs2E4mt25pGsnP8pnyiHcNGe-2BVPOE4qqO4hccjS_ZJ5QXmWS0pXt6_pTFw0kp81rfcCbclYo2SX7aV_1BDjtdb01LOLPMdXESpS2h-p6s9xkQ/s320/fan_under_x96_air.jpg" width="320" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: left;">Printed a 3D casing for the prototype board:</div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhk5cbvZhkn0blSamHYQZ5H0LAMPKv0naBxEvpFl84Fovco8BGi2A4iCjLBjLiDGIZ_QntE3aYJLsyUxPHnW6doYF2MgADSXZYX_i63nU3j1bnE5T-Mb4ORrabPocFb3rW1zosfKtzvAu9nLp2vODeJbADsiImdgFQk2-a2k8GkXjFZvNjnyi-v0Dh8/s4000/3d_casing.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="3000" data-original-width="4000" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhk5cbvZhkn0blSamHYQZ5H0LAMPKv0naBxEvpFl84Fovco8BGi2A4iCjLBjLiDGIZ_QntE3aYJLsyUxPHnW6doYF2MgADSXZYX_i63nU3j1bnE5T-Mb4ORrabPocFb3rW1zosfKtzvAu9nLp2vODeJbADsiImdgFQk2-a2k8GkXjFZvNjnyi-v0Dh8/s320/3d_casing.jpg" width="320" /></a></div><br /><div>This is with everything hooked up:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0mGJs5RjaoQxewnvXhkOGWu8aONP4r5Y6uQr_qS5eG7Nv13pX1OYG1i5yHUG-SC4H2-hAV-4qRw7ouvfwZghxy0xyFGzmH6xqAxDW98rGhLQGYYb5FXNvco3Ezs9fSZBiHjwo73AYqFQ3EFP75Xpgs4gtV55uuymDEbY-_ZPu2Ci0QARbrGzQND7n/s4000/x96_air_fan_control.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="3000" data-original-width="4000" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0mGJs5RjaoQxewnvXhkOGWu8aONP4r5Y6uQr_qS5eG7Nv13pX1OYG1i5yHUG-SC4H2-hAV-4qRw7ouvfwZghxy0xyFGzmH6xqAxDW98rGhLQGYYb5FXNvco3Ezs9fSZBiHjwo73AYqFQ3EFP75Xpgs4gtV55uuymDEbY-_ZPu2Ci0QARbrGzQND7n/s320/x96_air_fan_control.jpg" width="320" /></a></div><br /><div>I did hit a little snufu when I tried to get it working on the X96 Air box itself. The D1 Mini uses the CH340 USB-to-serial module, which I had problems getting to work on the box (CoreELEC 19.4). The D1 Mini was receiving gibberish from the box, and I spent many hours trying to fix it. Eventually I concluded it was the CH340 driver included with this kernel, because it had zero problems working with the CP210X.</div><div><br /></div><div>I had 2 options: recompile the kernel with an updated CH340 driver, or find a board that uses the CP210X. Luckily, I have a D1 Mini Pro lying around that uses the CP2104, and it was pin-compatible with the D1 Mini. So I simply did a board swap, and everything suddenly worked smoothly!</div><div><br /></div><div>List all the USB devices connected to the X96 Air:</div>
<pre style="background-color: #0f0f0f; color: #f0f0f0; font-size: 10pt; white-space: pre-wrap;"><span style="font-family: 'Lucida Console';">CoreELEC<span style="color: #ff00cc;">:</span>~ <span style="color: #33ff00;"># lsusb</span>
Bus <span style="color: #dfaf87;">002</span> Device <span style="color: #dfaf87;">001</span><span style="color: #ff00cc;">:</span> ID <span style="color: #dfaf87;">1</span>d6b<span style="color: #ff00cc;">:</span><span style="color: #dfaf87;">0003</span> Linux Foundation <span style="color: #dfaf87;">3.0</span> root hub
Bus <span style="color: #dfaf87;">001</span> Device <span style="color: #dfaf87;">004</span><span style="color: #ff00cc;">:</span> ID <span style="color: #dfaf87;">10</span>c4<span style="color: #ff00cc;">:</span>ea60 Silicon Labs CP210x UART Bridge
Bus <span style="color: #dfaf87;">001</span> Device <span style="color: #dfaf87;">003</span><span style="color: #ff00cc;">:</span> ID <span style="color: #dfaf87;">1915</span><span style="color: #ff00cc;">:</span>af11 Nordic Semiconductor ASA Wireless Receiver
Bus <span style="color: #dfaf87;">001</span> Device <span style="color: #dfaf87;">002</span><span style="color: #ff00cc;">:</span> ID <span style="color: #dfaf87;">1</span>a40<span style="color: #ff00cc;">:</span><span style="color: #dfaf87;">0101</span> Terminus Technology Inc. Hub
Bus <span style="color: #dfaf87;">001</span> Device <span style="color: #dfaf87;">001</span><span style="color: #ff00cc;">:</span> ID <span style="color: #dfaf87;">1</span>d6b<span style="color: #ff00cc;">:</span><span style="color: #dfaf87;">0002</span> Linux Foundation <span style="color: #dfaf87;">2.0</span> root hub</span><span style="font-family: "Cascadia Code";">
</span></pre>
<div>Unfortunately, <span style="font-family: 'Lucida Console';">setserial</span> does not work to configure the USB-serial port:</div>
<pre style="background-color: #0f0f0f; color: #f0f0f0; font-size: 10pt; white-space: pre-wrap;"><span style="font-family: 'Lucida Console';">CoreELEC<span style="color: #ff00cc;">:</span>~ <span style="color: #33ff00;"># setserial /dev/ttyUSB0</span>
setserial<span style="color: #ff00cc;">:</span> can<span style="color: #ffff77;">'t get serial info: Inappropriate ioctl for device</span>
</span></pre>
<div>We need to install <span style="font-family: 'Lucida Console';">stty</span>, but to do that, we need to install <a href="https://bin.entware.net/armv7sf-k3.2/Packages.html" target="_blank">Entware</a> support:</div>
<pre style="background-color: #0f0f0f; color: #f0f0f0; font-size: 10pt; white-space: pre-wrap;"><span style="font-family: 'Lucida Console';">CoreELEC<span style="color: #ff00cc;">:</span>~ <span style="color: #33ff00;"># installentware</span>
...
...
Would you like to reboot now to finish installation <span style="color: #ff00cc;">(</span>recommended<span style="color: #ff00cc;">) [</span>y<span style="color: #ff00cc;">/</span>N<span style="color: #ff00cc;">]</span>? y</span><span style="font-family: "Cascadia Code";">
</span></pre>
<div><p>After reboot, we can install the <span style="font-family: 'Lucida Console';">stty </span>package:</p>
<pre style="background-color: #0f0f0f; color: #f0f0f0; font-size: 10pt; white-space: pre-wrap;"><span style="font-family: 'Lucida Console';">CoreELEC<span style="color: #ff00cc;">:</span>~ <span style="color: #33ff00;"># opkg install coreutils-stty</span>
Installing coreutils<span style="color: #66ff99;">-stty</span> <span style="color: #ff00cc;">(</span><span style="color: #dfaf87;">9.1</span><span style="color: #66ff99;">-1</span><span style="color: #ff00cc;">)</span> to root...
Downloading http<span style="color: #ff00cc;">://</span>bin.entware.net<span style="color: #ff00cc;">/</span>aarch64<span style="color: #66ff99;">-k3</span>.10<span style="color: #ff00cc;">/</span>coreutils<span style="color: #66ff99;">-stty_9</span>.1<span style="color: #66ff99;">-1_aarch64-3</span>.10.ipk
Installing coreutils <span style="color: #ff00cc;">(</span><span style="color: #dfaf87;">9.1</span><span style="color: #66ff99;">-1</span><span style="color: #ff00cc;">)</span> to root...
Downloading http<span style="color: #ff00cc;">://</span>bin.entware.net<span style="color: #ff00cc;">/</span>aarch64<span style="color: #66ff99;">-k3</span>.10<span style="color: #ff00cc;">/</span>coreutils_9.1<span style="color: #66ff99;">-1_aarch64-3</span>.10.ipk
Configuring coreutils.
Configuring coreutils<span style="color: #66ff99;">-stty</span>.</span><span style="font-family: "Cascadia Code";">
</span></pre>
<p>Then I created a shell script in the <span style="font-family: 'Lucida Console';">/storage</span> folder called <span style="font-family: 'Lucida Console';">cpufan.sh</span>:</p>
<pre style="background-color: #0f0f0f; color: #f0f0f0; font-size: 10pt; white-space: pre-wrap;"><span style="font-family: 'Lucida Console';"><span style="color: #33ff00;">#!/usr/bin/sh</span>
<span style="color: #ff99aa;">$(stty -F /dev/ttyUSB0 9600 cs8 -cstopb -parenb)</span>
val<span style="color: #ff00cc;">=</span><span style="color: #ff99aa;">$(/usr/bin/cputemp | /usr/bin/sed 's/[^0-9]*//g')</span>
<span style="color: #66aaff;">if</span> <span style="color: #ff00cc;">[</span> <span style="color: #ff99aa;">$val</span> <span style="color: #66ff99;">-ge</span> <span style="color: #dfaf87;">55</span> <span style="color: #ff00cc;">]</span>
<span style="color: #66aaff;">then</span>
<span style="color: #66ff99;">echo -n</span> <span style="color: #dfaf87;">5</span> <span style="color: #ff00cc;">> /</span>dev<span style="color: #ff00cc;">/</span>ttyUSB0
<span style="color: #66aaff;">else</span>
<span style="color: #66ff99;">echo -n</span> <span style="color: #dfaf87;">0</span> <span style="color: #ff00cc;">> /</span>dev<span style="color: #ff00cc;">/</span>ttyUSB0
<span style="color: #66aaff;">fi</span></span><span style="font-family: Cascadia Code;">
</span></pre>
<p>At the moment, the script is very simple. It configures the serial port to 9600/8N1, and retrieves the CPU temperature using <span style="font-family: 'Lucida Console';">cputemp</span>. If the CPU temperature is >= 55c, it sets the fan speed to 5. Otherwise, it turns it off.</p><p>Remember to make the script executable by using:</p>
<pre style="background-color: #0f0f0f; color: #f0f0f0; font-size: 10pt; white-space: pre-wrap;"><span style="font-family: 'Lucida Console';"><span style="color: #cc9966;">chmod</span> a<span style="color: #ff00cc;">+</span>x cpufan.sh</span><span style="font-family: "Cascadia Code";">
</span></pre>
<p>Finally, I run the script every minute by adding it to <span style="font-family: 'Lucida Console';">crontab</span>:</p>
<pre style="background-color: #0f0f0f; color: #f0f0f0; font-size: 10pt; white-space: pre-wrap;"><span style="font-family: 'Lucida Console';"><span style="color: #ff00cc;">*/</span><span style="color: #dfaf87;">1</span> <span style="color: #ff00cc;">* * * * /</span>storage<span style="color: #ff00cc;">/</span>cpufan.sh</span><span style="font-family: "Cascadia Code";">
</span></pre>
<p>This command is very helpful to check if the script is being run by cron, and whether there are any execution errors:</p>
<pre style="background-color: #0f0f0f; color: #f0f0f0; font-size: 10pt; white-space: pre-wrap;"><span style="font-family: 'Lucida Console';">systemctl status cron</span><span style="font-family: "Cascadia Code";">
</span></pre>
<p>This command is useful for stressing the CPU quickly and raising its temperature:</p></div>
<div>
<pre style="background-color: #0f0f0f; color: #f0f0f0; font-size: 10pt; white-space: pre-wrap;"><span style="font-family: 'Lucida Console';">stress<span style="color: #66ff99;">-ng --matrix</span> <span style="color: #dfaf87;">0</span> <span style="color: #66ff99;">-t</span> <span style="color: #dfaf87;">5</span>m</span><span style="font-family: "Cascadia Code";">
</span></pre>
</div>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com1tag:blogger.com,1999:blog-5552255504869253075.post-34602430834837597262022-04-28T22:00:00.004-07:002022-04-28T23:18:41.759-07:00DC-DC Buck Stepdown Converter for ESP8266<p>I am working on a project that requires a step-down converter from 12V to 5V, that will then power a WeMOS D1 Mini.</p><p>I saw this new <a href="https://www.aliexpress.com/item/33014445559.html" target="_blank">mini buck converter</a> based on <strike>the usual LM2596</strike> <a href="https://cdn-shop.adafruit.com/datasheets/MP2307_r1.9.pdf" target="_blank">MP2307</a>, so I thought I'd give it a try.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjd-5VTHUONhHlF1c7gfwAZi1hYLc1DPGu511kgqSUpcF0oUcQBmE3imqY3sZDvb8bBgVlrQIrhg7MDOEwI-ecBQ4nf5CpA0MFIvgpONGNKRGNLaUKbElCVbKYyBKoPm3ijM5rw9E1QGoa_iSGUryj-7UoZDvJLooirN1bOKfP8Avf61SLrmXEIYHui/s540/mini-dc-converter.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="357" data-original-width="540" height="265" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjd-5VTHUONhHlF1c7gfwAZi1hYLc1DPGu511kgqSUpcF0oUcQBmE3imqY3sZDvb8bBgVlrQIrhg7MDOEwI-ecBQ4nf5CpA0MFIvgpONGNKRGNLaUKbElCVbKYyBKoPm3ijM5rw9E1QGoa_iSGUryj-7UoZDvJLooirN1bOKfP8Avf61SLrmXEIYHui/w400-h265/mini-dc-converter.png" width="400" /></a></div><br /><p>Unfortunately, it didn't work. Although it is supposed to be able to supply up to 1.8A, the D1 Mini was not able to boot up. The 5V pin was being properly supplied, but the 3.3V pin measures at only ~1.3V.</p><p>So I had to go back to my usual LM2596 module, which is much larger, but works to power the D1 Mini with a 12V source.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTbKU_7sriq8sJ_QcciUhLVvWM8dlMAoiwrS7MKtHpnkA3PEmLU1xo9nghPOHN3HgiJFmlYhfAVwr41NtoHyLWiGFIp4dHDosU9_dxkztWgSvHxAKDpem7M4T_I7yEK9HiktYj6O0pAS3tbU70E8AT0cZfNyuY-gHkI5qkoJ0-I6sxUf5dkXxHvCQE/s512/dc-converter-lm2596.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="351" data-original-width="512" height="274" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTbKU_7sriq8sJ_QcciUhLVvWM8dlMAoiwrS7MKtHpnkA3PEmLU1xo9nghPOHN3HgiJFmlYhfAVwr41NtoHyLWiGFIp4dHDosU9_dxkztWgSvHxAKDpem7M4T_I7yEK9HiktYj6O0pAS3tbU70E8AT0cZfNyuY-gHkI5qkoJ0-I6sxUf5dkXxHvCQE/w400-h274/dc-converter-lm2596.png" width="400" /></a></div>Here's a <a href="https://goughlui.com/2018/07/04/tested-mini-360-mp2307-based-3a-buck-converter-module/" target="_blank">great review</a> of the mini buck converter I found while trying to figure out how to make it work. The fact that it has high quiescent current (~60mA) is also mentioned in a few other sources.<br /><p><br /></p>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com0tag:blogger.com,1999:blog-5552255504869253075.post-32630475662281835212022-04-17T03:44:00.003-07:002022-04-17T03:49:45.337-07:00Cooling mod for the X96 Air<p>I realized after my Ugoos box died that overheating is a big problem with cheap Android TV boxes. A teardown of the Ugoos box shows that it does not have any heatsink or fan at all!</p><p> The X96 Air does have a heatsink, but the heatsink is located at the bottom of the casing with no ventilation.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZKYoG2vHUpWU4T_7pSqedZ5qP9sEmmdBXBI86G6v3BH54-x0JbqgXiAA6rYDp6n3LRVqwytEL4UfGX0-_W7CAE4iFfFlsiFSdnRNbgLe-YFSk83oryPlVRUvnlY3YidYWsfMrC54cJ0VLrfhWcZ_X-DLbpIzLucpfH6lsyE3KerWIZRU_lWfdVMg-/s1024/x96-air-02.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="768" data-original-width="1024" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZKYoG2vHUpWU4T_7pSqedZ5qP9sEmmdBXBI86G6v3BH54-x0JbqgXiAA6rYDp6n3LRVqwytEL4UfGX0-_W7CAE4iFfFlsiFSdnRNbgLe-YFSk83oryPlVRUvnlY3YidYWsfMrC54cJ0VLrfhWcZ_X-DLbpIzLucpfH6lsyE3KerWIZRU_lWfdVMg-/w640-h480/x96-air-02.jpg" width="640" /></a></div><p>In this default configuration, with the ambient room temperature at 25c and playing a 1080p video, I was seeing the CPU temperature at 67c.</p><p>I drilled a couple of holes at the bottom of the casing.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOPKVGxlVK6uKZzjW7dAxMHvRhNhjyDZw0FG5i13YfbTHKMbe05TaMlmnGCgf77sV0YU1tt1jvggbZdJG3F3Axu8h8l2FqPT_dsovzPrE0617j4skFPsoQhO_xATyglJoLeAAdgmYMpIShSCGsXxOBcqTHLbrKpbIZnNAVknDUZbey-RDGZHL1Ntv_/s1024/x96-air-04.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="768" data-original-width="1024" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOPKVGxlVK6uKZzjW7dAxMHvRhNhjyDZw0FG5i13YfbTHKMbe05TaMlmnGCgf77sV0YU1tt1jvggbZdJG3F3Axu8h8l2FqPT_dsovzPrE0617j4skFPsoQhO_xATyglJoLeAAdgmYMpIShSCGsXxOBcqTHLbrKpbIZnNAVknDUZbey-RDGZHL1Ntv_/w400-h300/x96-air-04.jpg" width="400" /></a></div><p>The CPU temperature fell to 59c with the box raised about 2cm with plastic blocks.</p><p>I retrieved an old 5V laptop fan:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjflLOCYy2S4LpgK-qXvAzOPhnInoKuRjDUAWg_IHFUFARHbH-cVMU5dTp3k526nO29Mbly4nFRIAqkllYKPBJKrD4XtVPQfK_XGe_w7yUtlmhmYzQHLbZhQbE35zuMVU_3EQVIBV1be8uBbZjD8Q7Vgqpn41igXror-aAJwumWJuBkMD06Tp2MigSt/s1024/laptop-fan.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="768" data-original-width="1024" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjflLOCYy2S4LpgK-qXvAzOPhnInoKuRjDUAWg_IHFUFARHbH-cVMU5dTp3k526nO29Mbly4nFRIAqkllYKPBJKrD4XtVPQfK_XGe_w7yUtlmhmYzQHLbZhQbE35zuMVU_3EQVIBV1be8uBbZjD8Q7Vgqpn41igXror-aAJwumWJuBkMD06Tp2MigSt/w400-h300/laptop-fan.jpg" width="400" /></a></div><p>Then cut and strip away a spare USB cable:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlM5P0hPDw6B0FtNNVdCwocCiZ4Sr5OqKgPD3OnawOAO2CoRt-nV1H6tG8Mtgt4pJm3ElnOETBCYfd4We6cWWjF-FXrAR6PyKqAWMVuKPPyuG0-A-V4545VH4KR6Q1BUQZQxUR3vvgKjTWgwGChJ3mQ4PJlyjI7KtluP1O5BTWIkXYonB4TXNBTq-n/s1365/usb-cable.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1365" data-original-width="1024" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlM5P0hPDw6B0FtNNVdCwocCiZ4Sr5OqKgPD3OnawOAO2CoRt-nV1H6tG8Mtgt4pJm3ElnOETBCYfd4We6cWWjF-FXrAR6PyKqAWMVuKPPyuG0-A-V4545VH4KR6Q1BUQZQxUR3vvgKjTWgwGChJ3mQ4PJlyjI7KtluP1O5BTWIkXYonB4TXNBTq-n/w300-h400/usb-cable.jpg" width="300" /></a></div><div><br /></div>Solder the red and black wires on the fan and the cable:<div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgX0baYrghQZPQivUnGkkNpeloOSKMDxNcA89uFBxJiz7V8Hyk9v0oh0_yC3-WhnUCa4WMAsFDHJj6FKEL07UbSt2Bvfturcx-Es59QN_0RpUHVY86zSbynoT7u0XsOrOD-hocjWwA83mH5JzXu2G78q_JZVxZPMeI19mu8sAIAXY0TR8xxIZEnSuBX/s1024/laptop-fan-usb-cable.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="768" data-original-width="1024" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgX0baYrghQZPQivUnGkkNpeloOSKMDxNcA89uFBxJiz7V8Hyk9v0oh0_yC3-WhnUCa4WMAsFDHJj6FKEL07UbSt2Bvfturcx-Es59QN_0RpUHVY86zSbynoT7u0XsOrOD-hocjWwA83mH5JzXu2G78q_JZVxZPMeI19mu8sAIAXY0TR8xxIZEnSuBX/w400-h300/laptop-fan-usb-cable.jpg" width="400" /></a></div><br /><p>Secure the fan to the bottom of the casing with double-sided tape, then plug the fan into the box's USB connector.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtKNQJ_C1nv7jcrULumJrPF3681ceatLQr9dx6vrOYwbMNmpOeDayfBRPngHPZ8aTPzfq1Eb8_qBKnH_9O4HsuMvnJwMsIo1BLrwvVch8n1C81ipXiEs6XsZiLiOCrqUQ828FZfO38R72N9RJ_HgmilRX-yIc6G55gBZLXXO24aS7qV2QUqRMv0HEU/s1024/x96-air-with-fan.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="768" data-original-width="1024" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtKNQJ_C1nv7jcrULumJrPF3681ceatLQr9dx6vrOYwbMNmpOeDayfBRPngHPZ8aTPzfq1Eb8_qBKnH_9O4HsuMvnJwMsIo1BLrwvVch8n1C81ipXiEs6XsZiLiOCrqUQ828FZfO38R72N9RJ_HgmilRX-yIc6G55gBZLXXO24aS7qV2QUqRMv0HEU/w400-h300/x96-air-with-fan.jpg" width="400" /></a></div><div><br /></div><div>Here's a view of the box with some 3D-printed risers installed at the bottom to give the mounted fan sufficient clearance:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghGoGSmTnq-SxmgM-TKMX6CTEUwbFhnDPZ9ZxTOp1pcKmLygHPeUQeVybyJmK7gPNhYZClAWDtr05MEY8f0_GSgieBlV4yYAnhwLgnHFo5hhFWVeBSoH5_WY7bvCSmxSZT8S0jOvPzDGYYX6xaJZJofiZxj9PNWf1Sm9L8TaJNfGfp1I3gpkI3vqaz/s1024/x96-air-with-riser.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="768" data-original-width="1024" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghGoGSmTnq-SxmgM-TKMX6CTEUwbFhnDPZ9ZxTOp1pcKmLygHPeUQeVybyJmK7gPNhYZClAWDtr05MEY8f0_GSgieBlV4yYAnhwLgnHFo5hhFWVeBSoH5_WY7bvCSmxSZT8S0jOvPzDGYYX6xaJZJofiZxj9PNWf1Sm9L8TaJNfGfp1I3gpkI3vqaz/w400-h300/x96-air-with-riser.jpg" width="400" /></a></div><p>The CPU now runs at 43c, a huge drop from the no-fan situation!</p></div><div><p>The only problem is the fan is always running when the box is powered up, and I cannot find any way to control the fan programmatically. Which is a shame, because otherwise it would be possible to write a script to only turn on the fan when the CPU gets too hot, and turn it off when the CPU temperature drops sufficiently.</p><p><br /></p></div>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com2tag:blogger.com,1999:blog-5552255504869253075.post-89103994833719684822022-04-17T00:19:00.000-07:002022-04-17T00:19:06.635-07:00Installing and customizing CoreELEC in X96 Air <p>I previously installed <a href="https://coreelec.org" target="_blank">CoreELEC</a> on another TV Box (<a href="https://ugoos.com/x3-tv-box-family-series-devices-based-on-android-9-0" target="_blank">Ugoos X3 Pro</a>), which unfortunately died after only 9 months during the summer (due to the unit overheating, which I learned is a common problem for cheap Android TV boxes). So this time I purchased a <a href="https://www.amazon.com.au/X96-Air-Amlogic-Android-Keyboard/dp/B08521LWSF" target="_blank">X96 Air</a> (4GB/32Gb) and had to do the whole thing again.</p><p>So this is a note-to-self in case I ever have to install CoreELEC again on some other device.</p><p>Installation of CoreELEC is simple enough by following <a href="https://discourse.coreelec.org/t/s905x3-x96air-p2-p3-4-32gb-1gbit-lan-how-to-config/8670" target="_blank">this guide</a>. Basically, it involves downloading and writing the firmware to a microSD card using <a href="https://bztsrc.gitlab.io/usbimager/" target="_blank">usbimager</a>. Then insert the microSD card, reset the unit and hold the reset until the logo appears. The unit will then proceed to boot into CoreELEC.</p><p>First thing is to connect to WiFi, then enable SSH. This allows me to login via ssh and execute:</p>
<pre style="background-color: #0f0f0f; color: #f0f0f0; font-family: "Cascadia Code"; font-size: 10pt; white-space: pre-wrap;">ceemmc <span style="color: #66ff99;">-x</span>
</pre>
<p>from the terminal. This writes CoreELEC to the built-in eMMC storage, after which I am able to remove the microSD card and reboot the unit into CoreELEC via the built-in storage.</p><p>Now here comes the bit that was tricky enough to get me stuck for quite some time. </p><p>I am using a <a href="https://www.geekbuying.com/item/MELE-F10-PRO-2-4GHz-Air-Mouse-Wireless-Keyboard-Intelligent-Voice-with-IR-Remote-Control-fpr-PC-TV-1005--316854-316854.html" target="_blank">Mele F10 Airmouse</a>, and CoreELEC was able to detect it automatically when the USB receiver was plugged in. However, I wanted to reconfigure the keymap such that during video playback, the left and right buttons will do forward and back stepping by 10s, and the back button will do stop the video. </p><p>This can be achieved by adding <i>/storage/.kodi/userdata/keymaps/keymap.xml</i>:</p>
<pre style="background-color: #0f0f0f; color: #f0f0f0; font-family: "Cascadia Code"; font-size: 10pt; white-space: pre-wrap;"><span style="color: #66aaff;"><keymap></span>
<span style="color: #66aaff;"><fullscreenvideo></span>
<span style="color: #66aaff;"><keyboard></span>
<span style="color: #66aaff;"><left></span>stepback<span style="color: #66aaff;"></left></span>
<span style="color: #66aaff;"><right></span>stepforward<span style="color: #66aaff;"></right></span>
<span style="color: #66aaff;"><browser_back></span>stop<span style="color: #66aaff;"></browser_back></span>
<span style="color: #66aaff;"></keyboard></span>
<span style="color: #66aaff;"><mouse></span>
<span style="color: #66aaff;"><mousedrag></span>noop<span style="color: #66aaff;"></mousedrag></span>
<span style="color: #66aaff;"><mousemove></span>noop<span style="color: #66aaff;"></mousemove></span>
<span style="color: #66aaff;"><rightclick></span>stop<span style="color: #66aaff;"></rightclick></span>
<span style="color: #66aaff;"></mouse></span>
<span style="color: #66aaff;"></fullscreenvideo></span>
<span style="color: #66aaff;"></keymap></span>
</pre>
<!--HTML generated by highlight 4.2, http://www.andre-simon.de/-->
<p>The back button on my airmouse will sometimes map to <i>browser_back</i>, and sometimes <i>rightclick</i>. I am not sure why, so I have mapped both to the command <i>stop </i>in the keymap file.</p><p>Also change <i>System > Interface > Skin > Fonts</i> to <i>Arial based</i> so that Unicode filenames can be displayed.</p><p>Don't forget <i>Settings > Interface > Regional > Timezone country</i> for the correct time.</p><div>Finally, my new TV box has a LED display at the front, and I was delighted this is supported by CoreELEC to display all kinds of info (date, time, CPU temperate etc.). This is configured by downloading the appropriate device VFD file as <i>/storage/.config/vfd.conf</i> (I used <i>x96-max-1gbit-vfd.conf</i>), and also to install the OpenVFD addon.</div><div><br /></div><div><br /></div><div><br /></div>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com0tag:blogger.com,1999:blog-5552255504869253075.post-12827410537036835262022-03-21T17:18:00.006-07:002022-03-21T17:21:04.715-07:00Automating split aircon units with Tasmota + Home Assistant<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEh2tkBadXRn--IsMYMJUEVGLKa6LCS4au1kWJC7mez01kQ6fbjbGmRq75Gg5R0B1-2gQcHo3pLUxqOcEFz3RxlYrUiNuPcJKF6ZxxgXZXKoymLB1pVHcv-1s9DDP1iG_O11p7MZRVkTl5-gkUX9nibVpTWTYEZ54ruGMvf2aScArfwftO-iNnjEI-dX=s654" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="654" data-original-width="500" height="640" src="https://blogger.googleusercontent.com/img/a/AVvXsEh2tkBadXRn--IsMYMJUEVGLKa6LCS4au1kWJC7mez01kQ6fbjbGmRq75Gg5R0B1-2gQcHo3pLUxqOcEFz3RxlYrUiNuPcJKF6ZxxgXZXKoymLB1pVHcv-1s9DDP1iG_O11p7MZRVkTl5-gkUX9nibVpTWTYEZ54ruGMvf2aScArfwftO-iNnjEI-dX=w490-h640" width="490" /></a></div><br /><div>The task is to automate the control of 6 split aircon units in my house. For historical reasons, they are a wild mix of different makes and ages - Panasonic, Fujitsu, Kelvinator, Stirling and MHI. The goal is to control these units via a home automation system that is hosted locally (instead of cloud-based).</div><h3 style="text-align: left;">Hardware Components</h3><div>I use the ESP8266-based WeMos D1 Mini, which is hooked up with a IR transmitter and receiver to emulate the aircon remote control. </div><div><br /></div><div>For the firmware, I settled on <a href="https://tasmota.github.io/docs/" target="_blank">Tasmota</a>, since it was able to support all the makes of aircon units in my house (via <a href="https://github.com/crankyoldgit/IRremoteESP8266" target="_blank">IRRemote8266</a>). I also played around with <a href="https://esphome.io/" target="_blank">ESPHome</a>, but its <a href="https://esphome.io/components/climate/ir_climate.html" target="_blank">IR Remote Climate component</a> has more limited aircon support (eg. no Panasonic support). </div><div><br /></div><div>Building the hardware is pretty straightforward. I bought the <a href="https://www.aliexpress.com/item/1005002531333720.html" target="_blank">stock IR transmitter and receiver boards</a> from AliExpress, thinking it would make my job easier (wrong). </div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjT4-1bRwbRYzOG3Ldm5Mbzwvj020b150YIdnOeYOsHBwxZCYWPdDSrD762KTjpTqMyTTaTmGc1W3jxCtSA4e_V5SG_iMC33w1drD4ASG81ouJsOFGsAA69p5HIasupQFBYejiI5im8kWqLWRHdZr_JkBKunBeUzOh3eEKtoWwwran5HNP-v7k-k6QX=s576" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="535" data-original-width="576" height="297" src="https://blogger.googleusercontent.com/img/a/AVvXsEjT4-1bRwbRYzOG3Ldm5Mbzwvj020b150YIdnOeYOsHBwxZCYWPdDSrD762KTjpTqMyTTaTmGc1W3jxCtSA4e_V5SG_iMC33w1drD4ASG81ouJsOFGsAA69p5HIasupQFBYejiI5im8kWqLWRHdZr_JkBKunBeUzOh3eEKtoWwwran5HNP-v7k-k6QX=s320" width="320" /></a></div><div>I also bought a bunch of <a href="https://www.x-on.com.au/mpn/vishay/tsop34838" target="_blank">TSOP34838 IR receivers</a> for comparison.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiAyqam00yOGvFqpkJ4lHiGirh85tbdTDkED_mQSURHGP6lR8qK3Dl_qCW4raNM8ZYAYzpndnFtPjYMjytmsZOHJ22LAHXuX5-5fVtIJeHdZIPC8cXq341eoFk0HN4Ag7sG4CnqrTlXpy7EXseIFHdDcZdcej6_laxT6GLcRWEXZa8BmTC0p3oi-vKa=s517" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="482" data-original-width="517" height="298" src="https://blogger.googleusercontent.com/img/a/AVvXsEiAyqam00yOGvFqpkJ4lHiGirh85tbdTDkED_mQSURHGP6lR8qK3Dl_qCW4raNM8ZYAYzpndnFtPjYMjytmsZOHJ22LAHXuX5-5fVtIJeHdZIPC8cXq341eoFk0HN4Ag7sG4CnqrTlXpy7EXseIFHdDcZdcej6_laxT6GLcRWEXZa8BmTC0p3oi-vKa=s320" width="320" /></a></div><div>Turns out you don't really need all the extra resistors (and LED) on the IR receiver board. You can pretty much just plug in the VCC, GND and OUT pins of the bare IR receiver to the D1 Mini, and it will work. </div><div><br /></div><div>I am not sure what is the model of the IR receiver on the AliExpress boards. I suspect they are <a href="https://www.aliexpress.com/item/1005001342458582.html" target="_blank">TSOP4838</a>. From the spec sheet, TSOP34838 should have superior performance compared to the TSOP4838 for long IR codes. But for my use case, they gave quite similar performance, except for the Panasonic remote, where the TSOP34838 was used because the AliExpress receiver didn't work very well.</div><div><br /></div><div>The bigger surprise came from the IR transmitter board. It worked, but needed to be very near to the aircon unit. Upon closer inspection, it turns out the IR led was powered directly by the MCU pin, instead of using a MOSFET which was my assumption! The other SMD components on the board are just a bunch of passive resistors. This severely limited the power driving the IR led, which restricted its range. </div><div><br /></div><div>I ended up desoldering the IR LED from the board and hooking up my own resistor (40ohm) and MOSFET (2N7000). When connected to a 5V source, the 40ohm resistor pretty much maxes out the current going through the IR LED, and I was able to increase the range to 2~3m.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgSXtIe4QafUSRnAq-M4m3WrzYYfoAFRmzXFNbsN48OmUKSi2blzeUeYRiqJ0SXC4dUp4aEzzcMlfZl9YLghzLYCBDBRl3t5K-mRWgj_NlJe9fS6l2R3Yg-1BhoBsIIlAGEFbfpdxCOLOyn3ZYwJnMVABsdfLbfsdlpOeKC4j8zgbsoVs_MI1WXr4bg=s507" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="470" data-original-width="507" height="297" src="https://blogger.googleusercontent.com/img/a/AVvXsEgSXtIe4QafUSRnAq-M4m3WrzYYfoAFRmzXFNbsN48OmUKSi2blzeUeYRiqJ0SXC4dUp4aEzzcMlfZl9YLghzLYCBDBRl3t5K-mRWgj_NlJe9fS6l2R3Yg-1BhoBsIIlAGEFbfpdxCOLOyn3ZYwJnMVABsdfLbfsdlpOeKC4j8zgbsoVs_MI1WXr4bg=s320" width="320" /></a></div><div>Lesson learned: I should have gone for the baked IR LEDs and receivers directly instead of buying the boards. </div><div><br /></div><div>This is what my final board looks like. </div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjUdZjYc0jebYXK2CHlOxtPYtppw16PnKfIlx5u6kREFvQ1V_J3n_lJicqVOIoJy0iJP1PTD_VD6nfX3VjAiWczkR9IysbMZq5nzw3MHwmzyYmyuaBhEc9y-5CRJrDTZXJDb2inGiDaRK6rSJVFqM-n-w17B34uPFpmA8bQ_xYauv-xwBSm0fy8D62a=s4000" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="3000" data-original-width="4000" height="240" src="https://blogger.googleusercontent.com/img/a/AVvXsEjUdZjYc0jebYXK2CHlOxtPYtppw16PnKfIlx5u6kREFvQ1V_J3n_lJicqVOIoJy0iJP1PTD_VD6nfX3VjAiWczkR9IysbMZq5nzw3MHwmzyYmyuaBhEc9y-5CRJrDTZXJDb2inGiDaRK6rSJVFqM-n-w17B34uPFpmA8bQ_xYauv-xwBSm0fy8D62a=s320" width="320" /></a></div><br /><h3 style="clear: both; text-align: left;">Hardware Unit Test</h3><div class="separator" style="clear: both; text-align: left;">The transmitter is hooked up to D1, and the receiver is hooked up to D1 on the MCU.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgespD5G_YlWVLr1LmbXTooc3mJT9dROzU8tG81ay8QNciZx34wQqsQm-RW2xdlhDJQuO8_NQQfytt00oDZKxBs1D0aOW1QAL5sprVON2KRpYkAU3ZG_FryDdcCWNM-mmSowrNShKam44yGQ9A2PqX0LXe_YM6JL90EdwjIgYspH7BIVYyR4-orVHl8=s700" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="700" data-original-width="421" height="320" src="https://blogger.googleusercontent.com/img/a/AVvXsEgespD5G_YlWVLr1LmbXTooc3mJT9dROzU8tG81ay8QNciZx34wQqsQm-RW2xdlhDJQuO8_NQQfytt00oDZKxBs1D0aOW1QAL5sprVON2KRpYkAU3ZG_FryDdcCWNM-mmSowrNShKam44yGQ9A2PqX0LXe_YM6JL90EdwjIgYspH7BIVYyR4-orVHl8=s320" width="192" /></a></div><br /><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both;">The IR receiver was verified to be working by pointing the aircon remote at the board and observing the output at the Tasmota console.</div><div class="separator" style="clear: both;"><br /></div>
<!--HTML generated using hilite.me--><div style="background: rgb(255, 255, 255); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #4400ee; font-weight: bold;">06</span><span style="color: #333333;">:</span><span style="color: #4400ee; font-weight: bold;">07</span><span style="color: #333333;">:</span><span style="color: #6600ee; font-weight: bold;">17.472</span> MQT<span style="color: #333333;">:</span> tele<span style="color: #333333;">/</span>office_ac<span style="color: #333333;">/</span><span style="color: #0000cc;">RESULT</span> <span style="color: #333333;">=</span> {<span style="background-color: #fff0f0;">"IrReceived":{"Protocol":"LG","Bits":28,"Data":"0x8808440","DataLSB":"0x10012102","Repeat":0,"IRHVAC":{"Vendor":"LG","Model":1,"Mode":"Cool","Power":"On","Celsius":"On","Temp":19,"FanSpeed":"Max","SwingV":"Off","SwingH":"Off","Quiet":"Off","Turbo":"Off","Econo":"Off","Light":"On","Filter":"Off","Clean":"Off","Beep":"Off","Sleep"</span><span style="color: #333333;">:-</span><span style="color: #0000dd; font-weight: bold;">1</span>}}}
<span style="color: #4400ee; font-weight: bold;">06</span><span style="color: #333333;">:</span><span style="color: #4400ee; font-weight: bold;">07</span><span style="color: #333333;">:</span><span style="color: #6600ee; font-weight: bold;">17.963</span> MQT<span style="color: #333333;">:</span> tele<span style="color: #333333;">/</span>office_ac<span style="color: #333333;">/</span><span style="color: #0000cc;">RESULT</span> <span style="color: #333333;">=</span> {<span style="background-color: #fff0f0;">"IrReceived":{"Protocol":"LG","Bits":28,"Data":"0x8808541","DataLSB":"0x1001A182","Repeat":0,"IRHVAC":{"Vendor":"LG","Model":1,"Mode":"Cool","Power":"On","Celsius":"On","Temp":20,"FanSpeed":"Max","SwingV":"Off","SwingH":"Off","Quiet":"Off","Turbo":"Off","Econo":"Off","Light":"On","Filter":"Off","Clean":"Off","Beep":"Off","Sleep"</span><span style="color: #333333;">:-</span><span style="color: #0000dd; font-weight: bold;">1</span>}}}
<span style="color: #4400ee; font-weight: bold;">06</span><span style="color: #333333;">:</span><span style="color: #4400ee; font-weight: bold;">07</span><span style="color: #333333;">:</span><span style="color: #6600ee; font-weight: bold;">18.965</span> MQT<span style="color: #333333;">:</span> tele<span style="color: #333333;">/</span>office_ac<span style="color: #333333;">/</span><span style="color: #0000cc;">RESULT</span> <span style="color: #333333;">=</span> {<span style="background-color: #fff0f0;">"IrReceived":{"Protocol":"LG","Bits":28,"Data":"0x8808642","DataLSB":"0x10016142","Repeat":0,"IRHVAC":{"Vendor":"LG","Model":1,"Mode":"Cool","Power":"On","Celsius":"On","Temp":21,"FanSpeed":"Max","SwingV":"Off","SwingH":"Off","Quiet":"Off","Turbo":"Off","Econo":"Off","Light":"On","Filter":"Off","Clean":"Off","Beep":"Off","Sleep"</span><span style="color: #333333;">:-</span><span style="color: #0000dd; font-weight: bold;">1</span>}}}
<span style="color: #4400ee; font-weight: bold;">06</span><span style="color: #333333;">:</span><span style="color: #4400ee; font-weight: bold;">07</span><span style="color: #333333;">:</span><span style="color: #6600ee; font-weight: bold;">19.915</span> MQT<span style="color: #333333;">:</span> tele<span style="color: #333333;">/</span>office_ac<span style="color: #333333;">/</span><span style="color: #0000cc;">RESULT</span> <span style="color: #333333;">=</span> {<span style="background-color: #fff0f0;">"IrReceived":{"Protocol":"LG","Bits":28,"Data":"0x8808844","DataLSB":"0x10011122","Repeat":0,"IRHVAC":{"Vendor":"LG","Model":1,"Mode":"Cool","Power":"On","Celsius":"On","Temp":23,"FanSpeed":"Max","SwingV":"Off","SwingH":"Off","Quiet":"Off","Turbo":"Off","Econo":"Off","Light":"On","Filter":"Off","Clean":"Off","Beep":"Off","Sleep"</span><span style="color: #333333;">:-</span><span style="color: #0000dd; font-weight: bold;">1</span>}}}
</pre></div>
<div class="separator" style="clear: both;"><br /></div><div class="separator" style="clear: both;">I find that for the different makes of aircon, I needed to tune certain parameters to make the IR decoding work better. In particular, I needed to tweak the <span style="font-family: courier;">IR_RCV_MIN_UNKNOWN_SIZE</span> and <span style="font-family: courier;">IR_RCV_TOLERANCE</span> parameters for the different makes. Some required higher or lower <span style="font-family: courier;">IR_RCV_MIN_UNKNOWN_SIZE</span> values, and vice versa for <span style="font-family: courier;">IR_RCV_TOLERANCE</span>. </div><div class="separator" style="clear: both;"><br /></div><div class="separator" style="clear: both;"><i>Note: I am compiling and uploading Tasmota from source using PlatformIO, and placing the customized values in <span style="font-family: courier;">tasmota/user_config_override.h</span>.</i></div><h3 style="clear: both; text-align: left;">Home Automation System</h3><div class="separator" style="clear: both;"><div>For the home automation system, I settled on <a href="https://www.home-assistant.io/" target="_blank">Home Assistant</a>. I also evaluated <a href="https://www.openhab.org/" target="_blank">OpenHAB</a>, but the learning curve appears much steeper compared to Home Assistant. What seals the deal for me is also the availability of <a href="https://github.com/hristo-atanasov/Tasmota-IRHVAC" target="_blank">Tasmota-IRHVAC</a>, a third-party component for Home Assistant that makes integrating my home-made Tasmota aircon controllers a breeze compared to OpenHAB.</div><div><br /></div></div></div><div class="separator" style="clear: both; text-align: left;">I am running Home Assistant in a <a href="https://www.virtualbox.org/" target="_blank">VirtualBox</a> VM on my Windows server. Getting it up-and-running is as simple as <a href="https://www.home-assistant.io/installation/windows" target="_blank">downloading the VDI file</a> and starting it up in VirtualBox using the recommended config (2GB RAM, 2 vCPU). Networking is set to "bridged", and using the "Paravirtualized network adapter". </div><div><br /></div><div>Once it is running, access the home page (<a href="http://homeassistant.local:8123">http://homeassistant.local:8123</a>) and create an admin user to login. </div><div><br /></div><div>Then we install 2 addons under <b>Configuration</b> / <b>Add-ons, Backups & Supervisor</b>:</div><div><ul style="text-align: left;"><li><a href="https://www.home-assistant.io/docs/mqtt/broker/" target="_blank">Mosquitto Broker</a></li><li><a href="https://community.home-assistant.io/t/home-assistant-community-add-on-ssh-web-terminal/33820" target="_blank">SSH & Web Terminal</a></li></ul></div><div>For <b>Mosquitto Broker</b>, the only thing I changed in the config file was adding a login/password:</div><div><br /></div>
<!--HTML generated using hilite.me--><div style="background: rgb(240, 243, 243); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;">certfile: fullchain.pem
customize:
active: false
folder: mosquitto
keyfile: privkey.pem
logins:
- username: <b><i>myuser</i></b>
password: <b><i>mypass</i></b>
require_certificate: false
</pre></div>
<div><br /></div><div>There are probably more sophisticated ways to improve the security, but that's what I have settled on for now.</div><div><br /></div><div><b>SSH & Web Terminal</b> lets you log into Home Assistant via secure shell, and use the normal shell commands to make changes to the system.</div><h3 style="text-align: left;">Integrating Tasmota-IRHVAC</h3><div>I created a folder in <span style="font-family: courier;">config</span> called <span style="font-family: courier;">aircons:</span></div><div><span style="font-family: courier;"><br /></span></div>
<!--HTML generated using hilite.me--><div style="background: rgb(240, 243, 243); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #336666;">cd </span>config
mkdir aircons
</pre></div>
<div><br /></div><div>In this folder, I can put all my aircon config files. A sample config file is available in <span style="font-family: courier;">Tasmota-IRHVAC-master/examples/configuration.yaml</span>, with lots of config paramaters that you probably won't need. </div><div><br /></div><div>For example, my Panasonic config file (<span style="font-family: courier;">living_ac.yaml</span>) ended up looking like:</div><div><br /></div>
<!--HTML generated using hilite.me--><div style="background: rgb(240, 243, 243); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"> - platform: tasmota_irhvac
name: <span style="color: #cc3300;">"Living</span><span style="color: #003333;"> </span><span style="color: #cc3300;">AC"</span>
command_topic: <span style="color: #cc3300;">"cmnd/living_ac/irhvac"</span>
state_topic: <span style="color: #cc3300;">"tele/living_ac/RESULT"</span>
vendor: <span style="color: #cc3300;">"PANASONIC_AC"</span>
target_temp: 25
initial_operation_mode: <span style="color: #cc3300;">"off"</span>
away_temp: 25
supported_modes:
- <span style="color: #cc3300;">"heat"</span>
- <span style="color: #cc3300;">"cool"</span>
- <span style="color: #cc3300;">"auto"</span>
- <span style="color: #cc3300;">"off"</span>
supported_fan_speeds:
- <span style="color: #cc3300;">"auto"</span>
- <span style="color: #cc3300;">"low"</span>
- <span style="color: #cc3300;">"medium"</span>
- <span style="color: #cc3300;">"high"</span>
- <span style="color: #cc3300;">"max"</span>
supported_swing_list:
- <span style="color: #cc3300;">"off"</span>
hvac_model: <span style="color: #cc3300;">"3"</span>
</pre></div>
<div><br /></div><div>As explained in the documentation, the <i>supported_modes</i>, <i>supported_fan_speeds</i> and <i>supported_swing_list</i> values are derived by using the target remote on the Tasmota device and observing the console output. You also get the <i>vendor</i> and <i>hvac_model</i> values this way.</div><div><br /></div><div><div>In the <span style="font-family: courier;">config</span> folder, there is a <span style="font-family: courier;">configuration.yaml</span> file. I need to add this line at the end to include all the files in the <span style="font-family: courier;">aircons</span> folder.</div><div><br /></div>
<!--HTML generated using hilite.me--><div style="background: rgb(240, 243, 243); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;">climate: <span style="color: #007788; font-weight: bold;">!include_dir_merge_list</span> aircon/
</pre></div>
</div><div><br /></div><div><div>After all this work is done, Home Assistant needs to be restarted for the changes to take effect. Validate the new config files by selecting <b>Configuration</b> / <b>Settings</b> / <b>Configuration validation</b> / <b>Check configuration</b>. Since yaml files are indentation sensitive, a common error you will encounter is incorrect indentation. Once the configuration is verified to be correct, Home Assistant can be restarted.</div><div><br /></div></div><div>One final step is to configure the MQTT section of the Tasmota device to point to MQTT broker on Home Assistant, and to match the instance value in the aircon yaml ("<i>living_ac</i>" in the example above).</div><h3 style="text-align: left;">A little detour about mDNS</h3><div>The documentation of Home Assistant is all about accessing the server through the mDNS name <a href="http://homeassistant.local">http://homeassistant.local</a>.</div><div><br /></div><div>Unfortunately, I find that mDNS is a little unreliable. Sometimes it takes quite a long while before the <span style="font-family: courier;">homeassistant.local</span> domain becomes available, if at all. I didn't have the time to properly troubleshoot this, and it seems it is such a common problem the Tasmota developers have decided to <a href="https://github.com/arendst/Tasmota/issues/4793" target="_blank">disable mDNS support by default</a>. </div><div><br /></div><div>As such, I decided to assign a static IP to the Home Assistant VM. This can be done by following the instructions <a href="https://rapidlydigital.com/assign-a-static-ip-to-home-assistant/" target="_blank">here</a>.</div><div><br /></div><div>So when configuring MQTT on Tasmota devices, I use the static IP for the MQTT broker address (<span style="font-family: courier;">192.168.1.3</span> in my case).</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEj7xwl7Wt8xhBNqlOVrebYA_2aEOSIeqwsO6C3Of2l7arwlzzCSCTv0Za8ATx3eM7lpM4XRXClinmpcZNntYe2PImvl7tgpEgtaO5wpJQ8qbPiT7dKF3G5IPNP0eo4g6H98TMHUSr00MpkJExuTzE17ELBk9liiYd-MP-hMmSi6XP-X5Yrtq8Vj3CSR=s714" style="margin-left: 1em; margin-right: 1em; outline-width: 0px; user-select: auto;"><img border="0" data-original-height="714" data-original-width="425" height="400" src="https://blogger.googleusercontent.com/img/a/AVvXsEj7xwl7Wt8xhBNqlOVrebYA_2aEOSIeqwsO6C3Of2l7arwlzzCSCTv0Za8ATx3eM7lpM4XRXClinmpcZNntYe2PImvl7tgpEgtaO5wpJQ8qbPiT7dKF3G5IPNP0eo4g6H98TMHUSr00MpkJExuTzE17ELBk9liiYd-MP-hMmSi6XP-X5Yrtq8Vj3CSR=w238-h400" width="238" /></a></div><br /><div><i>User</i> and <i>Password</i> are set to the "myuser" and "mypass" values set previously when configuring the MQTT broker. Topic is set to the name used in the command topic in the aircon yaml file (<i>living_ac</i> if you are following the example <span style="font-family: courier;">living_ac.yaml</span> file, but the screenshot above shows <i>office_ac</i> for another aircon yaml file).</div><h3 style="text-align: left;">Adding the AC Card to the Home Asisstant Dashboard</h3><div>If all goes well, you are finally ready to view and control the new AC in the Home Assistant dashboard.</div><div><br /></div><div>Select the hamburger menu on the dashboard and click <b>Edit dashboard</b>. </div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhBMXx8l5oSdorUUb5zg0ix92BhC7qaTPkn_J1Cyy3VSeMZXyWmwmCsRpmdl5OLRgTtv79YekjyZjQ30tXPMPtaFJoD8q6HgvNJn5NXGlIzdAGGCxyhFhWRTBTHPqRdbO0M7q5NeYrFnQYN0QVuZ5r-o7bSiSlRouWDZojd2Swtxk2uwI-hF5cJWPly=s646" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="646" data-original-width="500" height="400" src="https://blogger.googleusercontent.com/img/a/AVvXsEhBMXx8l5oSdorUUb5zg0ix92BhC7qaTPkn_J1Cyy3VSeMZXyWmwmCsRpmdl5OLRgTtv79YekjyZjQ30tXPMPtaFJoD8q6HgvNJn5NXGlIzdAGGCxyhFhWRTBTHPqRdbO0M7q5NeYrFnQYN0QVuZ5r-o7bSiSlRouWDZojd2Swtxk2uwI-hF5cJWPly=w310-h400" width="310" /></a></div><br /><div>Then select <b>Add card</b> / <b>By Entity</b>, and you should see the <b>Living AC</b> / <b>climate.living_ac</b> card ready to be added.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiyTAHm2EHh3vGUt0AmOwPEgiSEW4XZcS1OgDFKM6BF14ANRYgdOeP-xMeONrd4EwPFfchbA_rG78rmhnyCW9qNhXU1jd-zJXmEZj1N4oTeTjQg3g9coDg18UFpn11I2CNBz4QOUtmJHE0WlhfGAAbi48CsaOn8AxNvEaeJmBCtJF-JqVrVUHbzIXB6=s652" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="652" data-original-width="503" height="400" src="https://blogger.googleusercontent.com/img/a/AVvXsEiyTAHm2EHh3vGUt0AmOwPEgiSEW4XZcS1OgDFKM6BF14ANRYgdOeP-xMeONrd4EwPFfchbA_rG78rmhnyCW9qNhXU1jd-zJXmEZj1N4oTeTjQg3g9coDg18UFpn11I2CNBz4QOUtmJHE0WlhfGAAbi48CsaOn8AxNvEaeJmBCtJF-JqVrVUHbzIXB6=w309-h400" width="309" /></a></div><br /><div>This should now show up in the dashboard with all the modes that you have defined for that aircon in its YAML file:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjg9uwC6zxlal4TKKv4zMqa1zZnE_5qTk7Z69y4nQlsumDHP6GJ1rqcKkFoBukzKzW1xuw6qQguKQG9cCOCoLoFAYxVwCyzsPyn7XmLvwj83xlyv3ERGfbyuoccMl1zYu6tQRiZFf2zjgiY0Y3ZtShm9Q6PdBl4joJKuwTE6S4JY-2wvrMyZ2NVU1_V=s654" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="654" data-original-width="500" height="400" src="https://blogger.googleusercontent.com/img/a/AVvXsEjg9uwC6zxlal4TKKv4zMqa1zZnE_5qTk7Z69y4nQlsumDHP6GJ1rqcKkFoBukzKzW1xuw6qQguKQG9cCOCoLoFAYxVwCyzsPyn7XmLvwj83xlyv3ERGfbyuoccMl1zYu6tQRiZFf2zjgiY0Y3ZtShm9Q6PdBl4joJKuwTE6S4JY-2wvrMyZ2NVU1_V=w306-h400" width="306" /></a></div><br /><div>Not only can you now control each aircon remotely, if someone uses the physical remote to change the aircon settings, it will also be reflected in the dahsboard due to the IR receiver we have in our Tasmota hardware.</div><div><br /></div><div>This is how the boards are mounted, sitting in a 3D-printed enclosure, and placed somewhere under the aircon indoor unit.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgbmYBC7NqdPfeRJJks6MkjdSAgBC-fPWWp2n9cwIFjYZoe59yrh05qzJaAq2tL4yQaL6DQ1Dq5lOmIkYHHgT8zaV0prYvsImxgXmka2fYVg5l8Jbm-mYzMYkvR1hhhw58VXI5cbBtWQla3cTHmKMol-3PpMizZjf8ln2MTO83L2q-FimZDHMzmksHv=s4000" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="3000" data-original-width="4000" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEgbmYBC7NqdPfeRJJks6MkjdSAgBC-fPWWp2n9cwIFjYZoe59yrh05qzJaAq2tL4yQaL6DQ1Dq5lOmIkYHHgT8zaV0prYvsImxgXmka2fYVg5l8Jbm-mYzMYkvR1hhhw58VXI5cbBtWQla3cTHmKMol-3PpMizZjf8ln2MTO83L2q-FimZDHMzmksHv=w400-h300" width="400" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEg6Ursiye0hJcSclT12aImpPq5Y0cSyHhWYx-ZtaTzyfatmSmPiUAUyhe5EadxEyBl_XBmVQDq0urEjIxJSAJpSlvkfuQga7QoV-dg22C2VEGcQPMtFEkeoZP7XzHGUAsPn6ElWPq2ld10cFLRVrq6KhvekgHtEFLu1TmHlgU9lE3nBhP6-bfkWNbIP=s4000" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="3000" data-original-width="4000" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEg6Ursiye0hJcSclT12aImpPq5Y0cSyHhWYx-ZtaTzyfatmSmPiUAUyhe5EadxEyBl_XBmVQDq0urEjIxJSAJpSlvkfuQga7QoV-dg22C2VEGcQPMtFEkeoZP7XzHGUAsPn6ElWPq2ld10cFLRVrq6KhvekgHtEFLu1TmHlgU9lE3nBhP6-bfkWNbIP=w400-h300" width="400" /></a></div><br /><div>With the aircons added to Home Assistant, it also becomes possible to create scripts using <b>Configuration</b> / <b>Automations and Scenes</b> to, for example, turn them all on at the same time when the temperature goes above or below a certain value, or turn them all off at a certain time etc.</div>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com0tag:blogger.com,1999:blog-5552255504869253075.post-32117822120563745142022-01-21T22:44:00.004-08:002022-03-30T17:21:52.975-07:00Home-made Bakkwa (肉干)<p>This post is about making Singapore/Malaysia style bakkwa (肉干), or savory pork slices, at home.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiwOL6wkkaz3Zp3aHmako7aqc5Re7tSlBsNDnEsBlQVH7oqtJAnOh7V0HYtZQ6siMaDFphYsZI7w_lvf0u2kwNqp_1sScSoouP_gLwiLox-Qk93W4aX-FObupvqXcemh7J7Z4LKKASh776kkL4DHGZHqcmj2KB9td42uI-6JZkaEMJ_YGkPi8MX48Xd=s2048" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1536" data-original-width="2048" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEiwOL6wkkaz3Zp3aHmako7aqc5Re7tSlBsNDnEsBlQVH7oqtJAnOh7V0HYtZQ6siMaDFphYsZI7w_lvf0u2kwNqp_1sScSoouP_gLwiLox-Qk93W4aX-FObupvqXcemh7J7Z4LKKASh776kkL4DHGZHqcmj2KB9td42uI-6JZkaEMJ_YGkPi8MX48Xd=w400-h300" width="400" /></a></div><p>There are numerous recipes for this online, but none of them hit the mark for me. It took many attempts over several years, going through different recipes and techniques, before I put together a recipe that can be easily made at home, which I feel closely resembles the commercial product.</p><p>Commercial products are priced upwards of $50/kg in Singapore, and even up to $100/kg when they are exported to places like Australia. Yet, making it at home only cost $10+/kg in terms of ingredients, so it is something that is definitely worthwhile making at home. Also the process for making it is not really complicated.</p><p>First the main ingredient, pork. Most online recipe will use minced pork, but I like to use thinly sliced pork (1mm thickness). The texture of the final product is different. The former is softer, while the latter is more chewy. Both types are sold commercially: 切片 (sliced) and 碎肉 (minced). The sliced variety is typically $10/kg more expensive than the minced variety.</p><p>If you use minced pork, go as close as 20% fat/80% lean meat as possible. For sliced pork, go with a cut of meat that has a good proportion of fat. I use the "collar butt" cut from Costco, which typically cost $10.99/kg for a 3kg slab.</p><p>For slicing, I go with a cheap manual meat slicer like this:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjXKyy1bT6ktks78YUX-3Rb6E3NxH8CGZNVamyxHg1lrAYPr08slCyGeBami28whxpL7Heot7jg0chyVBD-IvusYztz9ES1XeJsExKfCoMCdr7RkzbAJa_xb5xHeHYpU2DS-kukuxiibStL-oLiw4fnBhw-HSD744PIP_W3E3xEW25ataWngrDEGPKS=s661" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="603" data-original-width="661" height="365" src="https://blogger.googleusercontent.com/img/a/AVvXsEjXKyy1bT6ktks78YUX-3Rb6E3NxH8CGZNVamyxHg1lrAYPr08slCyGeBami28whxpL7Heot7jg0chyVBD-IvusYztz9ES1XeJsExKfCoMCdr7RkzbAJa_xb5xHeHYpU2DS-kukuxiibStL-oLiw4fnBhw-HSD744PIP_W3E3xEW25ataWngrDEGPKS=w400-h365" width="400" /></a></div><p>I have written about the <a href="https://www.randseq.org/2020/11/planned-obsolescence.html" target="_blank">crappy plastic gears in cheap electric slicers</a>, so never am I going to get another one!</p><p>I will typically cut the meat into 1kg slabs, then freeze them in the freezer. This prepares them for slicing when I am ready to make a batch.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgcOO3KYv7YLsV2do6nz_l7Stnm65gjXOwaBxlGmBY6BVg1Hc_aoC9TmHO8OytS5xzNdjpgPrIwfnmXl0wQl-7RJ0db95FoQTZH5icfxMIVs5O31jIouSlrN0mni-to3FBCHpjsttIwSm5kRNObNt4jEVPNc6zuolTnvyyEVyhanVpMeZi7uQaVy5oS=s1920" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1440" data-original-width="1920" height="240" src="https://blogger.googleusercontent.com/img/a/AVvXsEgcOO3KYv7YLsV2do6nz_l7Stnm65gjXOwaBxlGmBY6BVg1Hc_aoC9TmHO8OytS5xzNdjpgPrIwfnmXl0wQl-7RJ0db95FoQTZH5icfxMIVs5O31jIouSlrN0mni-to3FBCHpjsttIwSm5kRNObNt4jEVPNc6zuolTnvyyEVyhanVpMeZi7uQaVy5oS=s320" width="320" /></a></div><div><br /></div>If you are using sliced pork, after slicing, leave them alone for an hour or two for the meat to thaw.<div><br /></div><div>Then marinate the meat with the following ingredients:</div><div><div><ul style="text-align: left;"><li>2 tbsp soy sauce</li><li>2 tbsp sweet soy sauce (kecap manis)</li><li>2 tbsp fish sauce</li><li>1 tbsp oyster sauce</li><li>2 tbsp rose wine </li><li>2 cubes fermented red tofu (玫瑰腐乳) </li><li>1/2 tsp pepper</li><li>1/2 tsp five spice powder</li><li>90g sugar</li></ul></div><div>What I'd typically do is to mix all the wet ingredients together in a cup, add the dry ingredients, then stir thoroughly. It's best to mash the fermented tofu with a fork before stirring. This is importantly because you do not want any lumps of fermented tofu in your marinate. Finally pour the marinate into the pork and stir thoroughly with a spatula. </div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhh1s5bu5FwM5eMPqrj16PRZrTWOUSiodGIDblU-vChVtrJIZ2m5oBmadO9BVsBRLGJ-1XjSMnCX-lYOoeWPA2_BV-igoQSwNMb_hHeuMqcgGX09Y9KsEwlcCl34uTUUFWIQSWzBSnZPifgI5N7nSF7_XzzlymHkPV4X7VDTlcKEQH57eXy6S1vdKpN=s1920" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1440" data-original-width="1920" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEhh1s5bu5FwM5eMPqrj16PRZrTWOUSiodGIDblU-vChVtrJIZ2m5oBmadO9BVsBRLGJ-1XjSMnCX-lYOoeWPA2_BV-igoQSwNMb_hHeuMqcgGX09Y9KsEwlcCl34uTUUFWIQSWzBSnZPifgI5N7nSF7_XzzlymHkPV4X7VDTlcKEQH57eXy6S1vdKpN=w400-h300" width="400" /></a></div><div><br /></div><div>Spend a bit more elbow grease doing this so that the pork are evenly coated with the marinate, otherwise you might end up with some pieces tasting very salty or sweet, while other pieces tasting rather bland.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhfHK5nrcsh7WgM4VzmqtNrlWC35TNqJSgT1uH69b-R9W5RmACCZpEor8WO2EFpCpJSSZlou3jmT-HWo8Z2Z3Fe7OdDrhWeqQyN5DBfppMkvoy_HNRMQg6yR2rd4Bb8117PM-5GLb7TBQlNoIwE5DqS7ruw-4oyApJqdBcGQMU-681b9CbKhZbgLYi4=s1920" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1440" data-original-width="1920" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEhfHK5nrcsh7WgM4VzmqtNrlWC35TNqJSgT1uH69b-R9W5RmACCZpEor8WO2EFpCpJSSZlou3jmT-HWo8Z2Z3Fe7OdDrhWeqQyN5DBfppMkvoy_HNRMQg6yR2rd4Bb8117PM-5GLb7TBQlNoIwE5DqS7ruw-4oyApJqdBcGQMU-681b9CbKhZbgLYi4=w400-h300" width="400" /></a></div><div><br /></div><div>Note that the critical ingredient here is the fermented tofu, which really enhances the taste of the final product. Regular versions are fine too, if you can't find the red variety. I can't remember where I discovered this, because most online recipes do not include this ingredient, but it is the secret sauce that makes all the difference! I have omitted the rose wine sometimes, or replace it with mirin. I also add a teaspoon of chili flakes to spice things up when I feel like it.</div><div><br /></div><div>Once you have thoroughly mixed the pork with the marinate, leave it for 15 minutes or so. Now we come to the second step, creating thin sheets of pork. Most recipes will tell you to lay a lump of pork on baking paper, lay a sheet of clingwrap over it, and flatten the lump using a rolling pin. However, it is really difficult to create a uniform sheet of consistent thickness using this approach. You will find invariably that there are some high and low spots in the sheet, and this critically affects the texture of the final product.</div><div><br /></div><div>My solution was to create a small tray like this:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhLn4TItQpU9daWnHP_eNNXxjJ5fAPCoAIw1IpHq9QGmxyvDmCj1qnkTRgjTO1wZxOiOoj2QQr72xiEulmnneakvkt1zJA5OH3REuYXklfqVOyuREvnMJaAweQtIsuZ87zKVoIdVRrxi0PF7qZFjcn7qPzHDimyN3WfUNUTF3Ui1BlWeqLrkOAD9elC=s1920" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1440" data-original-width="1920" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEhLn4TItQpU9daWnHP_eNNXxjJ5fAPCoAIw1IpHq9QGmxyvDmCj1qnkTRgjTO1wZxOiOoj2QQr72xiEulmnneakvkt1zJA5OH3REuYXklfqVOyuREvnMJaAweQtIsuZ87zKVoIdVRrxi0PF7qZFjcn7qPzHDimyN3WfUNUTF3Ui1BlWeqLrkOAD9elC=w400-h300" width="400" /></a></div><div><br /></div><div>Mine was printed using a 3D printer, but I can imagine making this using balsa wood or something equivalent. The inner dimension is 15x15cm, and the depth is 3mm. The round holes are there to make it easier to release the pork sheets once they are set.</div><div><br /></div><div>To use this tray, cut a piece of baking paper and place it at the centre:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjrkSoTNq5icIwGJYY8ilLt_Cw3d3l_n7-DtLfEI0K1COK2e5SNX-qGJx2WCpkfNJIE4thzgJ7XDq-fV3ZYQpDTz4vefw_UBCSukN5IsBvqsCxnKKBD7tJ6NmR3-HPjTZ9fUyz1xziddbqc2cu-42IQMEDBwYA7ayilzouCX_jiD4WZ6XGG-EeRqkHu=s1920" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1440" data-original-width="1920" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEjrkSoTNq5icIwGJYY8ilLt_Cw3d3l_n7-DtLfEI0K1COK2e5SNX-qGJx2WCpkfNJIE4thzgJ7XDq-fV3ZYQpDTz4vefw_UBCSukN5IsBvqsCxnKKBD7tJ6NmR3-HPjTZ9fUyz1xziddbqc2cu-42IQMEDBwYA7ayilzouCX_jiD4WZ6XGG-EeRqkHu=w400-h300" width="400" /></a></div><div><br /></div>Now scoop some pork and place it on the baking paper:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhBHl9X7yR1qsmkPnzpRTAdcQAVTFMvvwMGx-Vf5ifivN5OfwMeQzmj0h0tFXOLI1GFsxeLZ3WW8v5wOEtZE1FS5Dsr0hvgde-fGZ_GlfBbzhyJJE5Vs7pBDuJ9Lo0qoK-2eUUioOXz6rWiMbhYnY25XQM7y4LxWkNu1htQGD4VF0G9Jgf8K99261d6=s1920" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1440" data-original-width="1920" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEhBHl9X7yR1qsmkPnzpRTAdcQAVTFMvvwMGx-Vf5ifivN5OfwMeQzmj0h0tFXOLI1GFsxeLZ3WW8v5wOEtZE1FS5Dsr0hvgde-fGZ_GlfBbzhyJJE5Vs7pBDuJ9Lo0qoK-2eUUioOXz6rWiMbhYnY25XQM7y4LxWkNu1htQGD4VF0G9Jgf8K99261d6=w400-h300" width="400" /></a></div><div><br /></div><div>Cover with a sheet of clingwrap and flatten with a rolling pin. Then cut off any excess meat at the edges with a pair of scissors.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgwwzzcKinYmoT5SK2lMjAhzDvisItFOVxx0rVSGcwJqQ0sPkLaGAqZHp3ISo7l74SoW8Q-bc3VhZEHQnLhxqRkidQeA7_VE4TTfYW-pAY_U1Nf4QWV1L9End-KvhWI7VAZL8f5v-t1DT2X9oOdsLRSJCj5xqjbpkjBYMwRFuoKqHNTCuah8uaZJT-N=s1920" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1440" data-original-width="1920" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEgwwzzcKinYmoT5SK2lMjAhzDvisItFOVxx0rVSGcwJqQ0sPkLaGAqZHp3ISo7l74SoW8Q-bc3VhZEHQnLhxqRkidQeA7_VE4TTfYW-pAY_U1Nf4QWV1L9End-KvhWI7VAZL8f5v-t1DT2X9oOdsLRSJCj5xqjbpkjBYMwRFuoKqHNTCuah8uaZJT-N=w400-h300" width="400" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div>Now, gently peel off the clingwrap and invert the tray onto a perforated silicon mat (I will talk about that in a second).</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEh-86HgspNGEPLvpACsUktae4FjnNRKqVXmrTEHC9gVMBdNDQKXXyy3ZcdYFndZT0msbS3b_Ei-k_kb8Ck5k4gu0IJq1vPTe1tXhaE4XUTdoP2YUgDf9yAGqvW6l_Aqu6j8t1MuH51zj2lNBeWc1jkS5r8A8XtZcdgng56gnISjxVIMdZkIxmr1VPW-=s1920" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1440" data-original-width="1920" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEh-86HgspNGEPLvpACsUktae4FjnNRKqVXmrTEHC9gVMBdNDQKXXyy3ZcdYFndZT0msbS3b_Ei-k_kb8Ck5k4gu0IJq1vPTe1tXhaE4XUTdoP2YUgDf9yAGqvW6l_Aqu6j8t1MuH51zj2lNBeWc1jkS5r8A8XtZcdgng56gnISjxVIMdZkIxmr1VPW-=w400-h300" width="400" /></a></div><br /><div>Then slowly peel off the baking paper.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEi81ku0MD_zfKT-4soQ_ryUYh4nZ8IXM0vnS_AP6yjAE1W7IQV2tGKIuHNHwGzfRC13aDr1yuLO-H4FwC3kpvgDm1W_11jWNsZj2pWW7dZaeSGJVPUZ_SCxwhnxUnFOlfsxTVcF9RAyjEwcrNCrwqLdFGDJZuhuXs9oP4YXcw4aG_uXf1bVtau67NiW=s1920" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1440" data-original-width="1920" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEi81ku0MD_zfKT-4soQ_ryUYh4nZ8IXM0vnS_AP6yjAE1W7IQV2tGKIuHNHwGzfRC13aDr1yuLO-H4FwC3kpvgDm1W_11jWNsZj2pWW7dZaeSGJVPUZ_SCxwhnxUnFOlfsxTVcF9RAyjEwcrNCrwqLdFGDJZuhuXs9oP4YXcw4aG_uXf1bVtau67NiW=w400-h300" width="400" /></a></div><br /><div>and we are ready to make the next piece. I am able to fit 6 pieces onto one silicon mat.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiPNpxm9WkGhuwhJE0Y6lY6z2Nh9yhAET8VYleYa2egHlA-RCw5VELf376zihtN8M1HkE5LEDMHiqmioInPf5UYokPlHiNJjTCI4O-IulUIyUNIedO0t2BlfASHcvXjlGrmv0EGB5oPpqeLjAumjViIU92qepcM3eToVDqMIiImB_pAf056jjGu5sa6=s1920" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1440" data-original-width="1920" height="240" src="https://blogger.googleusercontent.com/img/a/AVvXsEiPNpxm9WkGhuwhJE0Y6lY6z2Nh9yhAET8VYleYa2egHlA-RCw5VELf376zihtN8M1HkE5LEDMHiqmioInPf5UYokPlHiNJjTCI4O-IulUIyUNIedO0t2BlfASHcvXjlGrmv0EGB5oPpqeLjAumjViIU92qepcM3eToVDqMIiImB_pAf056jjGu5sa6=s320" width="320" /></a></div><div><br /></div><div>Using this method, it is very easy to ensure that each piece of bakkwa has uniform thickness throughout, which ensures consistent texture in the final product. And each piece is already the right size without additional cutting or trimming.</div><div><br /></div><div>Now let's talk about the perforated silicon mat. In most recipes, the next step is to place the pork pieces in the oven and cook them under low temperature eg. 120c. But I find that a better way is to dehydrate the pork pieces at even lower temperatures (50~60c). I do this in my dehydrator for 2 hours at 50c.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEj199C32smztxfPRV1woACtnPKE7ogB51UDxfBIYAKpH4L9eRswD4YQdPHpT7UUc5n_A4M5dTPMXbgV9Ga0s4kdWRzgbDEt2mK5fuVG1DOlO1UzKcLZDV33eDO59O7Jl5VPj9YwFjEVTFkbfLNTP40c_M6IvIv4DUdx_IjdWTpLYLjlJ2pAb9MzH6Vo=s1920" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1440" data-original-width="1920" height="240" src="https://blogger.googleusercontent.com/img/a/AVvXsEj199C32smztxfPRV1woACtnPKE7ogB51UDxfBIYAKpH4L9eRswD4YQdPHpT7UUc5n_A4M5dTPMXbgV9Ga0s4kdWRzgbDEt2mK5fuVG1DOlO1UzKcLZDV33eDO59O7Jl5VPj9YwFjEVTFkbfLNTP40c_M6IvIv4DUdx_IjdWTpLYLjlJ2pAb9MzH6Vo=s320" width="320" /></a></div><br /><div>If your oven has a low temperature setting, you can use that as well. I have also done it in an oven by setting it to 100c and keeping the door ajar with a piece of rolled-up towel. In this case, the temperature was maintained at around 70c, and I only have to do it for 30 minutes.</div><div><br /></div><div>The perforated silicon mats are made for dehydrators, and is great for keeping the bottom of the pieces dry. If you use baking paper like most recipes suggest, the bottom will be damp. Even worse, at higher temperatures, some of the marinate will be released, making the final product less tasty.</div><div><br /></div><div>Whichever method you use to dehydrate the pork pieces, the objectives should be 1) to *not* cook the meat 2) to dehydrate them just enough so that they can be lifted off the silicon mats in one piece easily. If this is done correctly, you will find very little loss of marinate, and all the yummy goodness remains embedded within the pork pieces.</div><div><br /></div><div>The final step is to cook the pork pieces. Now, most recipes will suggest you bake them in an oven at high temperature (200c) using the grill mode. I have also tried using air fryers. However, I find that the best result can only be obtained using a traditional grill. This is due to the level of control you can achieve for each piece. Using the oven or air fryer, I find that the edges typically come out over-cooked, while the centres remain undercooked. Also you have very little control over the level of charring.</div><div><br /></div><div>Traditionally, bakkwa is grilled using charcoal grills, so if you have access to that, that is definitely the top choice. However, a more convenient method will be to get a cheap electric grill plate that can be had for as low as $25 at the stores (cheaper if you buy second hand). Then simply turn it up and grill each piece for about 2 to 3 minutes on each side.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEg9nA-MwwjeL9CPr0ek9-yvEK53xoAp65jOB1KQ0Mj948L7GOSMK2DmqF5SLi6h8UQhUDSRBMTEsI8DlcKoUauH1NRRBLH0Wzx3Rt0HGBqG4ExiOqnrZfFJLjVydDYTT8lX5aFu602pUPOxMBPyXopNhEaTWVoep436miiZWKea5GuqFGa1qY3Z7u0t=s1920" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1440" data-original-width="1920" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEg9nA-MwwjeL9CPr0ek9-yvEK53xoAp65jOB1KQ0Mj948L7GOSMK2DmqF5SLi6h8UQhUDSRBMTEsI8DlcKoUauH1NRRBLH0Wzx3Rt0HGBqG4ExiOqnrZfFJLjVydDYTT8lX5aFu602pUPOxMBPyXopNhEaTWVoep436miiZWKea5GuqFGa1qY3Z7u0t=w400-h300" width="400" /></a></div><br /><div>Now, this step takes some experimentation to get right, since it depends on the temperature of your grill and how chewy you like the final output to be. In general, these are the factors you are trying to balance:</div><div><ul style="text-align: left;"><li>The longer you cook the pieces, the more chewy they are going to be. But if you cook them for too long, they may become too tough. If the cooking time is too short, they will be soft and have that "porky" taste.</li></ul><ul style="text-align: left;"><li>You want the grill plate to be hot, but not so hot that it will immediately char the pork pieces. As for the final product, you want the pork pieces to be slightly charred, but not *too* charred. This can be achieved by turning the pieces over regularly to examine them.</li></ul></div><div><i><span style="color: #6aa84f;">Note: If you find the dehydrated pieces turning pinkish when you grill them, that's a sign that the pork pieces have not been dehydrated enough. I was reminded recently when I accidentally ended the dehydration process early (1 hour instead of 2 hour). The "porky" taste in the pieces were pretty obvious after I grilled them. This would not have happened if I didn't make this mistake.</span></i></div><div><br /></div><div>Once the pieces are cooked, rest them somewhere standing so that any excess fat can drip away. A 5 to 10-minute rest for each piece is more than sufficient.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjSFloJNK7Xlu_jsdSzgnNkS4z-trNaIFAYyMjyzdEw6c4iNS08RTdqDTHfBz8dz5DZwJrg0f3V5Hl_es8urjxItbvpowd5XlRYTu71RsId2caV2AUrKM0zMd98HVFJKXFAht2QhY4ogQK-DltUUQJO-1m_IVu_9wq-41w37fCjtqhMQTBgrIF6v5aM=s1920" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1440" data-original-width="1920" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEjSFloJNK7Xlu_jsdSzgnNkS4z-trNaIFAYyMjyzdEw6c4iNS08RTdqDTHfBz8dz5DZwJrg0f3V5Hl_es8urjxItbvpowd5XlRYTu71RsId2caV2AUrKM0zMd98HVFJKXFAht2QhY4ogQK-DltUUQJO-1m_IVu_9wq-41w37fCjtqhMQTBgrIF6v5aM=w400-h300" width="400" /></a></div><br /><div>Despite my somewhat lengthy essay above, the actual process is actually quite simple.</div><div><ul style="text-align: left;"><li>Prepare sliced pork (no preparation if using minced pork from supermarket)</li><li>Add marinate</li><li>Make pork slices</li><li>Dehydrate pork slices</li><li>Cook pork slices</li></ul></div><div>Actual working time (minus wait time) is only about 40 minutes for 1kg pork.</div><div><br /></div><div>Important differences from existing online recipes:</div><div><ul style="text-align: left;"><li>Use sliced pork for more chewy texture similar to 切片肉干 that can be purchased commercially</li><li>Use fermented tofu in marinate</li><li>Pork pieces should have uniform thickness for consistent texture in final product. My solution is to make a special tray for this.</li><li>Dehydrate the pork pieces using very low temperature, not cook them. This ensures maximal preservation of marinate in pork pieces.</li><li>Cook the pork pieces on electric grill for better control.</li></ul></div><div><br /></div><div><br /></div><div><br /></div>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com1tag:blogger.com,1999:blog-5552255504869253075.post-29896488353603643742022-01-02T19:08:00.005-08:002022-04-13T19:16:33.710-07:00ESPCLOCK4 - Implementation<p>I have uploaded the final code for ESPCLOCK4 to <a href="https://github.com/victor-chew/espclock4" target="_blank">GitHub</a>. Full details and schematic at the repository.</p><p>I have also created 2 prototypes, 25cm and 38cm clocks: </p><div class="separator" style="clear: both; text-align: center;"><a href="https://github.com/victor-chew/espclock4/raw/main/images/clock-25cm-front.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="552" data-original-width="800" height="552" src="https://github.com/victor-chew/espclock4/raw/main/images/clock-25cm-front.jpg" width="800" /></a></div><br /><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://github.com/victor-chew/espclock4/raw/main/images/clock-25cm-back.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="600" data-original-width="800" height="600" src="https://github.com/victor-chew/espclock4/raw/main/images/clock-25cm-back.jpg" width="800" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://github.com/victor-chew/espclock4/raw/main/images/clock-38cm-front.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="600" data-original-width="800" height="600" src="https://github.com/victor-chew/espclock4/raw/main/images/clock-38cm-front.jpg" width="800" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://github.com/victor-chew/espclock4/raw/main/images/clock-38cm-back.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="600" data-original-width="800" height="600" src="https://github.com/victor-chew/espclock4/raw/main/images/clock-38cm-back.jpg" width="800" /></a></div>I am quite happy with this version. It is much easier to debug (by using a dev board with the UART intact during development, and switching to one with the UART removed to save power for production), and very straightforward to implement on a prototype board. It also uses very few external components, but is equally power efficient.<div><br /></div><div>Future enhancements will include:</div><div><br /></div><div>- Adding an external clock crystal to eliminate the RTC_SLOW_CLK drift.</div><div><br /></div><div>- Using C to write the ULP code. Assembly code is tedious and error-prone.</div><div><br /></div><div><a href="https://www.randseq.org/search/label/espclock">ESPCLOCK1</a> / <a href="https://www.randseq.org/search/label/espclock2">ESPCLOCK2</a> / <a href="https://www.randseq.org/search/label/espclock3">ESPCLOCK3</a> / <a href="https://www.randseq.org/search/label/espclock4">ESPCLOCK4</a></div>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com5tag:blogger.com,1999:blog-5552255504869253075.post-44744753842479595432021-10-24T23:38:00.006-07:002021-10-24T23:42:50.146-07:00Added battery holder to shaver<p>In <a href="https://www.randseq.org/2020/11/planned-obsolescence.html">a previous post on planned obsolescence</a>, I mentioned I replaced the rechargeable battery of a shaver that was meant to be sent to the landfill after the battery has expired.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqO40cnlya4bRu-PK7pweyLTHjYuG6qqEdbOYeCwWQo6Bl2QwvqSqKURnv4S0jTIU4Rm9BBD8znkLZver3A5Hooc91wOrtmo-T7orJ5R8KBq7IFpdiw7m-PKE89Z3YdB4VpsAvzmJyLsY/s960/IMG_20200915_120152.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="720" data-original-width="960" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqO40cnlya4bRu-PK7pweyLTHjYuG6qqEdbOYeCwWQo6Bl2QwvqSqKURnv4S0jTIU4Rm9BBD8znkLZver3A5Hooc91wOrtmo-T7orJ5R8KBq7IFpdiw7m-PKE89Z3YdB4VpsAvzmJyLsY/w640-h480/IMG_20200915_120152.jpg" width="640" /></a></div><p>Well, the Eneloop that I soldered to it has run out of steam after about 3 years. So I 3D-printed a low profile AA battery holder for the shaver to make the job of replacing the battery much easier.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizpxMo8beROAXz4RoJ80PX_YdRgVmaK33HgJ5UxqUXdlQvIj8QJ7-nuqltxF5BvruQ1bIPTG1HADbg3HDsl6B9AbggXaoSSKaqLAY7meXRTEQjJ56lY-wbmvsTFxeItMIWEn33_H1Dck0/s1920/PXL_20211025_050633820_1.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1440" data-original-width="1920" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizpxMo8beROAXz4RoJ80PX_YdRgVmaK33HgJ5UxqUXdlQvIj8QJ7-nuqltxF5BvruQ1bIPTG1HADbg3HDsl6B9AbggXaoSSKaqLAY7meXRTEQjJ56lY-wbmvsTFxeItMIWEn33_H1Dck0/w640-h480/PXL_20211025_050633820_1.jpg" width="640" /></a></div><p>Of course, it would have been much simpler if the product was designed with a proper battery compartment that you can just open in the first place, but that would not have pleased the bean counters, would it?</p><p><br /></p>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com0tag:blogger.com,1999:blog-5552255504869253075.post-76731261079155552262021-08-17T00:00:00.004-07:002021-08-31T17:55:52.096-07:00Stealth port exclusions on Windows 10<p>I guess this is a perfect example of how people get cynical of software updates after going through the routine for awhile. And this is coming from someone who enjoys solving technical problems when he is in the right mood!</p><p>So recently, I started having some long-running software complain that it can't bind to a certain TCP port because "the port is already in use". I immediately pulled out my trusty <a href="https://www.nirsoft.net/utils/cports.html" target="_blank">CurrPorts</a> and check out which mysterious program is hogging the port behind my back (yeah I could use <a href="https://www.printsupportcenter.com/hc/en-us/articles/115003386949-Determine-which-program-uses-or-blocks-a-port">netstat</a>, but who has time to memorize all those command line arguments, right?)</p><p>To my surprise, nothing, nadda. No one is using that port. Yet that port is mysteriously barred from use. It's like you suddenly cannot open the door to your home with your existing key. Incredibly frustrating.</p><p>Anyway, after 2 whole days of research, I finally found the culprit. Apparently after a certain Windows update (1809 or 2004 from various sources, I didn't care to verify), Windows now reserves certain ports (called "Administered port exclusions") for Hyper-V (not sure why that would affect me, since I am not using it). </p><p>To view the list, using the command line:</p><!--HTML generated using hilite.me--><div style="background: rgb(240, 243, 243); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;">netsh int ipv4 show excludedportrange tcp
</pre></div>
<p>You'd be surprised by how many ports are reserved. On my machine, this is the output:</p><p><!--HTML generated using hilite.me--></p><div style="background: rgb(240, 243, 243); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;">Protocol tcp Port Exclusion Ranges
Start Port End Port
<span style="color: #555555;">----------</span> <span style="color: #555555;">--------</span>
<span style="color: #ff6600;">5357</span> <span style="color: #ff6600;">5357</span>
<span style="color: #ff6600;">7834</span> <span style="color: #ff6600;">7933</span>
<span style="color: #ff6600;">7934</span> <span style="color: #ff6600;">8033</span>
<span style="color: #ff6600;">8034</span> <span style="color: #ff6600;">8133</span>
<span style="color: #ff6600;">8134</span> <span style="color: #ff6600;">8233</span>
<span style="color: #ff6600;">8234</span> <span style="color: #ff6600;">8333</span>
<span style="color: #ff6600;">8334</span> <span style="color: #ff6600;">8433</span>
<span style="color: #ff6600;">8434</span> <span style="color: #ff6600;">8533</span>
<span style="color: #ff6600;">8637</span> <span style="color: #ff6600;">8736</span>
<span style="color: #ff6600;">8737</span> <span style="color: #ff6600;">8836</span>
<span style="color: #ff6600;">8837</span> <span style="color: #ff6600;">8936</span>
<span style="color: #ff6600;">8937</span> <span style="color: #ff6600;">9036</span>
<span style="color: #ff6600;">9037</span> <span style="color: #ff6600;">9136</span>
<span style="color: #ff6600;">9137</span> <span style="color: #ff6600;">9236</span>
<span style="color: #ff6600;">9237</span> <span style="color: #ff6600;">9336</span>
<span style="color: #ff6600;">9537</span> <span style="color: #ff6600;">9636</span>
<span style="color: #ff6600;">9637</span> <span style="color: #ff6600;">9736</span>
<span style="color: #ff6600;">9737</span> <span style="color: #ff6600;">9836</span>
<span style="color: #ff6600;">9837</span> <span style="color: #ff6600;">9936</span>
<span style="color: #ff6600;">9937</span> <span style="color: #ff6600;">10036</span>
<span style="color: #ff6600;">10037</span> <span style="color: #ff6600;">10136</span>
<span style="color: #ff6600;">10137</span> <span style="color: #ff6600;">10236</span>
<span style="color: #ff6600;">10551</span> <span style="color: #ff6600;">10650</span>
<span style="color: #ff6600;">10651</span> <span style="color: #ff6600;">10750</span>
<span style="color: #ff6600;">10751</span> <span style="color: #ff6600;">10850</span>
<span style="color: #ff6600;">10851</span> <span style="color: #ff6600;">10950</span>
<span style="color: #ff6600;">10951</span> <span style="color: #ff6600;">11050</span>
<span style="color: #ff6600;">11051</span> <span style="color: #ff6600;">11150</span>
<span style="color: #ff6600;">11151</span> <span style="color: #ff6600;">11250</span>
<span style="color: #ff6600;">11277</span> <span style="color: #ff6600;">11376</span>
<span style="color: #ff6600;">11377</span> <span style="color: #ff6600;">11476</span>
<span style="color: #ff6600;">11477</span> <span style="color: #ff6600;">11576</span>
<span style="color: #ff6600;">11577</span> <span style="color: #ff6600;">11676</span>
<span style="color: #555555;">*</span> <span style="color: #555555;">-</span> Administered port exclusions.
</pre></div>
<p></p><p>Here are some associated links from my research:</p><p></p><ul style="text-align: left;"><li><a href="https://superuser.com/questions/1450103/reserved-ports-in-windows-1809">Reserved ports in Windows 1809</a></li><li><a href="https://serverfault.com/questions/1045527/how-do-i-find-out-why-certain-ports-are-excluded-and-delete-the-exclusion" target="_blank">How do I find out why certain ports are excluded and delete the exclusion?</a></li><li><a href="https://superuser.com/questions/1579346/many-excludedportranges-how-to-delete-hyper-v-is-disabled" target="_blank">Many excludedportranges how to delete - hyper-v is disabled</a></li></ul><p></p><p>Anyway, the solution for me was to issue this command:</p><p><!--HTML generated using hilite.me--></p><div style="background: rgb(240, 243, 243); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;">reg add HKLM\SYSTEM\CurrentControlSet\Services\hns\State /v EnableExcludedPortRange /d <span style="color: #ff6600;">0</span> /f
</pre></div>
<p>It basically sets the <b>EnableExcludedPortRange</b> registry value to <b>0</b>. A reboot is required.</p><p>This is incredibly frustrating because it came out of nowhere, no meaningful error message was provided and even trying to research the problem took a lot of time to figure out the right keywords that will yield the right answer. It was as if the guys who came up with this wanted to inflict the maximum pain on the affected user (or more likely they didn't really give a f**k).</p><h4 style="text-align: left;">Update (1 Sep 2021):</h4><p>Discovered that a better solution is to issue this command at an elevated CMD:</p><!--HTML generated using hilite.me--><div style="background: rgb(240, 243, 243); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;">netsh int ipv4 <span style="color: #006699; font-weight: bold;">set</span> <span style="color: #003333;">dynamic</span> tcp start<span style="color: #555555;">=</span><span style="color: #ff6600;">49152</span> num<span style="color: #555555;">=</span><span style="color: #ff6600;">16384</span>
</pre></div><p>After a reboot, the new reserved ports will be:</p><!--HTML generated using hilite.me--><div style="background: rgb(240, 243, 243); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;">C:\>netsh int ipv4 show excludedportrange tcp
Protocol tcp Port Exclusion Ranges
Start Port End Port
---------- --------
<span style="color: #ff6600;">2869</span> <span style="color: #ff6600;">2869</span>
<span style="color: #ff6600;">5357</span> <span style="color: #ff6600;">5357</span>
<span style="color: #ff6600;">49152</span> <span style="color: #ff6600;">49251</span>
<span style="color: #ff6600;">49370</span> <span style="color: #ff6600;">49469</span>
<span style="color: #ff6600;">49470</span> <span style="color: #ff6600;">49569</span>
<span style="color: #ff6600;">49725</span> <span style="color: #ff6600;">49824</span>
<span style="color: #ff6600;">49825</span> <span style="color: #ff6600;">49924</span>
<span style="color: #ff6600;">49925</span> <span style="color: #ff6600;">50024</span>
<span style="color: #ff6600;">50025</span> <span style="color: #ff6600;">50124</span>
<span style="color: #ff6600;">50125</span> <span style="color: #ff6600;">50224</span>
<span style="color: #ff6600;">50443</span> <span style="color: #ff6600;">50542</span>
<span style="color: #ff6600;">50543</span> <span style="color: #ff6600;">50642</span>
<span style="color: #ff6600;">50643</span> <span style="color: #ff6600;">50742</span>
<span style="color: #ff6600;">50743</span> <span style="color: #ff6600;">50842</span>
<span style="color: #ff6600;">50843</span> <span style="color: #ff6600;">50942</span>
<span style="color: #ff6600;">50943</span> <span style="color: #ff6600;">51042</span>
<span style="color: #ff6600;">51043</span> <span style="color: #ff6600;">51142</span>
<span style="color: #ff6600;">51457</span> <span style="color: #ff6600;">51556</span>
<span style="color: #ff6600;">51557</span> <span style="color: #ff6600;">51656</span>
<span style="color: #ff6600;">51657</span> <span style="color: #ff6600;">51756</span>
<span style="color: #ff6600;">51757</span> <span style="color: #ff6600;">51856</span>
<span style="color: #ff6600;">51857</span> <span style="color: #ff6600;">51956</span>
<span style="color: #ff6600;">51957</span> <span style="color: #ff6600;">52056</span>
<span style="color: #ff6600;">52151</span> <span style="color: #ff6600;">52250</span>
<span style="color: #ff6600;">60580</span> <span style="color: #ff6600;">60679</span>
<span style="color: #ff6600;">60883</span> <span style="color: #ff6600;">60982</span>
<span style="color: #ff6600;">61088</span> <span style="color: #ff6600;">61187</span>
<span style="color: #ff6600;">61356</span> <span style="color: #ff6600;">61455</span>
<span style="color: #ff6600;">64877</span> <span style="color: #ff6600;">64976</span>
<span style="color: #ff6600;">64977</span> <span style="color: #ff6600;">65076</span>
<span style="color: #ff6600;">65077</span> <span style="color: #ff6600;">65176</span>
<span style="color: #ff6600;">65177</span> <span style="color: #ff6600;">65276</span>
<span style="color: #ff6600;">65277</span> <span style="color: #ff6600;">65376</span>
<span style="color: #ff6600;">65377</span> <span style="color: #ff6600;">65476</span>
* - Administered port exclusions.
</pre></div>
Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com0tag:blogger.com,1999:blog-5552255504869253075.post-81807454182688177312021-08-02T22:14:00.008-07:002022-04-13T19:16:23.479-07:00ESPCLOCK4 - Deciding which direction to traverse to catch up with the present time<p>For more obvious cases eg. clock time is 12:05 and present time is 12:00, we don't have to think too hard to decide which direction to take the second hand to match up the two times.</p><p>However, suppose the clock time is currently 12:00, and the present time is 6:00. We can move the second hand forward 8x, or backwards 4x. Which direction will result in quicker synchronization of the 2 times?</p><p>For both directions, the number of seconds to traverse is 6 x 60 x 60 = 21600 seconds.</p><p>If we take the forward direction, the time taken to traverse half the number of seconds i.e. 10800 is 1350 seconds. However, in that time, the present time would have advanced by the same amount, so the number of seconds left to traverse would be 10800 + 1350 = 12150 seconds. If we do this iteratively, we would find the total time required to achieve synchronization is 3085 seconds, with 4 seconds left to catch up.</p><p>If we take the reverse direction, the time taken to traverse half would be 10800 / 4 = 2700 seconds. However, the present time would have advanced by the same amount, so the number of seconds left to traverse would be 10800 - 2700 = 8100 seconds. If we do this iteratively, the total time required to achieve synchronization is 4320 seconds, with 1 second left to catch up.</p><p>So in this case, traversing in the forward direction will result in quicker synchronization.</p><p>The Python code for performing this calculation is:</p><!--HTML generated using hilite.me--><div style="background: rgb(240, 243, 243); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #006699; font-weight: bold;">def</span> <span style="color: #cc00ff;">calc_sync_time</span>(direction, duration, speedup):
result <span style="color: #555555;">=</span> <span style="color: #ff6600;">0</span>;
<span style="color: #006699; font-weight: bold;">while</span>(duration <span style="color: #555555;">></span> speedup<span style="color: #555555;">*</span><span style="color: #ff6600;">2</span>):
half <span style="color: #555555;">=</span> <span style="color: #336666;">int</span>(duration<span style="color: #555555;">/</span><span style="color: #ff6600;">2</span>)
interval <span style="color: #555555;">=</span> <span style="color: #336666;">int</span>(half <span style="color: #555555;">/</span> speedup)
result <span style="color: #555555;">+=</span> interval
duration <span style="color: #555555;">=</span> duration <span style="color: #555555;">-</span> (interval <span style="color: #555555;">*</span> speedup) <span style="color: #555555;">+</span> (interval <span style="color: #555555;">*</span> direction)
result <span style="color: #555555;">+=</span> <span style="color: #336666;">int</span>(duration<span style="color: #555555;">/</span>speedup)
<span style="color: #336666;">print</span>((<span style="color: #cc3300;">"Fwd"</span>,<span style="color: #cc3300;">"Rev"</span>)[direction <span style="color: #555555;">==</span> <span style="color: #555555;">-</span><span style="color: #ff6600;">1</span>], <span style="color: #cc3300;">"="</span>, result, duration<span style="color: #555555;">%</span>speedup)
fwd_duration <span style="color: #555555;">=</span> <span style="color: #ff6600;">7</span><span style="color: #555555;">*</span><span style="color: #ff6600;">60</span><span style="color: #555555;">*</span><span style="color: #ff6600;">60</span> <span style="color: #555555;">+</span> <span style="color: #ff6600;">2</span>
calc_sync_time( <span style="color: #ff6600;">1</span>, fwd_duration, <span style="color: #ff6600;">8</span>)
calc_sync_time(<span style="color: #555555;">-</span><span style="color: #ff6600;">1</span>, (<span style="color: #ff6600;">12</span><span style="color: #555555;">*</span><span style="color: #ff6600;">60</span><span style="color: #555555;">*</span><span style="color: #ff6600;">60</span>) <span style="color: #555555;">-</span> fwd_duration, <span style="color: #ff6600;">4</span>)
</pre></div>
<p>With a little trial and eror, I can determine that the dividing line is when <i>fwd_duration</i> = 7*60*60+2 = 25202 eg. when clock time is 4:59:58 and needs to sync to 12:00:00. The forward and reverse timing in this case are both exactly 3600 secs i.e. exactly 1 hour. </p><p>So in my clock logic, I can decide to move the second hand forward if <i>fwd_duration < 25202</i>, and go in reverse if <i>fwd_duration >= 25202</i>.</p><p>The logic to calculate the <i>fwd_duration</i> between 2 times <i>(hh1:mm1:ss1)</i> and <i>(hh2:mm1:ss2)</i> is:</p>
<!--HTML generated using hilite.me--><div style="background: rgb(240, 243, 243); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;">d1 <span style="color: #555555;">=</span> (hh1<span style="color: #555555;">*</span><span style="color: #ff6600;">3600</span>) <span style="color: #555555;">+</span> (mm1<span style="color: #555555;">*</span><span style="color: #ff6600;">60</span>) <span style="color: #555555;">+</span> ss1;
d2 <span style="color: #555555;">=</span> (hh2<span style="color: #555555;">*</span><span style="color: #ff6600;">3600</span>) <span style="color: #555555;">+</span> (mm2<span style="color: #555555;">*</span><span style="color: #ff6600;">60</span>) <span style="color: #555555;">+</span> ss2;
fwd_duration <span style="color: #555555;">=</span> d2 <span style="color: #555555;">-</span> d1;</pre><pre style="line-height: 125%; margin: 0px;"><span style="color: #006699; font-weight: bold;">if</span> fwd_duration <span style="color: #555555;"><</span> <span style="color: #ff6600;">0</span>: fwd_duration <span style="color: #555555;">=</span> (<span style="color: #ff6600;">12</span><span style="color: #555555;">*</span><span style="color: #ff6600;">60</span><span style="color: #555555;">*</span><span style="color: #ff6600;">60</span>) <span style="color: #555555;">+</span> fwd_duration;
</pre></div>
<p>However, as the ULP is 16-bit, signed number range between -32767 and 32768, so the above operation is out of range (12*60*60 = 43200), unless we cook up some 32-bit integer math code.</p><p>Another way is decompose everything into even simpler operations:</p><!--HTML generated using hilite.me--><div style="background: rgb(240, 243, 243); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #006699; font-weight: bold;">def</span> <span style="color: #cc00ff;">time_diff</span>(hh1, mm1, ss1, hh2, mm2, ss2):
ss3 <span style="color: #555555;">=</span> ss2 <span style="color: #555555;">-</span> ss1;
<span style="color: #006699; font-weight: bold;">if</span> ss3 <span style="color: #555555;"><</span> <span style="color: #ff6600;">0</span>:
ss3 <span style="color: #555555;">=</span> ss3 <span style="color: #555555;">+</span> <span style="color: #ff6600;">60</span>
mm1 <span style="color: #555555;">+=</span> <span style="color: #ff6600;">1</span>
<span style="color: #006699; font-weight: bold;">if</span> mm1 <span style="color: #555555;">==</span> <span style="color: #ff6600;">60</span>:
mm1 <span style="color: #555555;">=</span> <span style="color: #ff6600;">0</span>
hh1 <span style="color: #555555;">+=</span> <span style="color: #ff6600;">1</span>
mm3 <span style="color: #555555;">=</span> mm2 <span style="color: #555555;">-</span> mm1
<span style="color: #006699; font-weight: bold;">if</span> mm3 <span style="color: #555555;"><</span> <span style="color: #ff6600;">0</span>:
mm3 <span style="color: #555555;">=</span> mm3 <span style="color: #555555;">+</span> <span style="color: #ff6600;">60</span>
hh1 <span style="color: #555555;">+=</span> <span style="color: #ff6600;">1</span>
<span style="color: #006699; font-weight: bold;">if</span> hh1 <span style="color: #555555;">>=</span> <span style="color: #ff6600;">12</span>: hh1 <span style="color: #555555;">-=</span> <span style="color: #ff6600;">12</span>
hh3 <span style="color: #555555;">=</span> hh2 <span style="color: #555555;">-</span> hh1
<span style="color: #006699; font-weight: bold;">if</span> hh3 <span style="color: #555555;"><</span> <span style="color: #ff6600;">0</span>: hh3 <span style="color: #555555;">=</span> hh3 <span style="color: #555555;">+</span> <span style="color: #ff6600;">12</span>
<span style="color: #006699; font-weight: bold;">return</span> [hh3, mm3, ss3]
<span style="color: #006699; font-weight: bold;">def</span> <span style="color: #cc00ff;">time_less_than</span>(hh1, mm1, ss1, hh2, mm2, ss2):
<span style="color: #006699; font-weight: bold;">if</span> hh1 <span style="color: #555555;"><</span> hh2: <span style="color: #006699; font-weight: bold;">return</span> <span style="color: #006699; font-weight: bold;">True</span>
<span style="color: #006699; font-weight: bold;">if</span> hh1 <span style="color: #555555;">></span> hh2: <span style="color: #006699; font-weight: bold;">return</span> <span style="color: #006699; font-weight: bold;">False</span>
<span style="color: #006699; font-weight: bold;">if</span> mm2 <span style="color: #555555;"><</span> mm2: <span style="color: #006699; font-weight: bold;">return</span> <span style="color: #006699; font-weight: bold;">True</span>
<span style="color: #006699; font-weight: bold;">if</span> mm1 <span style="color: #555555;">></span> mm2: <span style="color: #006699; font-weight: bold;">return</span> <span style="color: #006699; font-weight: bold;">False</span>
<span style="color: #006699; font-weight: bold;">return</span> ss1 <span style="color: #555555;"><</span> ss2
<span style="color: #336666;">print</span>(time_diff(<span style="color: #ff6600;">11</span>, <span style="color: #ff6600;">55</span>, <span style="color: #ff6600;">10</span>, <span style="color: #ff6600;">0</span>, <span style="color: #ff6600;">10</span>, <span style="color: #ff6600;">20</span>))
<span style="color: #336666;">print</span>(time_less_than(<span style="color: #ff6600;">7</span>, <span style="color: #ff6600;">0</span>, <span style="color: #ff6600;">2</span>, <span style="color: #ff6600;">7</span>, <span style="color: #ff6600;">0</span>, <span style="color: #ff6600;">2</span>))
</pre></div>
<p><i>time_diff()</i> is able to compute <i>fwd_duration</i> in <i>[hh, mm, ss]</i> format.</p><p><i>time_less_than()</i> tells you whether one duration given in <i>[hh, mm, ss]</i> format is less than another. Our previous threshold of 25202 secs is <i>[7, 0, 2]</i> in <i>[hh, mm, ss]</i> format.</p><p><a href="https://www.randseq.org/search/label/espclock">ESPCLOCK1</a> / <a href="https://www.randseq.org/search/label/espclock2">ESPCLOCK2</a> / <a href="https://www.randseq.org/search/label/espclock3">ESPCLOCK3</a> / <a href="https://www.randseq.org/search/label/espclock4">ESPCLOCK4</a></p>
Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com0tag:blogger.com,1999:blog-5552255504869253075.post-39281709203296258122021-08-01T23:15:00.008-07:002022-04-13T19:16:14.386-07:00ESPCLOCK4 - Dealing with low or removed batteries<p>One of the requirement of the ESPCLOCK project is to deal with persisting the clock time when the supply batteries run low, or when they are removed for battery change. For previous iterations of the project, a 0.47F supercap was attached to the ATtiny85 to keep it powered it long enough during a cut-off to write the clock time to flash memory.</p><p>For the ESP32, I found out it is impossible to power the main processor with the supercap, even if WiFi is not activated. It is just too power hungry! However, it is able to keep the ULP powered for 5 to 6 minutes. I found this out by getting the ULP to toggle an output pin and monitoring the output with a logic analyzer. The 0.47F supercap is placed across the 3.3V and GND pins. Power is supplied via the 5V pin. When the supply is pulled, the ULP continues to produce output for a further 5 to 6 minutes.</p><p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgd4B5zCcUm1ARoZU5BFoCxJI5mS2ytYb945f9PwOFEJQeSz3d4iGX6PMFeJW43diWNhbb7wSpxTydH-BJ8knaD015B5mUBBNNv1FNWbtLrjAhV3uzD-p4WepdhYeqdURDN2mJsD95XNtA/s1651/supercap.png" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="262" data-original-width="1651" height="102" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgd4B5zCcUm1ARoZU5BFoCxJI5mS2ytYb945f9PwOFEJQeSz3d4iGX6PMFeJW43diWNhbb7wSpxTydH-BJ8knaD015B5mUBBNNv1FNWbtLrjAhV3uzD-p4WepdhYeqdURDN2mJsD95XNtA/w640-h102/supercap.png" width="640" /></a></p>So the strategy I will adopt is this. The ULP will measure the supply voltage via a voltage divider.<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjULMFw6T63eftScALsGZ5o45ruGwmY0GtxK_cBk4w2_zag1AGWonjz9TK7YrUlDzQRfk2A0Vbv-ckmKWROsjV01AMIFQCdyFbFVeABnOJCvito4cKBck12SYHKsoRdfe6DojJNxXKHRok/s605/espclock4.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="605" data-original-width="459" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjULMFw6T63eftScALsGZ5o45ruGwmY0GtxK_cBk4w2_zag1AGWonjz9TK7YrUlDzQRfk2A0Vbv-ckmKWROsjV01AMIFQCdyFbFVeABnOJCvito4cKBck12SYHKsoRdfe6DojJNxXKHRok/s320/espclock4.png" width="243" /></a></div>When the supply voltage drops below a certain threshold (eg. 4.2V for 4xAA), it will stop everything. The residual power in the batteries will keep the ULP and values stored in RTC slow memory running for a very long time, until the batteries are replaced.<div><br /></div><div>When the batteries are taken out for replacement, the supercap will keep the ULP and RTC slow memory intact for 5 to 6 minutes. This should give more than sufficient time for a battery change. Of course, if for whatever reason the batteries are absent for longer than this interval, the unit will have to be reconfigured by doing a factory reset.<br /><div><p><a href="https://www.randseq.org/search/label/espclock">ESPCLOCK1</a> / <a href="https://www.randseq.org/search/label/espclock2">ESPCLOCK2</a> / <a href="https://www.randseq.org/search/label/espclock3">ESPCLOCK3</a> / <a href="https://www.randseq.org/search/label/espclock4">ESPCLOCK4</a></p></div></div>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com0tag:blogger.com,1999:blog-5552255504869253075.post-17848847094060706202021-08-01T05:53:00.007-07:002022-04-13T19:16:04.836-07:00Analog clock torture test<p>After playing around with things a bit, I find that I am able to run the clock forward at 8x speed (125ms/tick), but only able to do so reliably in reverse at 4x speed (250ms/tick).</p><p>So here is the "torture test" I have devised. It fastforwards the clock for 60 ticks, then reverse the direction for another 60 ticks. At the end of it, the second hand should return to the original position. By running this in a loop for many hours, I can confirm that there is no slippage for a given set of parameters.</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="266" src="https://www.youtube.com/embed/Zlb1Gvbz1xA" width="320" youtube-src-id="Zlb1Gvbz1xA"></iframe></div>The parameters for this particular clock (another $2 clock I picked up from KMart after I accidentally stepped on and broke the Ikea one) are:<div><ul style="text-align: left;"><li>Forward : 32ms pulse (9ms on, 1ms off)</li><li>Reverse : 12ms pulse, 12ms wait, 28ms pulse</li></ul><p>The high-level code to implement the above looks like this:</p></div><!--HTML generated using hilite.me--><div style="background: rgb(240, 243, 243); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #007788; font-weight: bold;">void</span> <span style="color: #cc00ff;">forward_tick</span>() {
<span style="color: #006699; font-weight: bold;">for</span> (<span style="color: #007788; font-weight: bold;">int</span> i<span style="color: #555555;">=</span><span style="color: #ff6600;">0</span>; i<span style="color: #555555;"><</span><span style="color: #ff6600;">32</span>; i<span style="color: #555555;">++</span>) {
digitalWrite(tickpin, HIGH); delayMicroseconds(<span style="color: #ff6600;">900</span>);
digitalWrite(tickpin, LOW); delayMicroseconds(<span style="color: #ff6600;">100</span>);<br /> }
tickpin <span style="color: #555555;">=</span> (tickpin <span style="color: #555555;">==</span> <span style="color: #ff6600;">25</span> <span style="color: #555555;">?</span> <span style="color: #ff6600;">27</span> <span style="color: #555555;">:</span> <span style="color: #ff6600;">25</span>);
}
<span style="color: #007788; font-weight: bold;">void</span> <span style="color: #cc00ff;">reverse_tick</span>() {
digitalWrite(tickpin, HIGH); delay(<span style="color: #ff6600;">12</span>);
digitalWrite(tickpin, LOW); delay(<span style="color: #ff6600;">12</span>);
tickpin <span style="color: #555555;">=</span> (tickpin <span style="color: #555555;">==</span> <span style="color: #ff6600;">25</span> <span style="color: #555555;">?</span> <span style="color: #ff6600;">27</span> <span style="color: #555555;">:</span> <span style="color: #ff6600;">25</span>);
digitalWrite(tickpin, HIGH); delay(<span style="color: #ff6600;">28</span>);</pre><pre style="line-height: 125%; margin: 0px;"> digitalWrite(tickpin, LOW);
}
</pre></div><p>It is actually OK for the reverse tick to run slower because if you are trying to catch up to a certain time by running in reverse, the clock is still ticking ahead. So say if it's 3am now and I am trying to reverse back to 2am, that's 60 minutes worth of path to backtrace. At 4x speed, it will take me 60/4 = 15 minutes to do so. But in that 15 minutes, the clock has also moved ahead 15 minutes, so that's actually more like 45 minutes worth of time to backtrace.</p><p>Whereas if it's 2am and I am trying to fastforward to 3pm, again that's 60 minutes worth of time to traverse. At 8x speed, it will take me 60/8 = 7.5 minutes. But in that time, the clock has also moved ahead 7.5 minutes, so it will take me more than 7.5 minutes to catch up with the clock.</p><p>I need to come out with a function to calculate given the current and target time, how much time it will take me to fastforward or reverse to the desired position. Then it will provide an objective measure for the optimal route to take when the clock needs to catch up with the actual time.</p><p><a href="https://www.randseq.org/search/label/espclock">ESPCLOCK1</a> / <a href="https://www.randseq.org/search/label/espclock2">ESPCLOCK2</a> / <a href="https://www.randseq.org/search/label/espclock3">ESPCLOCK3</a> / <a href="https://www.randseq.org/search/label/espclock4">ESPCLOCK4</a></p>
Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com3tag:blogger.com,1999:blog-5552255504869253075.post-18155729649215053652021-07-30T23:17:00.005-07:002022-04-13T19:15:56.351-07:00Running an analog clock backwards<p>I didn't think it was possible, but recently I came across this YouTube video and its <a href="http://radiopench.blog96.fc2.com/blog-entry-927.html?utm_source=pocket_mylist" target="_blank">associated blog post</a> (in Japanese, which I was able to understand thanks to Google Translate):</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="266" src="https://www.youtube.com/embed/bdnXLv5za-k" width="320" youtube-src-id="bdnXLv5za-k"></iframe></div><p>Here's a diagram to contrast the pulses sent to the clock lines to turn the clock backwards, versus driving it forward:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEheqlrDZxysYN5ew34U-_S5Jo0NE7zHXjHB0hEEWnbZuek9YaVX_X4d_pT1J5HhW1GFD1jAXRJuaSzPKoJ-bP4QzDy-w7ppVkMVk3G3nAvzrKIP6fYamXhh36EyT-eodSQgAFwcvyOTN3Q/s675/forward-and-reverse-pulses.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="675" data-original-width="597" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEheqlrDZxysYN5ew34U-_S5Jo0NE7zHXjHB0hEEWnbZuek9YaVX_X4d_pT1J5HhW1GFD1jAXRJuaSzPKoJ-bP4QzDy-w7ppVkMVk3G3nAvzrKIP6fYamXhh36EyT-eodSQgAFwcvyOTN3Q/w566-h640/forward-and-reverse-pulses.png" width="566" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div><div>As you can see, during the first time step, instead of sending a single positive or negative pulse, you send a short pulse, wait a little, then send a longer pulse in the opposite direction. Then in the next time step, a mirror image of the pulses in the previous time step is sent. The duration of the pulses has to be experimentally determined for different clock motions.</div></div><div><br /></div><div>For example, I was able to use this follow code to move the second hand on my clock in a reverse motion reliably:</div><div><br /></div><div><!--HTML generated using hilite.me--><div style="background: rgb(240, 243, 243); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #007788; font-weight: bold;">int</span> tickpin <span style="color: #555555;">=</span> <span style="color: #ff6600;">25</span>;
<span style="color: #007788; font-weight: bold;">void</span> <span style="color: #cc00ff;">rtick</span>() {
digitalWrite(tickpin, HIGH); delay(<span style="color: #ff6600;">10</span>);
digitalWrite(tickpin, LOW); delay(<span style="color: #ff6600;">10</span>);
tickpin <span style="color: #555555;">=</span> (tickpin <span style="color: #555555;">==</span> <span style="color: #ff6600;">25</span> <span style="color: #555555;">?</span> <span style="color: #ff6600;">27</span> <span style="color: #555555;">:</span> <span style="color: #ff6600;">25</span>);
digitalWrite(tickpin, HIGH); delay(<span style="color: #ff6600;">30</span>);
digitalWrite(tickpin, LOW);
}
</pre></div>
</div><div><br /></div><div>So the short pulse is 10ms, the short wait is also 10ms. The longer pulse is 30ms.</div><div><br /></div><div>Here is a video of the clock in reverse motion:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="266" src="https://www.youtube.com/embed/EiReQkM-uaI" width="320" youtube-src-id="EiReQkM-uaI"></iframe></div><div><br /></div><div>I was able to reverse-drive the clock reliably up to 4x speed using the code above. I pause the second hand after every 60 ticks, and noted where the stoppage point is. Then I let the code run overnight and came back in the morning to check that the second hand is still stopping at the same place.</div><div><br /></div><div>But once I tried increasing the speed to 5x or 8x, slippages started to occur.</div><div><br /></div><div><a href="https://www.randseq.org/search/label/espclock">ESPCLOCK1</a> / <a href="https://www.randseq.org/search/label/espclock2">ESPCLOCK2</a> / <a href="https://www.randseq.org/search/label/espclock3">ESPCLOCK3</a> / <a href="https://www.randseq.org/search/label/espclock4">ESPCLOCK4</a><br /><p><br /></p></div>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com0tag:blogger.com,1999:blog-5552255504869253075.post-84325415485772480052021-07-17T04:14:00.002-07:002021-07-17T04:15:36.228-07:00Portable OBDII Memory Saver<p>Tired of losing ECU and radio memory when I replace the car battery myself, I followed the instructions in this video and made my own ODBII memory saver.</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="266" src="https://www.youtube.com/embed/i24x_vqNTqc" width="320" youtube-src-id="i24x_vqNTqc"></iframe></div><div><br /></div><div>The gadget is extremely simple to make. I ordered the ODBII plug and A23 battery holder. The diode could be any small signal diode that you have around the toolbox (I am using 1N4148).</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8vsSODILu0LqvtQXAoBp0biZjL_c_B6CPUAUNh4_NXuud3d4i8viVhgK38JdckKIv1i3NNW3EAWmFtlWfnAOJUIwDeOq4J4lYZYx_ECDynSf5iKaCt7npwYgAfUtXVK88QutyYWuG-3w/s2048/PXL_20210717_101938460.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="2048" data-original-width="1536" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8vsSODILu0LqvtQXAoBp0biZjL_c_B6CPUAUNh4_NXuud3d4i8viVhgK38JdckKIv1i3NNW3EAWmFtlWfnAOJUIwDeOq4J4lYZYx_ECDynSf5iKaCt7npwYgAfUtXVK88QutyYWuG-3w/w480-h640/PXL_20210717_101938460.jpg" width="480" /></a></div><br /><div>Then simply solder:</div><div><ul style="text-align: left;"><li>Battery holder GND => pins 4, 5 on OBDII</li><li>Battery holder VCC => diode (+)</li><li>Diode (-) => pin 16 on OBDII</li></ul></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9dMPsgzc9P877m6FQLQZzVSnAf812WRqNgh8bV8FukaPYy9zyzMfO4fN4zsqDxd0mevY2nzSOaCLVQScypEu-k3PpjobdWvWSDlLO1bHAHSPxz_r_FyaqMSChwluYQHCRJsqHNSfMT8E/s2048/PXL_20210717_102541952.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1536" data-original-width="2048" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9dMPsgzc9P877m6FQLQZzVSnAf812WRqNgh8bV8FukaPYy9zyzMfO4fN4zsqDxd0mevY2nzSOaCLVQScypEu-k3PpjobdWvWSDlLO1bHAHSPxz_r_FyaqMSChwluYQHCRJsqHNSfMT8E/w640-h480/PXL_20210717_102541952.jpg" width="640" /></a></div><p>That's it. Stuff the battery holder into the empty part of the shell and reassemble. I didn't even bother to screw the shell together.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifx_inRgLsCvUREogLsufAYyx-h0o0M8eKoY1APqalKlETt0HeUadLUOQj-T5J36yS_n5ShW1Silafovkw6Cteti07J814j_FcYa-9Cn0qhYleqMJXcMN0MZDYy2ZSnqqmEsMpYTuDucM/s2048/PXL_20210717_102829021.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1536" data-original-width="2048" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifx_inRgLsCvUREogLsufAYyx-h0o0M8eKoY1APqalKlETt0HeUadLUOQj-T5J36yS_n5ShW1Silafovkw6Cteti07J814j_FcYa-9Cn0qhYleqMJXcMN0MZDYy2ZSnqqmEsMpYTuDucM/w640-h480/PXL_20210717_102829021.jpg" width="640" /></a></div><div><br /></div>You can check by inserting a battery and measuring the voltage across pin 4/5 and pin 16. It should be around 12.6V (for a fresh A23 battery) - 0.4V ~= 12.2V. Anything above 11V should work well enough to keep the ECU and radio memory while inserted.</div><div><br /></div><div>To use, first insert the memory saver into the OBDII port in the car. Then remove the car battery and perform the battery replacement. Finally remove the memory saver.<br /><p><br /></p></div>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com0tag:blogger.com,1999:blog-5552255504869253075.post-53439780106025609502021-06-26T21:37:00.003-07:002021-06-27T04:32:04.190-07:00ESP32 D1 Mini with CH9102X UART<p>I noticed recently that there is a ESP32 D1 Mini board with a CH9102X UART, so I bought one to try.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgakQ35O_y3mMtx5SxeP6j8YvpkKnoc5bKU6CjLdQeWgLrAyytIpUA8lartSOOhipt_TvUm0OYNnUc9_ua5vjQKTeoJIbL1-txoKkDU4Ij_LS8FQS5uk0-ULrAk1alSrtpU962Yc0qudjk/s1121/CH9102X.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="347" data-original-width="1121" height="198" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgakQ35O_y3mMtx5SxeP6j8YvpkKnoc5bKU6CjLdQeWgLrAyytIpUA8lartSOOhipt_TvUm0OYNnUc9_ua5vjQKTeoJIbL1-txoKkDU4Ij_LS8FQS5uk0-ULrAk1alSrtpU962Yc0qudjk/w640-h198/CH9102X.jpg" width="640" /></a></div><br /><p>The <a href="http://www.wch-ic.com/downloads/file/297.html" target="_blank">CH9102X</a> appears to be a Chinese clone of the <a href="https://www.silabs.com/documents/public/data-sheets/CP2102-9.pdf" target="_blank">CP2102</a>.</p><p>Unfortunately, I couldn't get it to work. Connected via USB, and at a baudrate of 115200bps, I could only successfully upload a program 2/10 times. Other times, I get the "<i>Failed to connect to ESP32: Timed out waiting for packet header</i>".</p><p>I could program it without issue by plugging it into the FTDI programmer. It could deal with an upload speed of 912600bps 100% of the time.</p><p>I found <a href="https://randomnerdtutorials.com/solved-failed-to-connect-to-esp32-timed-out-waiting-for-packet-header/" target="_blank">this webpage</a> that talks about fixing the timeout error by adding a small capacitor between <b>EN</b> and <b>GND</b>, but I don't really have the need to try it because I am going to remove the UART module anyway for the ESPCLOCK4 project.</p><p>Anyway, I will just stick with the CP2104 UART for future purchases.</p><p><i>Update: I found that I could make it work by connecting GPIO0 to GND, and setting an upload speed of no greater than 115200. Terrible.</i></p>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com0tag:blogger.com,1999:blog-5552255504869253075.post-29837460446235418852021-06-17T00:16:00.028-07:002022-04-13T19:15:49.563-07:00ESPCLOCK4 - Reduce operating current by removing UART module<p>The ESP32 Mini uses a <a href="https://www.silabs.com/documents/public/data-sheets/cp2104.pdf" target="_blank">CP2104 UART module</a> to drive the USB port. The CP2104 is configured in self-powered mode, which means that even if nothing is plugged into the USB port and the board is powered by a 3.3V source, it is still drawing ~100uA of current. I wanted to see the effect of removing this module on the power consumption of my test circuit.</p><p>Removing the UART module means losing access to the USB port. However, I should still be able to program it using a standard FTDI programmer, which I wanted to verify. So I took out the same FTDI programmer that was used for programming the ESP8266, and built a custom holder for the ESP32 Mini.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMFWQ4SqkewUSaz4ZYfwJoKmv0KE_wz__LpNb1QPDZ8SGJS12lOH4nl0J92pIP7EKpGoR2M0E-c4yvfEoR4KFTqEhQKUyWurz11BFbqvwxhv5PaVLt56TS37xYQQAEmt9-p8Wv7KEvA9k/s1600/esp32a.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1200" data-original-width="1600" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMFWQ4SqkewUSaz4ZYfwJoKmv0KE_wz__LpNb1QPDZ8SGJS12lOH4nl0J92pIP7EKpGoR2M0E-c4yvfEoR4KFTqEhQKUyWurz11BFbqvwxhv5PaVLt56TS37xYQQAEmt9-p8Wv7KEvA9k/w400-h300/esp32a.jpg" width="400" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCQc5cVa5RQ6NFFrbhavyE2gv44adWFdG1SwzIB3rxHJAoF7EhEyDAW0c-vFI0ZW7y1lMviKaf1vbwrdIJIsRvbW6Lltl9zk6YGHsoYyjlFtyHsQAojzDP1fkvv7b6IR4-LmMq2ZBPT9U/s1600/esp32b.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1200" data-original-width="1600" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCQc5cVa5RQ6NFFrbhavyE2gv44adWFdG1SwzIB3rxHJAoF7EhEyDAW0c-vFI0ZW7y1lMviKaf1vbwrdIJIsRvbW6Lltl9zk6YGHsoYyjlFtyHsQAojzDP1fkvv7b6IR4-LmMq2ZBPT9U/w400-h300/esp32b.jpg" width="400" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEid8W-eiw3DXNbsIym19-2BmEiokMP2DVNGMIh30XFRz0hVSf1CN6DTFZwY6FVghgI3Byqk9EYrWlc8G7d8FjmbWHwJ5I6DOiUaaN-ea90nEXuddNU4SXI8aRcqeTJtlXlXv4RPYqnmGVw/s1600/esp32c.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1200" data-original-width="1600" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEid8W-eiw3DXNbsIym19-2BmEiokMP2DVNGMIh30XFRz0hVSf1CN6DTFZwY6FVghgI3Byqk9EYrWlc8G7d8FjmbWHwJ5I6DOiUaaN-ea90nEXuddNU4SXI8aRcqeTJtlXlXv4RPYqnmGVw/w400-h300/esp32c.jpg" width="400" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: left;">I started with the same connections as used with the ESP8266:</div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both;"><ul style="text-align: left;"><li>ESP32 VIN <-> FTDI 3.3V</li><li>ESP32 GND <-> FTDI GND</li><li>ESP32 TX <-> FTDI RX</li><li>ESP32 RX <-> FTDI TX</li></ul></div></div><div class="separator" style="clear: both; text-align: left;">However, I could not get it to work. After some fiddling around, I discovered I could only get it to work by connecting RX - RX and TX - TX. Not sure why.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">But now I am confident I can still program the board without its USB, so I proceed to remove the CP2104 module (marked in red) with a hot air gun.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOvU6SX5sHwfiDJ7aRiNBKUGHW17YIzHNpdG0cM3OLewkgiJGGsEAoyYMetTQa2mbpWToF1yiPULwXG9Tv1yBoSPAajKevVaKYZ4mlmMf64Z8yItNp8T-23fQvQ6jXejhxRsn21gBk0sk/s1920/esp32-2.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1440" data-original-width="1920" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOvU6SX5sHwfiDJ7aRiNBKUGHW17YIzHNpdG0cM3OLewkgiJGGsEAoyYMetTQa2mbpWToF1yiPULwXG9Tv1yBoSPAajKevVaKYZ4mlmMf64Z8yItNp8T-23fQvQ6jXejhxRsn21gBk0sk/w640-h480/esp32-2.jpg" width="640" /></a></div><br /><div class="separator" style="clear: both; text-align: left;">After removing the UART module, I verified that the board was still working by uploading the code <a href="https://www.randseq.org/2021/03/espclock4-rethink-on-tick-pulse.html" target="_blank">here</a>, and plugging it into the test circuit. Sure enough, the clock started ticking. It worked!</div><br /></div>I then tried measuring the current consumption using the <a href="https://www.randseq.org/2018/10/coulomb-counting-ltc4150-esp-12f.html" target="_blank">LTC4150</a>. Unfortunately, the current seems too low to be measured. After doing some research, I decided to swap out the 0.05ohm sense resistor with a 1ohm resistor. Because I didn't have a 1% part, I simply used a normal 5% metal film resistor. However, I did calibrate it against a multimeter with a 10kohm resistor over a 3.3V power source. I found the measurement to be within ~20uA tolerance.<div><br /></div><div><i><span style="color: #cc0000;"><b>Note:</b> with such small current, the power used by the LTC4150 itself becomes very important. From the datasheet, at 2.7V, its typical power usage is 80uA, with a max of 100uA. With a 10Kohm passive load, I got a reading of 430uA. Substracting 90uA from the reading gives me 340uA, which compares well with the multimeter reading of 326uA.</span></i></div><div><span style="color: #cc0000;"><br /></span></div><div><i><span style="color: #cc0000;">Using a 1ohm sense resistor means I can no longer use WiFi in my test code. The 500mA current drawn will cause the board to brown out and reset.</span></i></div><div><br /></div><div>So finally, with the UART module removed, the current draw is 650uA - 90uA = 560uA, <b>or ~0.6mA</b>. This is a hugely improvement over the <a href="https://www.randseq.org/2021/03/espclock4-rethink-on-tick-pulse.html" target="_blank">1.13mA that I measured previously</a> with the UART module intact.</div><div><br /></div><div>So it looks like I am going to go ahead and create the full ESPCLOCK code with this setup.</div><div><br /></div><div><a href="https://www.randseq.org/search/label/espclock">ESPCLOCK1</a> / <a href="https://www.randseq.org/search/label/espclock2">ESPCLOCK2</a> / <a href="https://www.randseq.org/search/label/espclock3">ESPCLOCK3</a> / <a href="https://www.randseq.org/search/label/espclock4">ESPCLOCK4</a></div>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com0tag:blogger.com,1999:blog-5552255504869253075.post-49203709508642354222021-06-16T20:07:00.004-07:002021-06-17T03:14:00.089-07:00Arduino control of PTX4 remote<p>I wanted to control the opening and closing of my front gate using the ESP8266. The gate uses a standard PTX4 remote, which is readily available at many hardware stores.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFJf_kYiczBn4LpK_ghPEw8YB39-i3jFf-BRBvNEn3O48wWebFqBlHiRqM_9yZu1-HqDie-TGS0HqW1lpw-oTYSrjIR41QbgReMIg-Kh5eFkkiDPpcGGehBusB3RqddoR36I0JeO-_d-g/s2048/PXL_20210617_014259715.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="2048" data-original-width="1536" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFJf_kYiczBn4LpK_ghPEw8YB39-i3jFf-BRBvNEn3O48wWebFqBlHiRqM_9yZu1-HqDie-TGS0HqW1lpw-oTYSrjIR41QbgReMIg-Kh5eFkkiDPpcGGehBusB3RqddoR36I0JeO-_d-g/s320/PXL_20210617_014259715.jpg" /></a></div><p>To my surprise, I couldn't find a hardware module that I can buy off-the-shelf and interface with the ESP8266. So I bought an extra remote, paired it with the gate receiver and proceeded to take it apart to see how I can use the circuitry inside.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOf-3Nf3ywc8Ocf8q1JetPOrrTkz_4kn2QA4aDisF1-pY5Z9Jcq3d2idLqgZxxPAL_w3Od-XRRlE06NLDSl_SboMUV2-X1YvJ96AP_TOVWdnV5LPtU6mbMTmruCzHj-PF9vtq7stBk6Zg/s1530/remote.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1096" data-original-width="1530" height="458" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOf-3Nf3ywc8Ocf8q1JetPOrrTkz_4kn2QA4aDisF1-pY5Z9Jcq3d2idLqgZxxPAL_w3Od-XRRlE06NLDSl_SboMUV2-X1YvJ96AP_TOVWdnV5LPtU6mbMTmruCzHj-PF9vtq7stBk6Zg/w640-h458/remote.jpg" width="640" /></a></div><p>SW4 is the button I press to open/close the gate. Points (1) and (2) are the junctions I need to short using a relay to simulate a button press. I measured point (1) to be 12V, so I need a 12V relay for this to work. The one that's readily available to me is <a href="https://www.jaycar.com.au/12v-spst-dil-reed-relay/p/SY4032" target="_blank">SY4032</a>. Since the operating voltage is 12V, I also need to use a MOSFET (the good ol' 2N7000) to drive the relay.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdWQ2eUKCp2a-lfIet9RNZsJ4dcW86uDSNOOEv53kcfIbqSp5cyZeM4lXIE67RjFy-SII2VtkiJjRMsMZx6lVHjC_TFUgDOPEtwIa3vcnGYoXfav5lvCufdToGvVumiInWv2SgB0ziZS8/s1000/circuit.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="649" data-original-width="1000" height="416" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdWQ2eUKCp2a-lfIet9RNZsJ4dcW86uDSNOOEv53kcfIbqSp5cyZeM4lXIE67RjFy-SII2VtkiJjRMsMZx6lVHjC_TFUgDOPEtwIa3vcnGYoXfav5lvCufdToGvVumiInWv2SgB0ziZS8/w640-h416/circuit.png" width="640" /></a></div><p>The gate pin needs to be grounded with a resistor, otherwise a spurious signal might be sent to the relay when the MCU is first powered on.</p><p>Another thing I did was to get rid of the A23 battery by connecting a DC booster (<a href="https://www.addicore.com/MT3608-Boost-Converter-p/ad300.htm" target="_blank">MT3608</a>) to the 5V output of the <a href="https://www.wemos.cc/en/latest/d1/d1_mini.html">WEMOS D1 Mini</a> and calibrating it to output 12V. So now the entire circuit can be driven by the USB input to the WEMOS D1 Mini without the need for an extra battery.</p><p><br /></p>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com0tag:blogger.com,1999:blog-5552255504869253075.post-76295387308693243952021-06-14T00:29:00.000-07:002021-06-14T00:29:38.614-07:00Link dump: Auto-ranging current measuring instrument for IoT devices<p>With more research, I am beginning to understand more about burden voltage, the difficulty of measuring the dynamic power draw of a circuit with a wide range of current consumption (a few uA all the way to hundreds of mA), as well as the limitations of the LTC4150 coulomb counter in dealing with this.</p><p>Here are a list of devices that are designed to do just that, and that are not too expensive (<$200). I hope to acquire one of them to tinker with in the future:</p><p></p><ul style="text-align: left;"><li><a href="https://www.nordicsemi.com/Software-and-tools/Development-Tools/Power-Profiler-Kit-2" target="_blank">Power Profiler Kit 2</a></li><li><a href="https://rocketlogger.ethz.ch/" target="_blank">RocketLogger</a></li><li><a href="https://bluebird-labs.com/products/" target="_blank">BattLab-One</a></li><li><a href="mikroAmpMeter">mikroAmpMeter</a></li></ul><div><br /></div><p></p>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com0tag:blogger.com,1999:blog-5552255504869253075.post-11598670261352148012021-06-13T23:06:00.004-07:002021-12-03T14:43:12.760-08:00Update: Line adapter for Ozito Blade Trimmer<p><i><b>Update (Dec 2021):</b> If you access to a 3D printer, I would now recommend <a href="https://www.thingiverse.com/make:970480" target="_blank">this solution</a>, which makes it super easy to replace the trimmer line. I have been using it for a few months now with zero issue.</i></p><span><a name='more'></a></span><p>An update on using ordinary trimmer line for the Ozito battery trimmer. I finally found a super easy and robust way to do so. No need for any <a href="https://www.randseq.org/2019/09/line-adapter-for-ozito-blade-trimmer.html">3D-printed adaptor</a>, which is simply not strong enough to withstand the amount of force we are dealing with. You should be able to start immediately with common tools that you already have.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBUewtQbiV4bTlz0gpf1vs3USW3MU0fw_vpsXx2hy13Mext5R7-a-xG_v4tNN9tuBQYCByWxb4MR7WLv-QnGaaXjCv5P4HkffmgmGZdCgwNqm2zTsi6ZYSv7OpsbEZwGzvbKUuHPpRQq8/s500/68873d33-e9a0-4b8b-944e-7c270577ccde.jpeg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="500" data-original-width="500" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBUewtQbiV4bTlz0gpf1vs3USW3MU0fw_vpsXx2hy13Mext5R7-a-xG_v4tNN9tuBQYCByWxb4MR7WLv-QnGaaXjCv5P4HkffmgmGZdCgwNqm2zTsi6ZYSv7OpsbEZwGzvbKUuHPpRQq8/w640-h640/68873d33-e9a0-4b8b-944e-7c270577ccde.jpeg" width="640" /></a></div><p>These are all the material/tools you need:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNVYFB-PBKk8bxf-z2Ppm5RT3jWuHhFX4YHW_tW24nEm6XVT2arnFp5BszGhtkUdCmNrhcN4T3RzIdH8GuFecMvVGbfVgqe9YfVvqqr8PmBPUXz0w1_D4MuDCXvM-l9IWPIBj0hAAPdB0/s2048/PXL_20210614_030314733.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1536" data-original-width="2048" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNVYFB-PBKk8bxf-z2Ppm5RT3jWuHhFX4YHW_tW24nEm6XVT2arnFp5BszGhtkUdCmNrhcN4T3RzIdH8GuFecMvVGbfVgqe9YfVvqqr8PmBPUXz0w1_D4MuDCXvM-l9IWPIBj0hAAPdB0/w640-h480/PXL_20210614_030314733.jpg" width="640" /></a></div><ul style="text-align: left;"><li>2.4mm trimmer line</li><li>0.75mm wire (just use any thin wire you have lying around)</li><li>flat nose plier</li><li>cutter (or scissors, to cut the trimmer line)</li></ul><div>Cut off a length of trimmer line about 18cm long and fold in the middle. Cut off a length of wire about 8cm long and wrap around the trimmer line near the top, creating a loop. The wrap should be tight enough to just stop the trimmer lines from sliding, but you should still be able to reduce the size of the loop by pulling on the ends of the line.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEuPY0370O7_g-yqFOmmyLHYQhC_PFdExPSfLgCHP6S9vgiY-_74GkhWivNg9FfbdY_EmrCXP-WuLJ9RF33WXy2-FgMUR0wo5DupsDNrHyp50r_ICzXPFkEpeQHWcr_CAI1nSjb9QnG5g/s2048/PXL_20210614_035453372.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1536" data-original-width="2048" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEuPY0370O7_g-yqFOmmyLHYQhC_PFdExPSfLgCHP6S9vgiY-_74GkhWivNg9FfbdY_EmrCXP-WuLJ9RF33WXy2-FgMUR0wo5DupsDNrHyp50r_ICzXPFkEpeQHWcr_CAI1nSjb9QnG5g/w640-h480/PXL_20210614_035453372.jpg" width="640" /></a></div><br /><div>The orientation of the wire wrap as shown in the photo above is important, because it prevents the wire from slipping off during operation. If you wrap the wire <i>around</i> the trimmer line, my experience is the wrap will slide off in operation no matter how tightly you think you have wrapped the wire due to the immense centrifugal force generated during operation.</div><div><br /></div><div>Now place the loop around the blade holder where the plastic blade usually goes, and pull the ends of the trimmer line, thus causing the loop to tighten around the blade holder.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjp68ejkl8XJR7PFyw50j2GbsKtxdkuwumJXlAnVerRxJYKeLj38oS4yhZK8XPWrmYjiU0UWVWhy9UjtnWC_Yy4zC8QN8i_lTjAGvE0AQWrI392wUs5hLobWeD4sa6t7upZ_RawcysCkrM/s2048/PXL_20210614_035558567.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="2048" data-original-width="1536" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjp68ejkl8XJR7PFyw50j2GbsKtxdkuwumJXlAnVerRxJYKeLj38oS4yhZK8XPWrmYjiU0UWVWhy9UjtnWC_Yy4zC8QN8i_lTjAGvE0AQWrI392wUs5hLobWeD4sa6t7upZ_RawcysCkrM/w480-h640/PXL_20210614_035558567.jpg" width="480" /></a></div><br /><div>Finally, tighten the wrap by giving the ends of the wire a few twists with the plier. Straighten the trimmer lines by pulling them towards the mud guard and trim off any excess. </div><div><br /></div><div><div><i>Note that there is no need to straighten the trimmer lines before use. During operation, the immense centrifugal force will do the job for you.</i></div><div><br /></div></div><div>Once you have done this once or twice, it takes only a minute to put a new length of trimmer wire on. In addition, unlike the plastic blade which randomly disconnects from its holder when it hits something hard and flies off, the trimmer line attached this way has always stayed on during my past year of use. Conclustion: it cuts way better than the plastic blade, and requires less frequent replacement.</div><div><br /></div><div>I have had this Ozito trimmer for over 5 years now, and it is still going strong. Over the past year, this new technique has giving it a new lease of life and makes it even easier to work with and cheaper to maintain!</div><p><i>PS: I have also tried using zip ties and found them to be unsuitable. 1) Trimmer lines are made with composite nylon, which makes them stronger and more resistant to abrasion. Zip ties simply don't have that kind of strength. 2) Thicker zip ties have about 5mm thickness, which does not fit into the blade holder. The thinner zip ties (eg. 2.5mm thickness) are simply to wimpy to be used for the task.</i></p>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com5tag:blogger.com,1999:blog-5552255504869253075.post-62340404547058161182021-03-18T21:38:00.005-07:002021-03-21T16:20:16.469-07:00RBX - Robotics Brick Extension<p><a href="https://rbx.randseq.org/index.php" target="_blank">RBX</a> is a project that I have been working on for some time now. It is a robotics kit designed for young tinkerers. It consists of a set of Lego-compatible "bricks" made with common components, such as LED, pushbutton, servo, motor etc. The brick housings are printed using a 3D printer. The components are hooked up to a microcontroller via standardized "ports". Programming is done via a variant of Javascript (a port of <a href="https://duktape.org/" target="_blank">Duktape</a>) on a browser-based IDE.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTsVqxmh5bK93_0tG4qDs3J2odj5ctfvqSL6w3wrzTf7qrd-STCmj6Hw8oDY8QWphDGRZzypct2cBwqP7_qC96f_7G-grD-oYxBR-xAvP8oT0VcLJfrg3keLHtFkg02Fk1txGgOHM-nVM/s926/rbx.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="782" data-original-width="926" height="540" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTsVqxmh5bK93_0tG4qDs3J2odj5ctfvqSL6w3wrzTf7qrd-STCmj6Hw8oDY8QWphDGRZzypct2cBwqP7_qC96f_7G-grD-oYxBR-xAvP8oT0VcLJfrg3keLHtFkg02Fk1txGgOHM-nVM/w640-h540/rbx.jpg" width="640" /></a></div><div><br /></div><div>I started the project because I found that block-based programming environment such as Lego Mindstorm are a little too simple for older kids (8 - 12 year old), yet the long compile-run cycle of Arduino is not suitable for tinkering and quick prototyping. Something that sits in the middle is needed.</div><div><br /></div><div>On the hardware side, one of the first obstacles for a child trying to break into microcontroller programming is hooking up the desired circuitry on a breadboard. Anything beyond the the introductory "Hello World" LED example requires a ton of wiring. If servos or motors are involved, a separate power rail is required, which makes things even more complicated.</div><div><br /></div><div>RBX uses various <a href="https://en.wikipedia.org/wiki/JST_connector" target="_blank">JST connectors</a> to connect to the microcontroller (which is based on the <a href="https://www.aliexpress.com/item/4000103411061.html">ESP32 dev board</a>). These are called "ports", which maps to the microcontroller pins. The ports come in 2, 3 and 4-pin variety, and each port has a unique identifier. </div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://rbx.randseq.org/images/rbx/rbx-mcu-assembled.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="600" data-original-width="800" height="240" src="https://rbx.randseq.org/images/rbx/rbx-mcu-assembled.jpg" width="320" /></a></div><br /><div>In this way, hooking up components becomes a plug-and-play affair. Since the connectors need to be matching and oriented correctly, this makes it less likely for components to be hooked up the wrong way.</div><div><br /></div><div>Programming is done via a browser-based IDE served directly from the microcontroller. Once the microcontroller is powered on, the user is able to connect via a web browser and start entering and running JavaScript code immediately.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://rbx.randseq.org/images/rbx/rbx-startup-06.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="485" data-original-width="800" height="389" src="https://rbx.randseq.org/images/rbx/rbx-startup-06.png" width="640" /></a></div><br /><div>The entire project can be found on <a href="https://github.com/victor-chew/rbx" target="_blank">Github</a>. It uses <a href="https://freecad.org/" target="_blank">FreeCAD</a> for designing the 3D housing, <a href="https://kicad.org/" target="_blank">KiCAD</a> for designing the microcontroller PCBs, and <a href="https://platformio.org/" target="_blank">Platformio</a> for coding the firmware.</div><div><br /></div><div><div>Project website: <a href="https://rbx.randseq.org/">https://rbx.randseq.org/</a></div><div>Showcase: <a href="https://rbx.randseq.org/tutorials">https://rbx.randseq.org/tutorials</a></div><div>GitHub repository: <a href="https://github.com/victor-chew/rbx">https://github.com/victor-chew/rbx</a></div></div><br />Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com0tag:blogger.com,1999:blog-5552255504869253075.post-69821139571648035612021-03-18T21:08:00.003-07:002022-04-13T19:15:41.050-07:00ESPCLOCK4 - Monitoring supply voltage via ADC<p>To monitor the battery pack voltage (4 x AA; max ~6V), I hooked up GPIO32 (maps to ADC1_CHANNEL_4) to a voltage divider consisting of 2 x 10K resistors. This divides the max voltage in half, giving us 3V, which is clear of the max 3.3V input voltage accepted by the ESP32 ADC pins.</p><p>To initialize the ADC channel so that it can be used by ULP code, this function is used:</p><!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #b00040;">void</span> <span style="color: blue;">init_adc</span>() {
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_DB_11);
adc1_ulp_enable();
}
</pre></div>
<p>I created 2 new RTC_SLOW_MEM variables:</p><!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: green; font-weight: bold;">enum</span> {
...
VDD_ADC, <span style="color: #408080; font-style: italic;">// Supply voltage measured by ADC</span>
VDD_LOW, <span style="color: #408080; font-style: italic;">// Low voltage threshold</span>
}
</pre></div>
<p>The ADC reading ranges from 0 to 4095 in a slightly non-linear mapping, so I use this code to figure out the right ADC value for the low voltage threshold for the battery pack (target: 1.05V x 4 = 4.2V).</p><!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #bc7a00;">#define SUPPLY_VLOW (1050*4) </span><span style="color: #408080; font-style: italic;">// 4 x 1.05V</span>
<span style="color: #408080; font-style: italic;">// Map given ADC value to corresponding voltage</span>
<span style="color: #b00040;">uint32_t</span> <span style="color: blue;">adc_to_voltage</span>(<span style="color: #b00040;">uint16_t</span> adc) {
<span style="color: #b00040;">esp_adc_cal_characteristics_t</span> adc_chars;
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, <span style="color: #666666;">1100</span>, <span style="color: #666666;">&</span>adc_chars);
<span style="color: green; font-weight: bold;">return</span> esp_adc_cal_raw_to_voltage(adc, <span style="color: #666666;">&</span>adc_chars);
}
<span style="color: green; font-weight: bold;">for</span> (<span style="color: #b00040;">uint16_t</span> adc<span style="color: #666666;">=2000</span>; adc<span style="color: #666666;"><4096</span>; adc<span style="color: #666666;">++</span>) {
<span style="color: #b00040;">uint32_t</span> v <span style="color: #666666;">=</span> adc_to_voltage(adc);
<span style="color: green; font-weight: bold;">if</span> (v <span style="color: #666666;">>=</span> SUPPLY_VLOW<span style="color: #666666;">/2</span>) {
RTCVAR_SET(VDD_LOW, adc);
RTCVAR_SET(VDD_ADC, adc<span style="color: #666666;">+1</span>);
<span style="color: green; font-weight: bold;">break</span>;
}
}
</pre></div>
<p>The ULP code below makes a series of 8 ADC readings, computes the average and writes it to VDD_ADC just in case the main CPU needs to use the value. Then it compares the average reading with VDD_LOW. If lower, it wakes the main CPU with a new wake reason <b>WAKE_VDD_THRESHOLD_LOW</b>. The main CPU will then stop the clock and write the clock time to the EEPROM.</p>
<!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #408080; font-style: italic;">// R0 = R0 + ADC reading from GPIO33</span>
<span style="color: #bc7a00;">#define X_ADC() \</span>
<span style="color: #bc7a00;"> I_ADC(R1, 0, VDD_CHANNEL), \</span>
<span style="color: #bc7a00;"> I_ADDR(R0, R0, R1)</span>
<span style="color: #408080; font-style: italic;">// Sum 8 x ADC readings from GPIO33</span>
<span style="color: #bc7a00;">#define X_ADC_SUM() \</span>
<span style="color: #bc7a00;"> X_ADC(), \</span>
<span style="color: #bc7a00;"> X_ADC(), \</span>
<span style="color: #bc7a00;"> X_ADC(), \</span>
<span style="color: #bc7a00;"> X_ADC(), \</span>
<span style="color: #bc7a00;"> X_ADC(), \</span>
<span style="color: #bc7a00;"> X_ADC(), \</span>
<span style="color: #bc7a00;"> X_ADC(), \</span>
<span style="color: #bc7a00;"> X_ADC()</span>
...
I_MOVI(R0, <span style="color: #666666;">0</span>),
X_ADC_SUM(), <span style="color: #408080; font-style: italic;">// R0 = sum(8 x ADC readings)</span>
I_RSHI(R0, R0, <span style="color: #666666;">3</span>), <span style="color: #408080; font-style: italic;">// Divide by 8 to get average ADC reading</span>
X_RTC_SAVE(VDD_ADC, R0), <span style="color: #408080; font-style: italic;">// Save to RTCMEM[VDD_ADC] for main CPU</span>
X_RTC_LOAD(VDD_LOW, R1),
X_RTC_LOAD(VDD_ADC, R2),
I_SUBR(R0, R1, R2), <span style="color: #408080; font-style: italic;">// VDD_LOW - VDD_ADC </span>
M_BXF(_READ_ADC_DONE), <span style="color: #408080; font-style: italic;">// Wake if VDD_ADC < VDD_LOW</span>
X_RTC_SAVEI(WAKE_REASON, WAKE_VDD_THRESHOLD_LOW),
I_WAKE(),
I_HALT(),
M(_READ_ADC_DONE),
...
</pre></div>
<p>In the main <b>setup()</b> code, all we have to do is to deal with another wake reason:</p><!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"> <span style="border: 1px solid rgb(255, 0, 0);">#</span>define WAKE_VDD_THRESHOLD_LOW <span style="color: #666666;">2</span>
</pre><pre style="line-height: 125%; margin: 0px;">...
<span style="color: green; font-weight: bold;">else</span> {
<span style="color: #408080; font-style: italic;">// Wake up due to ULP</span>
<span style="color: #b00040;">uint16_t</span> reason <span style="color: #666666;">=</span> RTCVAR(WAKE_REASON); RTCVAR_SET(WAKE_REASON, <span style="color: #666666;">0</span>);
<span style="color: green; font-weight: bold;">switch</span>(reason) {
<span style="color: green; font-weight: bold;">case</span> WAKE_VDD_THRESHOLD_LOW:
<span style="color: #408080; font-style: italic;">// Stop clock and write time to EEPROM</span>
<span style="color: green; font-weight: bold;">break</span>;
<span style="color: green; font-weight: bold;">case</span> WAKE_RESET_BUTTON:
esp_hard_restart();</pre><pre style="line-height: 125%; margin: 0px;"> }
}
...
</pre></div>
<p>The test went quite well when I hooked up GPIO32 to a variable voltage source. When the voltage drops below 4.2V, the main CPU is woken up with the correct wake reason. </p><p><a href="https://www.randseq.org/search/label/espclock">ESPCLOCK1</a> / <a href="https://www.randseq.org/search/label/espclock2">ESPCLOCK2</a> / <a href="https://www.randseq.org/search/label/espclock3">ESPCLOCK3</a> / <a href="https://www.randseq.org/search/label/espclock4">ESPCLOCK4</a></p>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com2tag:blogger.com,1999:blog-5552255504869253075.post-13446006530432623492021-03-17T22:40:00.002-07:002022-04-13T19:15:32.350-07:00ESPCLOCK4 - A Simpler Reset Module<p>In ESPCLOCK3, the following reset circuitry was used:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4Zv81i0BzBvfBYgR9H0qAeEZ7wFF0YfqO-U-B1k8oXb635EiYQ8kzL7zvC4ecatSxXSp6g9amxdO7rLRCPVmV7VKpvXUpdZitT43QgUSKYHTFpIIfvH9Mu3q8uqCFPowLCqgIP9X5XbU/s358/reset-circuitry.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="358" data-original-width="291" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4Zv81i0BzBvfBYgR9H0qAeEZ7wFF0YfqO-U-B1k8oXb635EiYQ8kzL7zvC4ecatSxXSp6g9amxdO7rLRCPVmV7VKpvXUpdZitT43QgUSKYHTFpIIfvH9Mu3q8uqCFPowLCqgIP9X5XbU/s320/reset-circuitry.png" /></a></div>This generates a short low pulse for the RST pin on the ESP8266 even if the switch is held down, so that the program can read the switch pin after reset. If it it found that the switch is still held down, it can perform a factory reset.<div><p>In the ESPCLOCK4, I wanted to try a software-only approach with the ULP. The main idea is the ULP will check the switch (with a little software debounce processing). If it finds the switch depressed, it will wake the main processor, which will then perform a software reset. </p><p>This means we will only need a single pushbutton, with one end connected to an input pin (pulled up), and the other end connected to GND. No need for additional resistors/capacitors.</p><p>I connect the reset button to GPIO4/GND, and add the following definitions:</p><!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #bc7a00;">#define RESETBTN_PIN RTCIO_GPIO4_CHANNEL</span>
<span style="color: #bc7a00;">#define RESETBTN_PIN_GPIO GPIO_NUM_4</span>
</pre><pre style="line-height: 125%; margin: 0px;"><span style="color: #bc7a00;">#define WAKE_RESET_BUTTON 1</span></pre></div>
<p>The following code is added to <b>init_gpio()</b> to configure the pin as pulled-up input:</p><!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #b00040;">void</span> <span style="color: blue;">init_gpio</span>() {
...
init_gpio_pin(RESETBTN_PIN_GPIO, RTC_GPIO_MODE_INPUT_OUTPUT, HIGH);
}
</pre></div>
<p>Another RTC_SLOW_MEM variable is added to store the wake up reason, which the ULP will use to indicate to the main CPU why it has been woken up.</p><!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #408080; font-style: italic;">// Named indices into RTC_SLOW_MEM</span>
<span style="color: green; font-weight: bold;">enum</span> {
...
WAKE_REASON, <span style="color: #408080; font-style: italic;">// Reason for waking up main CPU</span>
};
</pre></div>
<p>Also add a macro to read the state of a given GPIO pin:</p><!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #408080; font-style: italic;">// Read GPIO pin value into R0</span>
<span style="color: #bc7a00;">#define X_GPIO_GET(pin) \</span>
<span style="color: #bc7a00;"> I_RD_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S+pin, RTC_GPIO_IN_NEXT_S+pin)</span>
</pre></div>
<p>Now in the ULP, add the following code:</p><!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"> <span style="color: #408080; font-style: italic;">// Is reset button pressed (active low)?</span>
X_GPIO_GET(RESETBTN_PIN),
M_BGE(_RESETBTN_CHECKED, <span style="color: #666666;">1</span>),
I_DELAY(<span style="color: #666666;">8000</span>), <span style="color: #408080; font-style: italic;">// 1ms debounce</span>
X_GPIO_GET(RESETBTN_PIN),
M_BGE(_RESETBTN_CHECKED, <span style="color: #666666;">1</span>),
X_RTC_SAVEI(WAKE_REASON, WAKE_RESET_BUTTON),
M_LABEL(_WAIT_WAKEUP_RDY),
I_RD_REG(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP_S, RTC_CNTL_RDY_FOR_WAKEUP_S),
M_BL(_WAIT_WAKEUP_RDY, <span style="color: #666666;">1</span>),
I_WAKE(),
I_END(),
I_HALT(),
M_LABEL(_RESETBTN_CHECKED),
</pre><pre style="line-height: 125%; margin: 0px;"> ...</pre></div>
<p>What the code basically does is to read GPIO4. If it is low, it will wait for a debouncing period of 1ms and read the pin again. If it it still low, it writes <b>WAKE_RESET_BUTTON (1)</b> as the reason code to <b>WAKE_REASON</b>. Then it waits for the <b>RTC_CNTL_RDY_FOR_WAKEUP</b> bit to become 1, which indicates that the main CPU is ready for wake up. At this point, we issue:</p><p>- <b>I_WAKE()</b> to wake the main CPU</p><p>- <b>I_END()</b> to stop the RTC timer, because we don't want to ULP code to be run again</p><p>- <b>I_HALT()</b> to stop the ULP code</p><p>In the <b>setup()</b> code, an additional line is added to allow the ULP code to wake the main CPU:</p><!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><b> esp_sleep_enable_ulp_wakeup();</b>
esp_deep_sleep_start();
</pre></div>
<p>The code to handle the wakeup reason is this:</p><!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"> <span style="color: #b00040;">esp_sleep_wakeup_cause_t</span> cause <span style="color: #666666;">=</span> esp_sleep_get_wakeup_cause();
<span style="color: green; font-weight: bold;">if</span> (cause <span style="color: #666666;">!=</span> ESP_SLEEP_WAKEUP_ULP) {
<span style="color: #408080; font-style: italic;">// Initial startup</span>
...
<span style="color: #408080; font-style: italic;">// If reset detected, start in config mode</span>
delay(<span style="color: #666666;">1000</span>); <span style="color: #408080; font-style: italic;">// allow time for reset button to be released</span>
pinMode(RESETBTN_PIN_GPIO, INPUT_PULLUP);
<span style="color: #b00040;">bool</span> reset <span style="color: #666666;">=</span> <span style="color: #666666;">!</span>digitalRead(RESETBTN_PIN_GPIO);
<span style="color: green; font-weight: bold;">if</span> (reset) {
<span style="color: #408080; font-style: italic;">// Reset button is still held down</span>
<span style="color: #408080; font-style: italic;">// Perform factory reset</span>
}
...
} <span style="color: green; font-weight: bold;">else</span> {
<span style="color: #408080; font-style: italic;">// Wake up due to ULP</span>
<span style="color: #b00040;">uint16_t</span> reason <span style="color: #666666;">=</span> RTCVAR(WAKE_REASON);
<span style="color: green; font-weight: bold;">if</span> (reason <span style="color: #666666;">==</span> WAKE_RESET_BUTTON) {
esp_restart();
}
}
</pre></div>
<p>The <b>cause</b> value will tell us whether the main CPU is just starting up, or it is being woken up by the ULP. If it is starting up, it checks GPIO4 to see if the button is still being held down. If so, it can initiate the factory reset logic.</p><p>If it is woken up by the ULP, it checks the wake reason to determine the action to take. So if the wake reason is <b>WAKE_RESET_BUTTON</b>, we call <b>esp_restart()</b> to reset the ESP32.</p><h3 style="text-align: left;">Issue with esp_restart()</h3><p>After playing around with the code above, it was found that <b>esp_restart()</b> does not always perform a full reset of the ESP32. This resulted in the ULP code not running, or running once and stopping, in a unpredictable manner. This behaviour can be observed every 2 or 3 resets using <b>esp_restart()</b>.</p><p>I googled around, and <a href="https://esp32.com/viewtopic.php?t=9298" target="_blank">this post</a> seems to fit the bill. It also appears <b>esp_restart()</b> has a long history of problems: <a href="https://github.com/espressif/esp-idf/issues/2926" target="_blank">example</a>, <a href="https://github.com/espressif/esp-idf/issues/4115" target="_blank">example</a>, <a href="https://github.com/espressif/esp-idf/issues/2756" target="_blank">example</a>, <a href="https://github.com/espressif/arduino-esp32/issues/1270" target="_blank">example</a>. This is because <b>esp_restart()</b> performs a software reset of the unit, which is different from pulling the <b>EN</b> pin low, and sometimes it misses certain things.</p><p>After much digging around and hair pulling, I finally found a reliable way to perform a hardware reset of the MCU:</p><!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #b00040;">void</span> <span style="color: blue;">esp_hardware_restart</span>() {
rtc_wdt_protect_off();
rtc_wdt_disable();
rtc_wdt_set_length_of_reset_signal(RTC_WDT_SYS_RESET_SIG, RTC_WDT_LENGTH_3_2us);
rtc_wdt_set_stage(RTC_WDT_STAGE0, RTC_WDT_STAGE_ACTION_RESET_RTC);
rtc_wdt_set_time(RTC_WDT_STAGE0, <span style="color: #666666;">500</span>);
rtc_wdt_enable();
rtc_wdt_protect_on();
<span style="color: green; font-weight: bold;">while</span>(<span style="color: green;">true</span>);
}
</pre></div>
<p>This uses the RTC watchdog timer to induce a hardware reset. The idea for this solution is taken from <a href="https://www.esp32.com/viewtopic.php?t=14705" target="_blank">this post</a> and <a href="https://www.esp32.com/viewtopic.php?t=14705" target="_blank">this post</a>. </p><p>The above code sets the stage 0 timeout for the RTC WDT to 500ms. If the WDT is not "fed" or disabled by the code within that time, a hardware reset signal is initiated. You can see that a hardware reset is performed by connecting the ESP32 to a serial monitor. The same status messages as powering on the ESP32 are displayed when the reset occurs. </p><p>When this solution is adopted, everything works as expected. The ULP code runs without a hitch after each reset.</p><p><a href="https://www.randseq.org/search/label/espclock">ESPCLOCK1</a> / <a href="https://www.randseq.org/search/label/espclock2">ESPCLOCK2</a> / <a href="https://www.randseq.org/search/label/espclock3">ESPCLOCK3</a> / <a href="https://www.randseq.org/search/label/espclock4">ESPCLOCK4</a></p></div>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com0tag:blogger.com,1999:blog-5552255504869253075.post-72956992797309104772021-03-16T19:51:00.001-07:002022-04-13T19:15:19.003-07:00ESPCLOCK4 - A Rethink On Tick Pulse Generation<p>Having dabbled recently with PWM (Pulse Width Modulation) for motor control, it struck me that maybe the same technique can be applied for pulsing the Lavet stepper motor in the analog clock.</p><p>Just a recap. The Lavet stepper motor within the analog clock is design to function at ~1.5V. However, the output on the ESP8266 and ESP32 is 3.3V, and in ESPCLOCK3, the ATtiny85 is powered at ~3V. For reasons I don't fully understand, the pulse width needs to be as long as 100ms in this case, otherwise the clock will not tick reliably. This also leads to high power draw, because higher voltage + longer pulse width. In contrast, the original clock signal is 32ms pulse width at ~1.5V (which can go as low as ~1.1V since it is running on a normal AA battery).</p><p>So in <a href="https://www.randseq.org/2020/05/espclock3-final-version.html">ESPCLOCK3</a>, a diode bridge is introduced, where 2 diodes in each direction drops the 3V output by the ATtiny85 down to ~1.8V (3V - 0.6V x 2), which worked remarkably well in reducing the power draw and making the clock tick with 100% reliability.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizCyKt8uIZjalv3bNQQgc7TsY08k0zjRMXIdEQi0LDF0EV8uqzcWgwk2hTE6wVztwTbRhGkpcUUSlCBjrKMYOItDiBGoVG6RonuulM4teMQYkZNhz_aReQYUtg4R3tf1O3aEIiIGjmJZE/s710/diode-clamp.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="399" data-original-width="710" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizCyKt8uIZjalv3bNQQgc7TsY08k0zjRMXIdEQi0LDF0EV8uqzcWgwk2hTE6wVztwTbRhGkpcUUSlCBjrKMYOItDiBGoVG6RonuulM4teMQYkZNhz_aReQYUtg4R3tf1O3aEIiIGjmJZE/w640-h360/diode-clamp.png" width="640" /></a></div><p>Much like PWM in DC motor control, the idea is to eliminate the diode bridge and instead output a PWM modulated pulse signal, which should reduce the effective voltage to the clock.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1gdRs4pe8Vm7REG0zSm95i_AcbMW_7pAufSwttcvjwkdN1F4sX_mUzbrAE3XwZF8vjTGVKmRHSDNthz1lT6exJYHU6En1XmFcxXURAt0rIqL8bs1cvg27oX-ysuxabkS5IN2F-ffGaow/s1144/pwm.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="175" data-original-width="1144" height="98" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1gdRs4pe8Vm7REG0zSm95i_AcbMW_7pAufSwttcvjwkdN1F4sX_mUzbrAE3XwZF8vjTGVKmRHSDNthz1lT6exJYHU6En1XmFcxXURAt0rIqL8bs1cvg27oX-ysuxabkS5IN2F-ffGaow/w640-h98/pwm.png" width="640" /></a></div><div><br /></div>With some experimentation, the ideal duty cycle of the PWM was found to be 62.5%. The output is 1.25ms on, 0.75ms off, repeated 20 times (for a total of 40ms). Both 62.5% and 40ms were found to be lower bounds for reliable operation.<div><br /></div><div>The 62.5% duty cycle surprised me, because from my naive calculation, for an effective voltage of say, 1.5V, the duty cycle should be 1.5 / 3.3, which is 45%. 62.5% duty cycle implies an effective voltage of ~2V, which is much higher than I expected. I suspect my naive formula for calculating the duty cycle is wrong.</div><div><br /></div><div>It was quite easy to modify the test program to output PWM pulses. This macro was added:</div><div><br /></div>
<!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #408080; font-style: italic;">// Pulse given tickpin - 40ms duration = 20 x (1.25/2ms) PWM => 62.5% duty cycle ~ 2V</span>
<span style="color: #bc7a00;">#define X_TICK_PIN(pin) \</span>
<span style="color: #bc7a00;"> I_MOVI(R0, 20), \</span>
<span style="color: #bc7a00;"> M_LABEL(_PULSE_##pin), \</span>
<span style="color: #bc7a00;"> X_GPIO_SET(pin, 1), I_DELAY(10000), \</span>
<span style="color: #bc7a00;"> X_GPIO_SET(pin, 0), I_DELAY(16000-10000), \</span>
<span style="color: #bc7a00;"> I_SUBI(R0, R0, 1), \</span>
<span style="color: #bc7a00;"> M_BGE(_PULSE_##pin, 1)</span>
</pre></div>
<div><br /></div><div>The macro is then used in the ticking logic:</div><div><br /></div>
<!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"> <span style="color: #408080; font-style: italic;">// Decide which pin to tick</span>
X_RTC_LOAD(TICK_PIN, R0),
M_BGE(_HANDLE_TICKPIN2, <span style="color: #666666;">2</span>),
<span style="color: #408080; font-style: italic;">// Pulse tick pin 1</span>
X_TICK_PIN(TICKPIN1),
X_RTC_SAVEI(TICK_PIN, <span style="color: #666666;">2</span>),
I_HALT(),
M_LABEL(_HANDLE_TICKPIN2),
<span style="color: #408080; font-style: italic;">// Pulse tick pin 2</span>
X_TICK_PIN(TICKPIN2),
X_RTC_SAVEI(TICK_PIN, <span style="color: #666666;">1</span>),
I_HALT()
</pre></div>
<div><br /></div><div>I was pleasantly surprised this worked very well. It made the clock tick reliably without the diode bridge. Even better, this approach brought the power draw from <b>1.37mA</b> down to <b>1.13mA</b>, so it's a keeper!</div><div><br /></div><div><a href="https://www.randseq.org/search/label/espclock">ESPCLOCK1</a> / <a href="https://www.randseq.org/search/label/espclock2">ESPCLOCK2</a> / <a href="https://www.randseq.org/search/label/espclock3">ESPCLOCK3</a> / <a href="https://www.randseq.org/search/label/espclock4">ESPCLOCK4</a></div>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com3tag:blogger.com,1999:blog-5552255504869253075.post-49440141601716036062021-03-15T20:32:00.002-07:002022-04-13T19:15:11.960-07:00ESPCLOCK4 - Generating Tick Pulses<h3 style="text-align: left;">Hardware</h3><p>I settled on the <a href="https://github.com/r0oland/ESP32_mini_KiCad_Library" target="_blank">ESP32 Mini</a> for this project:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZrTzBEVmh-U3Dt0AfWlVaPb9NumRveYcP0G2r0UV0OKHjzCWAU9H7EA_3RdsssXSyCcsWFwm2yNRD-Ur3olMb4jAcGfSmJopNRlW2S4D_G10fbNTCgiZFNAi6b89H2klaA8t8taHDNCY/s696/esp32-mini-01.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="479" data-original-width="696" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZrTzBEVmh-U3Dt0AfWlVaPb9NumRveYcP0G2r0UV0OKHjzCWAU9H7EA_3RdsssXSyCcsWFwm2yNRD-Ur3olMb4jAcGfSmJopNRlW2S4D_G10fbNTCgiZFNAi6b89H2klaA8t8taHDNCY/w640-h440/esp32-mini-01.png" width="640" /></a></div><div><br /></div>A couple of reasons why I chose this board:<p></p><p>- It has a much smaller form factor than the <a href="https://www.aliexpress.com/item/32834130422.html" target="_blank">canonical ESP32 dev board</a>, yet have a good number of pins exposed through its double-row header layout. </p><p>- It is one of the cheaper ESP32 dev boards. I got mine for ~A$6 (including shipping) on <a href="https://www.aliexpress.com/item/32858054775.html" target="_blank">AliExpress</a>.<br /><br /></p><h3 style="text-align: left;">Generating Tick Pulses using the ULP</h3><p>The <i>platformio.ini</i> file looks like this:</p>
<!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: green; font-weight: bold;">[platformio]</span>
<span style="color: #7d9029;">default_envs</span> <span style="color: #666666;">=</span> <span style="color: #ba2121;">wemos_d1_mini32</span>
<span style="color: green; font-weight: bold;">[env:wemos_d1_mini32]</span>
<span style="color: #7d9029;">platform</span> <span style="color: #666666;">=</span> <span style="color: #ba2121;">espressif32</span>
<span style="color: #7d9029;">framework</span> <span style="color: #666666;">=</span> <span style="color: #ba2121;">arduino</span>
<span style="color: #7d9029;">platform_packages</span> <span style="color: #666666;">=</span> <span style="color: #ba2121;">framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git</span>
<span style="color: #7d9029;">board</span> <span style="color: #666666;">=</span> <span style="color: #ba2121;">wemos_d1_mini32</span>
<span style="color: #7d9029;">upload_speed</span> <span style="color: #666666;">=</span> <span style="color: #ba2121;">912600</span>
<span style="color: #7d9029;">monitor_speed</span> <span style="color: #666666;">=</span> <span style="color: #ba2121;">115200</span>
</pre></div>
<p>Here's the test code:</p>
<!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><table><tbody><tr><td><pre style="line-height: 125%; margin: 0px;"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128</pre></td><td><pre style="line-height: 125%; margin: 0px;"><span style="color: #408080; font-style: italic;">// Make 60 ticks on a non-sweeping clock and stop</span>
<span style="color: #bc7a00;">#include <Arduino.h></span>
<span style="color: #bc7a00;">#include <esp32/ulp.h></span>
<span style="color: #bc7a00;">#include <driver/adc.h></span>
<span style="color: #bc7a00;">#include <driver/rtc_io.h></span>
<span style="color: #408080; font-style: italic;">// Named indices into RTC_SLOW_MEM</span>
<span style="color: green; font-weight: bold;">enum</span> {
TICK_PIN, <span style="color: #408080; font-style: italic;">// tick pin: 1 or 2</span>
TICK_COUNT, <span style="color: #408080; font-style: italic;">// stop ticking after certain count to check that we have no missed ticks</span>
};
<span style="color: #bc7a00;">#define RTCVAR_OFFSET 2000 </span><span style="color: #408080; font-style: italic;">// RTC vars starts here in RTC_SLOW_MEM (32-bit/8KB => max range = 2047)</span>
<span style="color: #bc7a00;">#define RTCVAR(var) (RTC_SLOW_MEM[RTCVAR_OFFSET+var] & UINT16_MAX)</span>
<span style="color: #bc7a00;">#define RTCVAR_SET(var, value) RTC_SLOW_MEM[RTCVAR_OFFSET+var] = value</span>
<span style="color: #bc7a00;">#define TICK_INTERVAL 1000000 - 40000 </span><span style="color: #408080; font-style: italic;">// delay between calls to ULP code in usec (1sec - 40msec)</span>
<span style="color: #bc7a00;">#define TICKPIN1 RTCIO_GPIO25_CHANNEL</span>
<span style="color: #bc7a00;">#define TICKPIN2 RTCIO_GPIO27_CHANNEL</span>
<span style="color: #bc7a00;">#define TICKPIN1_GPIO GPIO_NUM_25 </span>
<span style="color: #bc7a00;">#define TICKPIN2_GPIO GPIO_NUM_27 </span>
<span style="color: #408080; font-style: italic;">// Load RTCMEM[var] into reg</span>
<span style="color: #bc7a00;">#define X_RTC_LOAD(var, reg) \</span>
<span style="color: #bc7a00;"> I_MOVI(R3, RTCVAR_OFFSET+var), \<br /></span><span style="color: #bc7a00;"> I_LD(reg, R3, 0)</span>
<span style="color: #408080; font-style: italic;">// Save reg value into RTCMEM[var]</span>
<span style="color: #bc7a00;">#define X_RTC_SAVE(var, reg) \</span>
<span style="color: #bc7a00;"> I_MOVI(R3, RTCVAR_OFFSET+var), \<br /></span><span style="color: #bc7a00;"> I_ST(reg, R3, 0)</span>
<span style="color: #408080; font-style: italic;">// Save constant value into RTCMEM[var]</span>
<span style="color: #bc7a00;">#define X_RTC_SAVEI(var, value) \</span>
<span style="color: #bc7a00;"> I_MOVI(R3, RTCVAR_OFFSET+var), \<br /></span><span style="color: #bc7a00;"> I_MOVI(R0, value), \</span>
<span style="color: #bc7a00;"> I_ST(R0, R3, 0)</span>
<span style="color: #408080; font-style: italic;">// Set GPIO pin value to level</span>
<span style="color: #bc7a00;">#define X_GPIO_SET(pin, level) \</span>
<span style="color: #bc7a00;"> I_WR_REG_BIT(RTC_GPIO_OUT_REG, RTC_GPIO_IN_NEXT_S+pin, level)</span>
<span style="color: #408080; font-style: italic;">// Delay 40ms (64000 cycles = 8ms)</span>
<span style="color: #bc7a00;">#define X_TICK_LEN() \</span>
<span style="color: #bc7a00;"> I_DELAY(64000), \</span>
<span style="color: #bc7a00;"> I_DELAY(64000), \</span>
<span style="color: #bc7a00;"> I_DELAY(64000), \</span>
<span style="color: #bc7a00;"> I_DELAY(64000), \</span>
<span style="color: #bc7a00;"> I_DELAY(64000) </span>
<span style="color: #408080; font-style: italic;">// Branch labels</span>
<span style="color: green; font-weight: bold;">enum</span> {
_INC_TICK_COUNT,
_PULSE_TICKPIN_2,
};
<span style="color: green; font-weight: bold;">const</span> <span style="color: #b00040;">ulp_insn_t</span> ulp_code[] <span style="color: #666666;">=</span> {
<span style="color: #408080; font-style: italic;">// Stop after 60 ticks</span>
X_RTC_LOAD(TICK_COUNT, R0),
M_BL(_INC_TICK_COUNT, <span style="color: #666666;">60</span>),
I_END(),
I_HALT(),
M_LABEL(_INC_TICK_COUNT),
I_ADDI(R0, R0, <span style="color: #666666;">1</span>),
X_RTC_SAVE(TICK_COUNT, R0),
<span style="color: #408080; font-style: italic;">// Decide which pin to tick</span>
X_RTC_LOAD(TICK_PIN, R0),
M_BGE(_PULSE_TICKPIN_2, <span style="color: #666666;">2</span>),
<span style="color: #408080; font-style: italic;">// Pulse tick pin 1</span>
X_GPIO_SET(TICKPIN1, <span style="color: #666666;">1</span>),
X_TICK_LEN(),
X_GPIO_SET(TICKPIN1, <span style="color: #666666;">0</span>),
X_RTC_SAVEI(TICK_PIN, <span style="color: #666666;">2</span>),
I_HALT(),
M_LABEL(_PULSE_TICKPIN_2),
<span style="color: #408080; font-style: italic;">// Pulse tick pin 2</span>
X_GPIO_SET(TICKPIN2, <span style="color: #666666;">1</span>),
X_TICK_LEN(),
X_GPIO_SET(TICKPIN2, <span style="color: #666666;">0</span>),
X_RTC_SAVEI(TICK_PIN, <span style="color: #666666;">1</span>),
I_HALT()
};
<span style="color: #b00040;">void</span> <span style="color: blue;">init_vars</span>() {
RTCVAR_SET(TICK_PIN, <span style="color: #666666;">1</span>);
RTCVAR_SET(TICK_COUNT, <span style="color: #666666;">0</span>);
}
<span style="color: #b00040;">void</span> <span style="color: blue;">init_gpio_pin</span>(<span style="color: #b00040;">gpio_num_t</span> pin, <span style="color: #b00040;">rtc_gpio_mode_t</span> state, <span style="color: #b00040;">int</span> level) {
rtc_gpio_init(pin);
rtc_gpio_set_direction(pin, state);
rtc_gpio_set_level(pin, level);
}
<span style="color: #b00040;">void</span> <span style="color: blue;">init_gpio</span>() {
rtc_gpio_isolate(GPIO_NUM_12); <span style="color: #408080; font-style: italic;">// Reduce current drain through pullups/pulldowns</span>
rtc_gpio_isolate(GPIO_NUM_15);
esp_deep_sleep_disable_rom_logging(); <span style="color: #408080; font-style: italic;">// Suppress boot messages</span>
init_gpio_pin(TICKPIN1_GPIO, RTC_GPIO_MODE_OUTPUT_ONLY, LOW);
init_gpio_pin(TICKPIN2_GPIO, RTC_GPIO_MODE_OUTPUT_ONLY, LOW);
}
<span style="color: #b00040;">void</span> <span style="color: blue;">init_ulp</span>() {
<span style="color: #b00040;">size_t</span> size <span style="color: #666666;">=</span> <span style="color: green; font-weight: bold;">sizeof</span>(ulp_code) <span style="color: #666666;">/</span> <span style="color: green; font-weight: bold;">sizeof</span>(<span style="color: #b00040;">ulp_insn_t</span>);
ulp_set_wakeup_period(<span style="color: #666666;">0</span>, TICK_INTERVAL);
ulp_process_macros_and_load(<span style="color: #666666;">0</span>, ulp_code, <span style="color: #666666;">&</span>size);
ulp_run(<span style="color: #666666;">0</span>);
}
<span style="color: #b00040;">void</span> <span style="color: blue;">setup</span>() {
<span style="color: #408080; font-style: italic;">// Reduce CPU frequency to save power</span>
setCpuFrequencyMhz(<span style="color: #666666;">80</span>);
<span style="color: #b00040;">esp_sleep_wakeup_cause_t</span> cause <span style="color: #666666;">=</span> esp_sleep_get_wakeup_cause();
<span style="color: green; font-weight: bold;">if</span> (cause <span style="color: #666666;">!=</span> ESP_SLEEP_WAKEUP_ULP) {
<span style="color: #408080; font-style: italic;">// Initial startup</span>
init_vars();
init_gpio();
init_ulp();
}
<span style="color: #408080; font-style: italic;">// Power off RTC FAST MEM during deep sleep to save power</span>
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
esp_deep_sleep_start();
}
<span style="color: #b00040;">void</span> <span style="color: blue;">loop</span>() {
}
</pre></td></tr></tbody></table></div>
<p>The 2 pins assigned to emit the tick pulses are GPIO25 and GPIO27. The ULP code generates 60 tick pulses (30 on each pin, 40ms pulse width) and stops. Since this is exactly 1 minute, it allows me to easily check if there are any missed ticks. The pulses are fed into the diode bridge in the ESPCLOCK3 circuit, and makes the analog clock tick as expected. </p><p>Using the <a href="https://www.randseq.org/2019/09/current-meter.html">current meter</a>, the current draw was determined to be <b>2.62mA</b>.</p><p>Next, I used a precision side cutter to remove the red power LED that is always illuminated when power is applied and cannot be turned off.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiaur3WofVu-ATFmXAJNukr81u00FjMxprnsW9TDCcCblNKqgVdkW6DnNNOGb-F9vstXxfskoAIkPvN62aQTiUum4Hh7tCzdLxcK40D36mZwckVDHn4O7Mon8Ac7CeZ3Zc6C9WDXoUXLx0/s781/esp32-mini-02.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="781" data-original-width="759" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiaur3WofVu-ATFmXAJNukr81u00FjMxprnsW9TDCcCblNKqgVdkW6DnNNOGb-F9vstXxfskoAIkPvN62aQTiUum4Hh7tCzdLxcK40D36mZwckVDHn4O7Mon8Ac7CeZ3Zc6C9WDXoUXLx0/w389-h400/esp32-mini-02.png" width="389" /></a></div>After the power LED is removed, the current draw of the above code is now <b>1.37mA</b>.<br /><p>So we are off to a good start! The power consumption is on par with the <b>~1.2mA</b> drawn on the ESPCLOCK3, which I am sure can be reduced further through optimization.</p><h3 style="text-align: left;">Some observations about ULP programming</h3><p>- <b>I_END()</b> only ends the RTC timer. It does not stop the ULP code. To do that, it needs to be followed by a <b>I_HALT()</b> command.</p><p>- Initially, I was under the impression that <b>ulp_set_wakeup_period()</b> is like Javascript's <b>setInterval()</b> i.e. it calls the ULP repeatedly at the specified interval eg. every second. Turns out I was wrong. The timer only starts counting down when <b>I_HALT()</b> is called. So if I want the ULP code to be called every second, and I know the ULP code takes ~40ms, then I need to subtract that from my wakeup period.</p><h3 style="text-align: left;">Jut for fun..</h3><p>I connected the circuit to the sweeping clock that I modded previously, and modified the code slightly to pulse 16 times a second (8 pulses on each pin, 32ms pulse width, spaced 62.5ms apart).</p><p>The power draw is measured to be a whooping <b>14.7mA</b>!</p><p><a href="https://www.randseq.org/search/label/espclock">ESPCLOCK1</a> / <a href="https://www.randseq.org/search/label/espclock2">ESPCLOCK2</a> / <a href="https://www.randseq.org/search/label/espclock3">ESPCLOCK3</a> / <a href="https://www.randseq.org/search/label/espclock4">ESPCLOCK4</a></p>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com0tag:blogger.com,1999:blog-5552255504869253075.post-61104716582844353862021-03-14T17:57:00.002-07:002022-04-13T19:15:01.165-07:00ESPCLOCK4 - Design<p>The idea for ESPCLOCK4 came after encountering <a href="https://hackaday.com/2020/01/07/how-low-can-an-esp32-go/">this article</a>. It made me realize the successor to the ESP8266 - the ESP32 - has an ultra low power (ULP) coprocessor that can perform simple processing eg. toggle pins, read ADC etc. while the power hungry main CPU is in deep sleep. </p><p>After reading through the specs, it seems the ULP can substitute for the ATtiny85 in the ESPCLOCK3 design. The advantage is that the I2C bus is not longer required, and the circuitry can be vastly simplified. </p><p>Just like the watchdog timer on the ATtiny85, it appears the real-time clock (RTC) that drives the ULP is <a href="https://www.esp32.com/viewtopic.php?t=3715">not very accurate</a> (5% jitter). However, I think I can use the same <a href="https://www.randseq.org/2018/11/espclock2-part-4-implementation.html">calibration technique</a> employed to adjust <i>OCR1A</i> on the ATtiny85 in the ESPCLOCK2 design to dynamically tune the RTC. Basically, by using the difference between the computed network time and actual network time, the RTC counter can be increased or decreased accordingly.</p><p>So here's the preliminary design. The ESP32 will again serve only as the conduit to the Internet to retrieve the network time. It will be in deep sleep otherwise to conserve power.</p><p>The ULP will perform 3 tasks:</p><p></p><ul style="text-align: left;"><li>It will advance the physical clock by generating the tick pulses.</li></ul><ul style="text-align: left;"><li>It will check to see if battery voltage is low. If so, it will wake ESP32 up, which will write the physical clock time to EEPROM and shutdown.</li></ul><ul style="text-align: left;"><li>It will monitor a reset button and perform a software reset when the button is pressed.</li></ul><div>As before, a supercapacitor will be used to supply power to the circuit until the ESP32 has time to properly shutdown when power is removed.</div><div><br /></div><div>I am interested to see what's the power profile of this approach compared to ESP8266 + ATtiny85.</div><div><br /></div><div><a href="https://www.randseq.org/search/label/espclock">ESPCLOCK1</a> / <a href="https://www.randseq.org/search/label/espclock2">ESPCLOCK2</a> / <a href="https://www.randseq.org/search/label/espclock3">ESPCLOCK3</a> / <a href="https://www.randseq.org/search/label/espclock4">ESPCLOCK4</a></div><p></p>Random Sequencehttp://www.blogger.com/profile/10546946005720252471noreply@blogger.com0